如何让 Jackson ObjectMapper 与包含 Jakarta 注释的 xjc 生成的类一起使用?

问题描述 投票:0回答:1

这是我的案例,

我已经从一些 XSD 和 WSDL 文件生成了一些源文件。生成的源具有使用 Jakarta 注释进行注释的类和属性。我的应用程序中有 3 层,

  1. 控制器层 - Spring MVC 控制器。它们接受 XJC 生成的类对象之一作为请求主体,并给出另一个作为响应主体
  2. 客户端层 - 使用 Jaxb2Marshaller 创建 WebServiceTemplate 并将请求作为 SOAP 请求转发到另一个 URL 的层
  3. XML Element 类层 - 使用 xjc 生成的 XML 类型等效类

我面临的问题是解析 JAXBElement 类型的类的属性。下面是一个示例类,

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
    "filter",
    "initialTerminationTime",
    "subscriptionPolicy",
    "any"
})
@XmlRootElement(name = "CreatePullPointSubscription")
public class CreatePullPointSubscription {
    @XmlElement(name = "Filter")
    protected FilterType filter;

    @XmlElementRef(name = "InitialTerminationTime", namespace = "http://www.onvif.org/ver10/events/wsdl", type = JAXBElement.class, required = false)
    protected JAXBElement<String> initialTerminationTime;

    ...
}

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

MyWebMvcConfigurer:

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        SimpleModule serializerModule = new SimpleModule();
        serializerModule.addSerializer(JAXBElement.class, new JAXBJsonSerializer());
        serializerModule.addDeserializer(Object.class, new JAXBJsonDeserializer<>(Object.class));

        for (HttpMessageConverter o : converters) {
            if (o instanceof AbstractJackson2HttpMessageConverter) {
                ObjectMapper om = ((AbstractJackson2HttpMessageConverter)o).getObjectMapper();
                om.addMixIn(JAXBElement.class, JAXBElementMixin.class);

                // This line tells swagger to show what I expect
                ModelConverters.getInstance().addConverter(new ModelResolver(om));
                om.registerModule(serializerModule);
            }
        }

        WebMvcConfigurer.super.configureMessageConverters(converters);
    }

ModelResolver 似乎只是更改了开放 API 模式,而不是实际的反序列化。为此,我实现了一个自定义序列化器和一个反序列化器,如上面的代码所示。

JAXBJson反序列化器

package com.ibi.onvif.web;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;

import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.deser.std.StdScalarDeserializer;
import jakarta.xml.bind.JAXBElement;
import jakarta.xml.bind.JAXBException;
import jakarta.xml.bind.annotation.XmlElementDecl;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.Map;

public class JAXBJsonDeserializer<T> extends StdDeserializer<T> {
    protected JAXBJsonDeserializer(Class<?> vc) {
        super(vc);
    }

    protected JAXBJsonDeserializer(JavaType valueType) {
        super(valueType);
    }

    protected JAXBJsonDeserializer(StdDeserializer<?> src) {
        super(src);
    }

