spring 如何让JacksonObjectMapper使用xjc生成的包含Jakarta注解的类?

xfb7svmp  于 2024-01-05  发布在  Spring
关注(0)|答案(1)|浏览(148)

这是我的箱子
我已经从一些XSD和WSDL文件中生成了一些源文件。生成的源文件有用Jakarta注解注解的类和属性。我的应用程序中有3层,
1.控制器层- Spring MVC控制器。它们接受一个XJC生成的类对象作为请求体,并给予另一个作为响应体
1.客户端层-使用Jaxb 2 Marshaller创建WebServiceTemplate并将请求作为SOAP请求转发到另一个URL的层

  1. XML元素类层-使用xjc生成的XML类型等效类
    我面临的问题是解析JAXBElement类型类的属性。下面是一个示例类,
  1. @XmlAccessorType(XmlAccessType.FIELD)
  2. @XmlType(name = "", propOrder = {
  3. "filter",
  4. "initialTerminationTime",
  5. "subscriptionPolicy",
  6. "any"
  7. })
  8. @XmlRootElement(name = "CreatePullPointSubscription")
  9. public class CreatePullPointSubscription {
  10. @XmlElement(name = "Filter")
  11. protected FilterType filter;
  12. @XmlElementRef(name = "InitialTerminationTime", namespace = "http://www.onvif.org/ver10/events/wsdl", type = JAXBElement.class, required = false)
  13. protected JAXBElement<String> initialTerminationTime;
  14. ...
  15. }

字符串
默认情况下,将这些类视为POJO,生成的模式显示ObjectMapper期望JSON主体反映JAXElement的定义,而不是实际属性的类型。我不希望用户发送JAXBElement的附加属性。
MyWebMvcConfigurer:

  1. @Override
  2. public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
  3. SimpleModule serializerModule = new SimpleModule();
  4. serializerModule.addSerializer(JAXBElement.class, new JAXBJsonSerializer());
  5. serializerModule.addDeserializer(Object.class, new JAXBJsonDeserializer<>(Object.class));
  6. for (HttpMessageConverter o : converters) {
  7. if (o instanceof AbstractJackson2HttpMessageConverter) {
  8. ObjectMapper om = ((AbstractJackson2HttpMessageConverter)o).getObjectMapper();
  9. om.addMixIn(JAXBElement.class, JAXBElementMixin.class);
  10. // This line tells swagger to show what I expect
  11. ModelConverters.getInstance().addConverter(new ModelResolver(om));
  12. om.registerModule(serializerModule);
  13. }
  14. }
  15. WebMvcConfigurer.super.configureMessageConverters(converters);
  16. }


