这是我的案例,
我已经从一些 XSD 和 WSDL 文件生成了一些源文件。生成的源具有使用 Jakarta 注释进行注释的类和属性。我的应用程序中有 3 层,
我面临的问题是解析 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 工作?
如有任何帮助,我们将不胜感激!
我通过为 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);
}
}
我希望这有帮助!