    @Override
    public T deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        try {
            // Get the root element's class
            Class<?> rootType = ctxt.getContextualType().getRawClass();

            // Check if the root element is part of a JAXB-generated package
            if (isJAXBGenerated(rootType)) {
                Object objectFactory = getObjectFactory(rootType);

                // Deserialize the JSON into a JsonNode
                JsonNode node = p.getCodec().readTree(p);

                // Identify the appropriate factory method by annotations or return type
                Method factoryMethod = findFactoryMethod(objectFactory, rootType, null);

                // Use the factory method to generate the object
                T rootObject = (T) factoryMethod.invoke(objectFactory);

                // Recursively process attributes
                processAttributes(objectFactory, rootObject, node, ctxt);

                return rootObject;
            } else {
                // If not a JAXB-generated class, use the default deserialization
                return ctxt.readValue(p, (Class<T>) rootType);
            }
        } catch (Exception e) {
            throw new IOException("JAXB deserialization error", e);
        }
    }

    private boolean isJAXBGenerated(Class<?> type) {
        // Implement logic to check if the class is part of a JAXB-generated package
        // For example, you can check package names or annotations.
        // Replace the following line with your actual logic.
        return type.getPackage().getName().startsWith("org.onvif");
    }

    private Object getObjectFactory(Class<?> type) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        String objectFactoryClassName = type.getPackage().getName() + ".ObjectFactory";
        Class<?> objectFactoryClass = Class.forName(objectFactoryClassName);
        return objectFactoryClass.newInstance();
    }

    private Method findFactoryMethod(Object objectFactory, Class<?> rootType, String attr) throws NoSuchMethodException, NoSuchFieldException {
        // If attr is null, the caller is asking for rootType factory method
        Method[] methods = objectFactory.getClass().getDeclaredMethods();
        if (attr == null) {
            for (Method method : methods) {
                if (method.getName().equals(String.format("create%s", rootType.getName()))) {
                    return method;
                }
            }
            throw new NoSuchMethodException("Factory method not found for class: " + rootType);
        }

        assert rootType.getField(attr).getType() == JAXBElement.class;

        for (Field field : rootType.getFields()) {
            if (field.getName().equals(attr)) {
                XmlElement annotation = field.getAnnotation(XmlElement.class);
                return objectFactory.getClass().getMethod(String.format("create%s%s", rootType.getName(), annotation.name()));
            }
        }
        
        throw new NoSuchMethodException("Factory method not found for class: " + rootType + " and attr: " + attr);
    }

    private void processAttributes(Object objectFactory, Object parentObject, JsonNode node, DeserializationContext ctxt)
            throws IllegalAccessException, InvocationTargetException, NoSuchMethodException, IOException, JAXBException, NoSuchMethodException, NoSuchFieldException {
        for (Iterator<Map.Entry<String, JsonNode>> it = node.fields(); it.hasNext(); ) {
            Map.Entry<String, JsonNode> field = it.next();
            String fieldName = field.getKey();
            JsonNode fieldValue = field.getValue();

            // Find the corresponding object factory field value generator
            Method setterMethod = findSetterMethod(parentObject.getClass(), fieldName);

            // Get the attribute value
            Object attributeValue;
            Class<?> parameterType = setterMethod.getParameterTypes()[0];
            if (parameterType == JAXBElement.class) {
                // if it is a JAXBElement-type field, call ObjectFactory to get factory method
                Method fieldFactoryMethod = findFactoryMethod(objectFactory, parentObject.getClass(), fieldName);

                // get attribute value from the object factory attribute factory
                attributeValue = fieldFactoryMethod.invoke(objectFactory, ctxt.readTreeAsValue(fieldValue, fieldFactoryMethod.getParameterTypes()[0]));
            }   else    {
                attributeValue = ctxt.readTreeAsValue(fieldValue, parameterType);
            }

            // Invoke the setter method to set the attribute value
            setterMethod.invoke(parentObject, attributeValue);
        }
    }

    private Method findSetterMethod(Class<?> parentClass, String fieldName) throws NoSuchMethodException {
        return parentClass.getMethod(String.format("set%s", fieldName));
    }
}

JAXBJson序列化器

package com.ibi.onvif.web;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import jakarta.xml.bind.JAXBElement;

import java.io.IOException;

public class JAXBJsonSerializer extends JsonSerializer<Object> {
    @Override
    public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        Object objectValue = ((JAXBElement<?>)value).getValue();
        gen.writeObject(objectValue);
    }
}

ObjectMapper 似乎从未使用我的反序列化器。它抛出的异常如下

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

如何让 ObjectMapper 工作?

如有任何帮助,我们将不胜感激!

java xml spring jackson jaxb
1个回答
0
投票

我通过为 JAXBElement 创建自定义反序列化器解决了这个问题。 JsonParser 类允许我们访问根元素属性,通过它我可以识别当前 JSON 字段正在反序列化的类字段,因此提取诸如

XmlElementRef
XmlElement
之类的注释。

请查找下面的代码以获取相同的信息。

JAXBElementDeserializer.java:

public class JAXBElementDeserializer extends StdDeserializer<JAXBElement<?>>{
    public JAXBElementDeserializer() {
        super(JAXBElement.class);
    }