ModelResolver似乎只是改变了开放的API模式,而不是实际的序列化。为此,我实现了一个自定义序列化器和一个序列化器,正如你在上面的代码中看到的。
JAXBJ声发射器

  1. package com.ibi.onvif.web;
  2. import com.fasterxml.jackson.core.JsonParser;
  3. import com.fasterxml.jackson.databind.DeserializationContext;
  4. import com.fasterxml.jackson.databind.JavaType;
  5. import com.fasterxml.jackson.databind.JsonDeserializer;
  6. import com.fasterxml.jackson.databind.JsonNode;
  7. import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
  8. import com.fasterxml.jackson.databind.deser.std.StdScalarDeserializer;
  9. import jakarta.xml.bind.JAXBElement;
  10. import jakarta.xml.bind.JAXBException;
  11. import jakarta.xml.bind.annotation.XmlElementDecl;
  12. import java.io.IOException;
  13. import java.lang.reflect.InvocationTargetException;
  14. import java.lang.reflect.Method;
  15. import java.util.Iterator;
  16. import java.util.Map;
  17. public class JAXBJsonDeserializer<T> extends StdDeserializer<T> {
  18. protected JAXBJsonDeserializer(Class<?> vc) {
  19. super(vc);
  20. }
  21. protected JAXBJsonDeserializer(JavaType valueType) {
  22. super(valueType);
  23. }
  24. protected JAXBJsonDeserializer(StdDeserializer<?> src) {
  25. super(src);
  26. }
  27. @Override
  28. public T deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
  29. try {
  30. // Get the root element's class
  31. Class<?> rootType = ctxt.getContextualType().getRawClass();
  32. // Check if the root element is part of a JAXB-generated package
  33. if (isJAXBGenerated(rootType)) {
  34. Object objectFactory = getObjectFactory(rootType);
  35. // Deserialize the JSON into a JsonNode
  36. JsonNode node = p.getCodec().readTree(p);
  37. // Identify the appropriate factory method by annotations or return type
  38. Method factoryMethod = findFactoryMethod(objectFactory, rootType, null);
  39. // Use the factory method to generate the object
  40. T rootObject = (T) factoryMethod.invoke(objectFactory);
  41. // Recursively process attributes
  42. processAttributes(objectFactory, rootObject, node, ctxt);
  43. return rootObject;
  44. } else {
  45. // If not a JAXB-generated class, use the default deserialization
  46. return ctxt.readValue(p, (Class<T>) rootType);
  47. }
  48. } catch (Exception e) {
  49. throw new IOException("JAXB deserialization error", e);
  50. }
  51. }
  52. private boolean isJAXBGenerated(Class<?> type) {
  53. // Implement logic to check if the class is part of a JAXB-generated package
  54. // For example, you can check package names or annotations.
  55. // Replace the following line with your actual logic.
  56. return type.getPackage().getName().startsWith("org.onvif");
  57. }
  58. private Object getObjectFactory(Class<?> type) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
  59. String objectFactoryClassName = type.getPackage().getName() + ".ObjectFactory";
  60. Class<?> objectFactoryClass = Class.forName(objectFactoryClassName);
  61. return objectFactoryClass.newInstance();
  62. }
  63. private Method findFactoryMethod(Object objectFactory, Class<?> rootType, String attr) throws NoSuchMethodException, NoSuchFieldException {
  64. // If attr is null, the caller is asking for rootType factory method
  65. Method[] methods = objectFactory.getClass().getDeclaredMethods();
  66. if (attr == null) {
  67. for (Method method : methods) {
  68. if (method.getName().equals(String.format("create%s", rootType.getName()))) {
  69. return method;
  70. }
  71. }
  72. throw new NoSuchMethodException("Factory method not found for class: " + rootType);
  73. }
  74. assert rootType.getField(attr).getType() == JAXBElement.class;
  75. for (Field field : rootType.getFields()) {
  76. if (field.getName().equals(attr)) {
  77. XmlElement annotation = field.getAnnotation(XmlElement.class);
  78. return objectFactory.getClass().getMethod(String.format("create%s%s", rootType.getName(), annotation.name()));
  79. }
  80. }
  81. throw new NoSuchMethodException("Factory method not found for class: " + rootType + " and attr: " + attr);
  82. }
  83. private void processAttributes(Object objectFactory, Object parentObject, JsonNode node, DeserializationContext ctxt)
  84. throws IllegalAccessException, InvocationTargetException, NoSuchMethodException, IOException, JAXBException, NoSuchMethodException, NoSuchFieldException {
  85. for (Iterator<Map.Entry<String, JsonNode>> it = node.fields(); it.hasNext(); ) {
  86. Map.Entry<String, JsonNode> field = it.next();
  87. String fieldName = field.getKey();
  88. JsonNode fieldValue = field.getValue();
  89. // Find the corresponding object factory field value generator
  90. Method setterMethod = findSetterMethod(parentObject.getClass(), fieldName);
  91. // Get the attribute value
  92. Object attributeValue;
  93. Class<?> parameterType = setterMethod.getParameterTypes()[0];
  94. if (parameterType == JAXBElement.class) {
  95. // if it is a JAXBElement-type field, call ObjectFactory to get factory method
  96. Method fieldFactoryMethod = findFactoryMethod(objectFactory, parentObject.getClass(), fieldName);
  97. // get attribute value from the object factory attribute factory
  98. attributeValue = fieldFactoryMethod.invoke(objectFactory, ctxt.readTreeAsValue(fieldValue, fieldFactoryMethod.getParameterTypes()[0]));
  99. } else {
  100. attributeValue = ctxt.readTreeAsValue(fieldValue, parameterType);
  101. }
  102. // Invoke the setter method to set the attribute value
  103. setterMethod.invoke(parentObject, attributeValue);
  104. }
  105. }
  106. private Method findSetterMethod(Class<?> parentClass, String fieldName) throws NoSuchMethodException {
  107. return parentClass.getMethod(String.format("set%s", fieldName));
  108. }
  109. }


