这是我的箱子
我已经从一些XSD和WSDL文件中生成了一些源文件。生成的源文件有用Jakarta注解注解的类和属性。我的应用程序中有3层,
1.控制器层- Spring MVC控制器。它们接受一个XJC生成的类对象作为请求体,并给予另一个作为响应体
1.客户端层-使用Jaxb 2 Marshaller创建WebServiceTemplate并将请求作为SOAP请求转发到另一个URL的层
- 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工作?
任何帮助是赞赏!
1条答案
按热度按时间j7dteeu81#
我通过为JAXBElement创建一个自定义的解析器解决了这个问题。JsonParser类允许我们访问父元素属性,通过它我可以识别当前JSON字段正在为哪个类字段进行解析,从而提取像
XmlElementRef
和XmlElement
这样的注解。请在下面找到相同的代码。
JAXBElementDeserializer.java:
字符串
JAXBElementSerializer:
型
WebMvcConfigurer.java:
型
我希望这对你有帮助!