Rester au sec avec JAXB
-
28-10-2019 - |
Question
Je développe un certain nombre de classes Java qui doivent sérialisation XML dans le format suivant:
<foo value="123"/>
<!-- or this -->
<bar value="abc"/>
<!-- or this -->
<baz value="true"/>
Au début, Foo.java
avait l'air quelque chose comme ceci:
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
class Foo {
@XmlAttribute
String value;
// snip constructors
// snip methods
// getValue
// equals, hashCode, toString
// static valueOf(String), static valueOf(int)
}
Il ne faut pas beaucoup d'imagination pour deviner ce Bar.java
et Baz.java
pourrait ressembler. Ce sont des classes d'emballage très simples pour (dans cet exemple) int
, String
et boolean
. Jusqu'à ce point, tout est Hunky Dory. Après avoir écrit le neuvième tour de
class WhoCares {
@XmlAttribute
Whatever value;
/* 2 constructors */
/* 6 methods found in every one of these classes */
}
Je pensais que: "Hé, je sais comment je peux résoudre ce - j'ai juste besoin d'un niveau d'abstraction!" Présentation Wrapper<T>
:
abstract class Wrapper<T> {
@XmlAttribute
private T value;
Wrapper() {} // default ctor for JAXB
Wrapper(T value) {
this.value = value;
}
T getValue() {
return value;
}
// snip equals, hashCode, toString
}
Maintenant, pour mettre en œuvre Foo
, tous besoin I à faire est d'étendre Wrapper
, mettre en œuvre les méthodes d'usine j'ai besoin:
class Foo extends Wrapper<String> {
// snip constructors
static Foo valueOf(String value) {
return new Foo(value);
}
}
et ajouter un @XmlSeeAlso({... Foo.class ...})
à la déclaration de Wrapper
.
- Pro. Ce qui est beaucoup plus agréable
- Con. JAXB ne sérialiser ces objets
Tous les appels à JAXBContext.getInstance(Wrapper.class)
ou JAXBContext.getInstance(Foo.class)
échouent à cause d'un NullPointerException
jeté quelque part dans les entrailles de JAXB:
17:06:03,706 ERROR [org.apache.catalina.core.ContainerBase.[jboss.web].[localhost].[/Redacted].[JAX-RS Servlet]] Servlet.service() for servlet JAX-RS Servlet threw exception: java.lang.NullPointerException
at com.sun.xml.bind.v2.runtime.reflect.TransducedAccessor.get(TransducedAccessor.java:165) [:2.2]
at com.sun.xml.bind.v2.runtime.property.AttributeProperty.<init>(AttributeProperty.java:87) [:2.2]
at com.sun.xml.bind.v2.runtime.property.PropertyFactory.create(PropertyFactory.java:104) [:2.2]
at com.sun.xml.bind.v2.runtime.ClassBeanInfoImpl.<init>(ClassBeanInfoImpl.java:179) [:2.2]
at com.sun.xml.bind.v2.runtime.JAXBContextImpl.getOrCreate(JAXBContextImpl.java:515) [:2.2]
at com.sun.xml.bind.v2.runtime.ClassBeanInfoImpl.<init>(ClassBeanInfoImpl.java:166) [:2.2]
at com.sun.xml.bind.v2.runtime.JAXBContextImpl.getOrCreate(JAXBContextImpl.java:515) [:2.2]
at com.sun.xml.bind.v2.runtime.JAXBContextImpl.getOrCreate(JAXBContextImpl.java:534) [:2.2]
at com.sun.xml.bind.v2.runtime.property.SingleElementNodeProperty.<init>(SingleElementNodeProperty.java:101) [:2.2]
at sun.reflect.GeneratedConstructorAccessor49.newInstance(Unknown Source) [:1.6.0_24]
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27) [:1.6.0_24]
at java.lang.reflect.Constructor.newInstance(Constructor.java:513) [:1.6.0_24]
at com.sun.xml.bind.v2.runtime.property.PropertyFactory.create(PropertyFactory.java:124) [:2.2]
at com.sun.xml.bind.v2.runtime.ClassBeanInfoImpl.<init>(ClassBeanInfoImpl.java:179) [:2.2]
at com.sun.xml.bind.v2.runtime.JAXBContextImpl.getOrCreate(JAXBContextImpl.java:515) [:2.2]
at com.sun.xml.bind.v2.runtime.ClassBeanInfoImpl.<init>(ClassBeanInfoImpl.java:166) [:2.2]
at com.sun.xml.bind.v2.runtime.JAXBContextImpl.getOrCreate(JAXBContextImpl.java:515) [:2.2]
at com.sun.xml.bind.v2.runtime.JAXBContextImpl.<init>(JAXBContextImpl.java:330) [:2.2]
at com.sun.xml.bind.v2.runtime.JAXBContextImpl$JAXBContextBuilder.build(JAXBContextImpl.java:1140) [:2.2]
at com.sun.xml.bind.v2.ContextFactory.createContext(ContextFactory.java:154) [:2.2]
at com.sun.xml.bind.v2.ContextFactory.createContext(ContextFactory.java:121) [:2.2]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) [:1.6.0_24]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) [:1.6.0_24]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) [:1.6.0_24]
at java.lang.reflect.Method.invoke(Method.java:597) [:1.6.0_24]
at javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:201) [:1.0.0.Final]
at javax.xml.bind.ContextFinder.find(ContextFinder.java:362) [:1.0.0.Final]
at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:618) [:1.0.0.Final]
at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:565) [:1.0.0.Final]
at com.sun.jersey.core.provider.jaxb.AbstractJAXBProvider.getStoredJAXBContext(AbstractJAXBProvider.java:189) [:1.6]
at com.sun.jersey.core.provider.jaxb.AbstractJAXBProvider.getJAXBContext(AbstractJAXBProvider.java:182) [:1.6]
at com.sun.jersey.core.provider.jaxb.AbstractJAXBProvider.getMarshaller(AbstractJAXBProvider.java:160) [:1.6]
at com.sun.jersey.core.provider.jaxb.AbstractJAXBProvider.getMarshaller(AbstractJAXBProvider.java:139) [:1.6]
at com.sun.jersey.core.provider.jaxb.AbstractRootElementProvider.writeTo(AbstractRootElementProvider.java:151) [:1.6]
at com.sun.jersey.spi.container.ContainerResponse.write(ContainerResponse.java:306) [:1.6]
at com.sun.jersey.server.impl.application.WebApplicationImpl._handleRequest(WebApplicationImpl.java:1310) [:1.6]
at com.sun.jersey.server.impl.application.WebApplicationImpl.handleRequest(WebApplicationImpl.java:1223) [:1.6]
at com.sun.jersey.server.impl.application.WebApplicationImpl.handleRequest(WebApplicationImpl.java:1213) [:1.6]
at com.sun.jersey.spi.container.servlet.WebComponent.service(WebComponent.java:414) [:1.6]
at com.sun.jersey.spi.container.servlet.ServletContainer.service(ServletContainer.java:537) [:1.6]
at com.sun.jersey.spi.container.servlet.ServletContainer.service(ServletContainer.java:699) [:1.6]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:847) [:1.0.0.Final]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:324) [:6.0.0.Final]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:242) [:6.0.0.Final]
at com.example.mgp.filter.RetryFilter.doFilter(RetryFilter.java:109) [:]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:274) [:6.0.0.Final]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:242) [:6.0.0.Final]
at com.example.mgp.filter.AuthFilter.doFilter(AuthFilter.java:57) [:]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:274) [:6.0.0.Final]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:242) [:6.0.0.Final]
at org.jboss.weld.servlet.ConversationPropagationFilter.doFilter(ConversationPropagationFilter.java:67) [:6.0.0.Final]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:274) [:6.0.0.Final]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:242) [:6.0.0.Final]
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:275) [:6.0.0.Final]
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191) [:6.0.0.Final]
at org.jboss.web.tomcat.security.SecurityAssociationValve.invoke(SecurityAssociationValve.java:181) [:6.0.0.Final]
at org.jboss.modcluster.catalina.CatalinaContext$RequestListenerValve.event(CatalinaContext.java:285) [:1.1.0.Final]
at org.jboss.modcluster.catalina.CatalinaContext$RequestListenerValve.invoke(CatalinaContext.java:261) [:1.1.0.Final]
at org.jboss.web.tomcat.security.JaccContextValve.invoke(JaccContextValve.java:88) [:6.0.0.Final]
at org.jboss.web.tomcat.security.SecurityContextEstablishmentValve.invoke(SecurityContextEstablishmentValve.java:100) [:6.0.0.Final]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127) [:6.0.0.Final]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102) [:6.0.0.Final]
at org.jboss.web.tomcat.service.jca.CachedConnectionValve.invoke(CachedConnectionValve.java:158) [:6.0.0.Final]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109) [:6.0.0.Final]
at org.jboss.web.tomcat.service.request.ActiveRequestResponseCacheValve.invoke(ActiveRequestResponseCacheValve.java:53) [:6.0.0.Final]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:362) [:6.0.0.Final]
at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:877) [:6.0.0.Final]
at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:654) [:6.0.0.Final]
at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:951) [:6.0.0.Final]
at java.lang.Thread.run(Thread.java:662) [:1.6.0_24]
Je l'ai déjà essayé de commutation ma mise en œuvre de JAXB du métro à Moxy (et je l'ai fait que la mise en œuvre a changé ) mais un NPE similaire est lancée avec Moxy:
17:46:48,481 SEVERE [com.sun.jersey.spi.container.ContainerResponse] Mapped exception to response: 500 (Internal Server Error): javax.ws.rs.WebApplicationException: javax.xml.bind.MarshalException
- with linked exception:
[Exception [EclipseLink-25003] (Eclipse Persistence Services - 2.2.0.v20110202-r8913): org.eclipse.persistence.exceptions.XMLMarshalException
Exception Description: An error occurred marshalling the object
Internal Exception: java.lang.NullPointerException]
at com.sun.jersey.core.provider.jaxb.AbstractRootElementProvider.writeTo(AbstractRootElementProvider.java:159) [:1.6]
at com.sun.jersey.spi.container.ContainerResponse.write(ContainerResponse.java:306) [:1.6]
at com.sun.jersey.server.impl.application.WebApplicationImpl._handleRequest(WebApplicationImpl.java:1310) [:1.6]
at com.sun.jersey.server.impl.application.WebApplicationImpl.handleRequest(WebApplicationImpl.java:1223) [:1.6]
at com.sun.jersey.server.impl.application.WebApplicationImpl.handleRequest(WebApplicationImpl.java:1213) [:1.6]
at com.sun.jersey.spi.container.servlet.WebComponent.service(WebComponent.java:414) [:1.6]
at com.sun.jersey.spi.container.servlet.ServletContainer.service(ServletContainer.java:537) [:1.6]
at com.sun.jersey.spi.container.servlet.ServletContainer.service(ServletContainer.java:699) [:1.6]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:847) [:1.0.0.Final]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:324) [:6.0.0.Final]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:242) [:6.0.0.Final]
at com.example.mgp.filter.RetryFilter.doFilter(RetryFilter.java:109) [:]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:274) [:6.0.0.Final]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:242) [:6.0.0.Final]
at com.example.mgp.filter.AuthFilter.doFilter(AuthFilter.java:57) [:]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:274) [:6.0.0.Final]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:242) [:6.0.0.Final]
at org.jboss.weld.servlet.ConversationPropagationFilter.doFilter(ConversationPropagationFilter.java:67) [:6.0.0.Final]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:274) [:6.0.0.Final]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:242) [:6.0.0.Final]
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:275) [:6.0.0.Final]
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191) [:6.0.0.Final]
at org.jboss.web.tomcat.security.SecurityAssociationValve.invoke(SecurityAssociationValve.java:181) [:6.0.0.Final]
at org.jboss.modcluster.catalina.CatalinaContext$RequestListenerValve.event(CatalinaContext.java:285) [:1.1.0.Final]
at org.jboss.modcluster.catalina.CatalinaContext$RequestListenerValve.invoke(CatalinaContext.java:261) [:1.1.0.Final]
at org.jboss.web.tomcat.security.JaccContextValve.invoke(JaccContextValve.java:88) [:6.0.0.Final]
at org.jboss.web.tomcat.security.SecurityContextEstablishmentValve.invoke(SecurityContextEstablishmentValve.java:100) [:6.0.0.Final]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127) [:6.0.0.Final]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102) [:6.0.0.Final]
at org.jboss.web.tomcat.service.jca.CachedConnectionValve.invoke(CachedConnectionValve.java:158) [:6.0.0.Final]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109) [:6.0.0.Final]
at org.jboss.web.tomcat.service.request.ActiveRequestResponseCacheValve.invoke(ActiveRequestResponseCacheValve.java:53) [:6.0.0.Final]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:362) [:6.0.0.Final]
at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:877) [:6.0.0.Final]
at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:654) [:6.0.0.Final]
at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:951) [:6.0.0.Final]
at java.lang.Thread.run(Thread.java:662) [:1.6.0_24]
Caused by: javax.xml.bind.MarshalException
- with linked exception:
[Exception [EclipseLink-25003] (Eclipse Persistence Services - 2.2.0.v20110202-r8913): org.eclipse.persistence.exceptions.XMLMarshalException
Exception Description: An error occurred marshalling the object
Internal Exception: java.lang.NullPointerException]
at org.eclipse.persistence.jaxb.JAXBMarshaller.marshal(JAXBMarshaller.java:314) [:2.2.0.v20110202-r8913]
at com.sun.jersey.core.provider.jaxb.AbstractRootElementProvider.writeTo(AbstractRootElementProvider.java:179) [:1.6]
at com.sun.jersey.core.provider.jaxb.AbstractRootElementProvider.writeTo(AbstractRootElementProvider.java:157) [:1.6]
... 36 more
Caused by: Exception [EclipseLink-25003] (Eclipse Persistence Services - 2.2.0.v20110202-r8913): org.eclipse.persistence.exceptions.XMLMarshalException
Exception Description: An error occurred marshalling the object
Internal Exception: java.lang.NullPointerException
at org.eclipse.persistence.exceptions.XMLMarshalException.marshalException(XMLMarshalException.java:78) [:2.2.0.v20110202-r8913]
at org.eclipse.persistence.oxm.XMLMarshaller.marshal(XMLMarshaller.java:516) [:2.2.0.v20110202-r8913]
at org.eclipse.persistence.jaxb.JAXBMarshaller.marshal(JAXBMarshaller.java:312) [:2.2.0.v20110202-r8913]
... 38 more
Caused by: java.lang.NullPointerException
at org.eclipse.persistence.oxm.record.OutputStreamRecord.outputStreamWrite(OutputStreamRecord.java:511) [:2.2.0.v20110202-r8913]
at org.eclipse.persistence.oxm.record.OutputStreamRecord.openStartElement(OutputStreamRecord.java:159) [:2.2.0.v20110202-r8913]
at org.eclipse.persistence.internal.oxm.XPathNode.startElement(XPathNode.java:330) [:2.2.0.v20110202-r8913]
at org.eclipse.persistence.internal.oxm.XMLCompositeObjectMappingNodeValue.marshalSingleValue(XMLCompositeObjectMappingNodeValue.java:185) [:2.2.0.v20110202-r8913]
at org.eclipse.persistence.internal.oxm.XMLCompositeObjectMappingNodeValue.marshal(XMLCompositeObjectMappingNodeValue.java:116) [:2.2.0.v20110202-r8913]
at org.eclipse.persistence.internal.oxm.NodeValue.marshal(NodeValue.java:104) [:2.2.0.v20110202-r8913]
at org.eclipse.persistence.internal.oxm.record.ObjectMarshalContext.marshal(ObjectMarshalContext.java:60) [:2.2.0.v20110202-r8913]
at org.eclipse.persistence.internal.oxm.XPathNode.marshal(XPathNode.java:322) [:2.2.0.v20110202-r8913]
at org.eclipse.persistence.internal.oxm.TreeObjectBuilder.marshalAttributes(TreeObjectBuilder.java:338) [:2.2.0.v20110202-r8913]
at org.eclipse.persistence.internal.oxm.XPathNode.startElement(XPathNode.java:344) [:2.2.0.v20110202-r8913]
at org.eclipse.persistence.internal.oxm.XMLCompositeObjectMappingNodeValue.marshalSingleValue(XMLCompositeObjectMappingNodeValue.java:167) [:2.2.0.v20110202-r8913]
at org.eclipse.persistence.internal.oxm.XMLCompositeObjectMappingNodeValue.marshal(XMLCompositeObjectMappingNodeValue.java:116) [:2.2.0.v20110202-r8913]
at org.eclipse.persistence.internal.oxm.NodeValue.marshal(NodeValue.java:104) [:2.2.0.v20110202-r8913]
at org.eclipse.persistence.internal.oxm.record.ObjectMarshalContext.marshal(ObjectMarshalContext.java:60) [:2.2.0.v20110202-r8913]
at org.eclipse.persistence.internal.oxm.XPathNode.marshal(XPathNode.java:322) [:2.2.0.v20110202-r8913]
at org.eclipse.persistence.internal.oxm.TreeObjectBuilder.buildRow(TreeObjectBuilder.java:325) [:2.2.0.v20110202-r8913]
at org.eclipse.persistence.internal.oxm.XMLAnyObjectMappingNodeValue.marshalSingleValue(XMLAnyObjectMappingNodeValue.java:154) [:2.2.0.v20110202-r8913]
at org.eclipse.persistence.internal.oxm.XMLAnyObjectMappingNodeValue.marshal(XMLAnyObjectMappingNodeValue.java:70) [:2.2.0.v20110202-r8913]
at org.eclipse.persistence.internal.oxm.NodeValue.marshal(NodeValue.java:104) [:2.2.0.v20110202-r8913]
at org.eclipse.persistence.internal.oxm.record.ObjectMarshalContext.marshal(ObjectMarshalContext.java:60) [:2.2.0.v20110202-r8913]
at org.eclipse.persistence.internal.oxm.XPathNode.marshal(XPathNode.java:322) [:2.2.0.v20110202-r8913]
at org.eclipse.persistence.internal.oxm.TreeObjectBuilder.buildRow(TreeObjectBuilder.java:325) [:2.2.0.v20110202-r8913]
at org.eclipse.persistence.oxm.XMLMarshaller.marshal(XMLMarshaller.java:932) [:2.2.0.v20110202-r8913]
at org.eclipse.persistence.oxm.XMLMarshaller.marshal(XMLMarshaller.java:486) [:2.2.0.v20110202-r8913]
... 39 more
Est-il possible de produire le XML nécessaire tout en minimisant boilerplate et rester sec ?
La solution
La question que vous voyez est dû à des bugs dans des arrêts de métro (implémentation de référence) et EclipseLink Moxy implémentations de JAXB. Le bogue Moxy pertinent est:
Une chose à noter au sujet de votre cas d'utilisation est qu'en raison de l'effacement de type, une implémentation JAXB va traiter la propriété de valeur en tant que type objet. Cela signifie que les opérations de maréchal va travailler pour vous, mais une opération unmarshal retourne la valeur en tant que chaîne. Voilà pourquoi si le champ de valeur a été annotées avec @XmlElement que xsi: informations de type seraient inclus pour préserver les informations de type:
Moxy Solution
Vous contourner le bug Moxy en tirant parti de la XMLAdapter suivante:
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class ObjectAdapter extends XmlAdapter<Object, Object> {
@Override
public Object unmarshal(Object v) throws Exception {
return v;
}
@Override
public Object marshal(Object v) throws Exception {
return v;
}
}
L'adaptateur XML est enregistré sur votre propriété comme suit:
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlSeeAlso;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
@XmlTransient
@XmlAccessorType(XmlAccessType.FIELD)
@XmlSeeAlso(Foo.class)
abstract class Wrapper<T> {
@XmlAttribute
@XmlJavaTypeAdapter(ObjectAdapter.class)
private T value;
Wrapper() {} // default ctor for JAXB
public Wrapper(T t) {
value = t;
}
}