JAXBJsonSerializer

  1. package com.ibi.onvif.web;
  2. import com.fasterxml.jackson.core.JsonGenerator;
  3. import com.fasterxml.jackson.databind.JsonSerializer;
  4. import com.fasterxml.jackson.databind.SerializerProvider;
  5. import jakarta.xml.bind.JAXBElement;
  6. import java.io.IOException;
  7. public class JAXBJsonSerializer extends JsonSerializer<Object> {
  8. @Override
  9. public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
  10. Object objectValue = ((JAXBElement<?>)value).getValue();
  11. gen.writeObject(objectValue);
  12. }
  13. }


ObjectMapper似乎从来没有使用过我的验证器。

  1. com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of
  2. `jakarta.xml.bind.JAXBElement` (no Creators, like default constructor, exist):
  3. no String-argument constructor/factory method to deserialize from String value ('PT600S')


如何让ObjectMapper工作?
任何帮助是赞赏!

j7dteeu8

j7dteeu81#

我通过为JAXBElement创建一个自定义的解析器解决了这个问题。JsonParser类允许我们访问父元素属性,通过它我可以识别当前JSON字段正在为哪个类字段进行解析,从而提取像XmlElementRefXmlElement这样的注解。
请在下面找到相同的代码。
JAXBElementDeserializer.java:

  1. public class JAXBElementDeserializer extends StdDeserializer<JAXBElement<?>>{
  2. public JAXBElementDeserializer() {
  3. super(JAXBElement.class);
  4. }
  5. @Override
  6. public JAXBElement<?> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
  7. try {
  8. // Get the root element's class
  9. Class<?> rootType = p.getParsingContext().getCurrentValue().getClass();
  10. // Check if the root element is part of a JAXB-generated package
  11. if (isJAXBGenerated(rootType)) {
  12. Object objectFactory = getObjectFactory(rootType);
  13. // Deserialize the JSON into a JsonNode
  14. JsonNode node = p.getCodec().readTree(p);
  15. // Identify the appropriate factory method by annotations or return type
  16. Method factoryMethod = findFactoryMethod(objectFactory, rootType, p.getCurrentName());
  17. //Get the parameter type
  18. Class<?> type = factoryMethod.getParameters()[0].getType();
  19. // Use the factory method to generate the object
  20. return (JAXBElement<?>) factoryMethod.invoke(objectFactory, ctxt.readTreeAsValue(node, type));
  21. } else {
  22. // If not a JAXB-generated class, use the default deserialization
  23. throw new IOException();
  24. }
  25. } catch (Exception e) {
  26. throw new IOException("JAXB deserialization error", e);
  27. }
  28. }
  29. private boolean isJAXBGenerated(Class<?> type) {
  30. // Implement logic to check if the class is part of a JAXB-generated package
  31. // For example, you can check package names or annotations.
  32. // Replace the following line with your actual logic.
  33. return type.getPackage().getName().startsWith("org.onvif");
  34. }
  35. private Object getObjectFactory(Class<?> type) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
  36. String objectFactoryClassName = type.getPackage().getName() + ".ObjectFactory";
  37. Class<?> objectFactoryClass = Class.forName(objectFactoryClassName);
  38. return objectFactoryClass.newInstance();
  39. }
  40. private Method findFactoryMethod(Object objectFactory, Class<?> rootType, String attr) throws NoSuchMethodException, NoSuchFieldException {
  41. String annotatedName;
  42. assert rootType.getField(attr).getType() == JAXBElement.class;
  43. for (Field field : rootType.getDeclaredFields()) {
  44. if (field.getName().equals(attr)) {
  45. XmlElement annotation = field.getAnnotation(XmlElement.class);
  46. if (annotation != null) {
  47. annotatedName = annotation.name();
  48. } else {
  49. XmlElementRef ref_annotation = field.getAnnotation(XmlElementRef.class);
  50. annotatedName = ref_annotation.name();
  51. }
  52. // Don't know the type, simple search the method by its name
  53. for (Method method : objectFactory.getClass().getMethods()) {
  54. if (method.getName().equals(String.format("create%s%s", rootType.getSimpleName(), annotatedName))) {
  55. return method;
  56. }
  57. }
  58. }
  59. }
  60. throw new NoSuchMethodException("Factory method not found for class: " + rootType + " and attr: " + attr);
  61. }
  62. }

