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

xfb7svmp  于 5个月前  发布在  Spring
关注(0)|答案(1)|浏览(37)

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

  1. XML元素类层-使用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期望JSON主体反映JAXElement的定义,而不是实际属性的类型。我不希望用户发送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模式,而不是实际的序列化。为此,我实现了一个自定义序列化器和一个序列化器,正如你在上面的代码中看到的。
JAXBJ声发射器

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));
    }
}


JAXBJsonSerializer

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工作?
任何帮助是赞赏!

j7dteeu8

j7dteeu81#

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


我希望这对你有帮助!

相关问题