    @Override
    public JAXBElement<?> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        try {
            // Get the root element's class
            Class<?> rootType = p.getParsingContext().getCurrentValue().getClass();

            // Check if the root element is part of a JAXB-generated package
            if (isJAXBGenerated(rootType)) {
                Object objectFactory = getObjectFactory(rootType);

                // Deserialize the JSON into a JsonNode
                JsonNode node = p.getCodec().readTree(p);

                // Identify the appropriate factory method by annotations or return type
                Method factoryMethod = findFactoryMethod(objectFactory, rootType, p.getCurrentName());

                //Get the parameter type
                Class<?> type = factoryMethod.getParameters()[0].getType();

                // Use the factory method to generate the object
                return  (JAXBElement<?>) factoryMethod.invoke(objectFactory, ctxt.readTreeAsValue(node, type));
            } else {
                // If not a JAXB-generated class, use the default deserialization
                throw new IOException();
            }
        } catch (Exception e) {
            throw new IOException("JAXB deserialization error", e);
        }
    }

    private boolean isJAXBGenerated(Class<?> type) {
        // Implement logic to check if the class is part of a JAXB-generated package
        // For example, you can check package names or annotations.
        // Replace the following line with your actual logic.
        return type.getPackage().getName().startsWith("org.onvif");
    }

    private Object getObjectFactory(Class<?> type) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        String objectFactoryClassName = type.getPackage().getName() + ".ObjectFactory";
        Class<?> objectFactoryClass = Class.forName(objectFactoryClassName);
        return objectFactoryClass.newInstance();
    }

    private Method findFactoryMethod(Object objectFactory, Class<?> rootType, String attr) throws NoSuchMethodException, NoSuchFieldException {
        String annotatedName;
        assert rootType.getField(attr).getType() == JAXBElement.class;

        for (Field field : rootType.getDeclaredFields()) {
            if (field.getName().equals(attr)) {
                XmlElement annotation = field.getAnnotation(XmlElement.class);
                if (annotation != null) {
                    annotatedName = annotation.name();
                }   else {
                    XmlElementRef ref_annotation = field.getAnnotation(XmlElementRef.class);
                    annotatedName = ref_annotation.name();
                }

                // Don't know the type, simple search the method by its name
                for (Method method : objectFactory.getClass().getMethods()) {
                    if (method.getName().equals(String.format("create%s%s", rootType.getSimpleName(), annotatedName))) {
                        return method;
                    }
                }
            }
        }

        throw new NoSuchMethodException("Factory method not found for class: " + rootType + " and attr: " + attr);
    }
}

JAXBElementSerializer:

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

WebMvcConfigurer.java:

public class OnvifWebMvcConfigurer implements WebMvcConfigurer {
    // Mixins
    public static interface JAXBElementMixin {
        @JsonValue
        Object getValue();
    }

    public static interface DurationMixin {
        @JsonValue
        String toString();
    }

    final private Class<?>[][] MIXIN_CLASSES = {
            {JAXBElement.class, JAXBElementMixin.class},
            {Duration.class, DurationMixin.class},
    };
    
    private final SimpleModule module = new SimpleModule();

    private void setupModule() {
        module.addSerializer(JAXBElement.class, new JAXBElementSerializer());
        module.addSerializer(Duration.class, new DurationSerializer());
        module.addDeserializer(JAXBElement.class, new JAXBElementDeserializer());
        module.addDeserializer(Duration.class, new DurationDeserializer());
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new OnvifCredentialsExtractionInterceptor());
    }

    private void configureMixins(ObjectMapper om) {
        for (Class<?>[] pair : MIXIN_CLASSES) {
            om.addMixIn(pair[0], pair[1]);
            SpringDocUtils.getConfig().replaceWithClass(pair[0], pair[1]);
        }
    }

    private void configureMessageConverter(HttpMessageConverter<?> httpMessageConverter) {
        if (!(httpMessageConverter instanceof AbstractJackson2HttpMessageConverter)) {
            return;
        }
        ObjectMapper om = ((AbstractJackson2HttpMessageConverter) httpMessageConverter).getObjectMapper();
        configureMixins(om);
        ModelConverters.getInstance().addConverter(new ModelResolver(om));
        om.registerModule(module);
    }

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        setupModule();

        for (HttpMessageConverter<?> o : converters) {
            configureMessageConverter(o);
        }

        WebMvcConfigurer.super.configureMessageConverters(converters);
    }
}

我希望这有帮助!

© www.soinside.com 2019 - 2024. All rights reserved.