字符串
JAXBElementSerializer:

  1. public class JAXBElementSerializer extends JsonSerializer<JAXBElement> {
  2. @Override
  3. public void serialize(JAXBElement value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
  4. Object objectValue = value.getValue();
  5. gen.writeObject(objectValue);
  6. }
  7. }


WebMvcConfigurer.java:

  1. public class OnvifWebMvcConfigurer implements WebMvcConfigurer {
  2. // Mixins
  3. public static interface JAXBElementMixin {
  4. @JsonValue
  5. Object getValue();
  6. }
  7. public static interface DurationMixin {
  8. @JsonValue
  9. String toString();
  10. }
  11. final private Class<?>[][] MIXIN_CLASSES = {
  12. {JAXBElement.class, JAXBElementMixin.class},
  13. {Duration.class, DurationMixin.class},
  14. };
  15. private final SimpleModule module = new SimpleModule();
  16. private void setupModule() {
  17. module.addSerializer(JAXBElement.class, new JAXBElementSerializer());
  18. module.addSerializer(Duration.class, new DurationSerializer());
  19. module.addDeserializer(JAXBElement.class, new JAXBElementDeserializer());
  20. module.addDeserializer(Duration.class, new DurationDeserializer());
  21. }
  22. @Override
  23. public void addInterceptors(InterceptorRegistry registry) {
  24. registry.addInterceptor(new OnvifCredentialsExtractionInterceptor());
  25. }
  26. private void configureMixins(ObjectMapper om) {
  27. for (Class<?>[] pair : MIXIN_CLASSES) {
  28. om.addMixIn(pair[0], pair[1]);
  29. SpringDocUtils.getConfig().replaceWithClass(pair[0], pair[1]);
  30. }
  31. }
  32. private void configureMessageConverter(HttpMessageConverter<?> httpMessageConverter) {
  33. if (!(httpMessageConverter instanceof AbstractJackson2HttpMessageConverter)) {
  34. return;
  35. }
  36. ObjectMapper om = ((AbstractJackson2HttpMessageConverter) httpMessageConverter).getObjectMapper();
  37. configureMixins(om);
  38. ModelConverters.getInstance().addConverter(new ModelResolver(om));
  39. om.registerModule(module);
  40. }
  41. @Override
  42. public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
  43. setupModule();
  44. for (HttpMessageConverter<?> o : converters) {
  45. configureMessageConverter(o);
  46. }
  47. WebMvcConfigurer.super.configureMessageConverters(converters);
  48. }
  49. }


我希望这对你有帮助!

展开查看全部

相关问题