【发布时间】:2014-01-13 15:05:57
【问题描述】:
试图将对象发送到端点,RESTEasy 正在丢弃一些对象,将它们替换为 Ns 空值,然后导致类转换异常。
这是一个简化的测试用例,显示了最新版本的 RESTEasy 和早期版本(例如 2.3.7)的问题:
请求类(注意是根元素):
package org.problem;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Request implements Serializable {
private static final long serialVersionUID = 4057201224391819116L;
public Request() {
super();
}
// Yes, I could use an adapter to serialize a map, but that's not germane to the problem.
public List<String> names = new ArrayList<String>();
public List<Object> values = new ArrayList<Object>();
public Map<String, Object> getArgs() {
int size = names.size();
Map<String, Object> args = new HashMap<String, Object>(size);
for (int i = 0; i < size; i++) {
args.put(names.get(i), values.get(i));
}
return args;
}
}
附加信息类(也是根元素):
package org.problem;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import java.io.Serializable;
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class AdditionalInfo implements Serializable {
private static final long serialVersionUID = 4286757723357099359L;
public AdditionalInfo() {
super();
}
public String value;
}
端点:
package org.problem;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
@Path("/problem/")
public class Endpoint {
@POST
@Path("example/v1")
@Consumes(MediaType.APPLICATION_XML)
@Produces(MediaType.APPLICATION_XML)
public String doProblem(Request request) {
Object obj = null;
try {
obj = request.getArgs().get("info");
AdditionalInfo info = (AdditionalInfo)obj;
return info.value;
} catch (ClassCastException e) {
throw new RuntimeException("Expected " + AdditionalInfo.class.getCanonicalName() + " but instead got " + obj.getClass().getCanonicalName());
}
}
}
重现问题:
package org.problem;
import org.jboss.resteasy.core.Dispatcher;
import org.jboss.resteasy.mock.MockDispatcherFactory;
import org.jboss.resteasy.mock.MockHttpRequest;
import org.jboss.resteasy.mock.MockHttpResponse;
import org.jboss.resteasy.plugins.server.resourcefactory.POJOResourceFactory;
import javax.ws.rs.core.MediaType;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.StringWriter;
public class TestProblem {
public static void main(String[] args) throws Exception {
AdditionalInfo info = new AdditionalInfo();
info.value = "Got The Info";
Request request = new Request();
request.names.add("info");
request.values.add(info);
// To see that AdditionalInfo has to be specifically added to JAXB context, try running without it:
// JAXBContext requestContext = JAXBContext.newInstance(Request.class);
JAXBContext requestContext = JAXBContext.newInstance(Request.class, AdditionalInfo.class);
StringWriter writer = new StringWriter();
Marshaller marshaller = requestContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(request, writer);
String marshalledRequestString = writer.toString();
System.out.println(marshalledRequestString);
InputStream xmlStream = new ByteArrayInputStream(marshalledRequestString.getBytes("UTF-8"));
MockHttpRequest mockRequest = MockHttpRequest.post("/problem/example/v1");
mockRequest.contentType(MediaType.APPLICATION_XML);
mockRequest.content(xmlStream);
MockHttpResponse mockResponse = new MockHttpResponse();
Dispatcher dispatcher = MockDispatcherFactory.createDispatcher();
dispatcher.getRegistry().addResourceFactory(new POJOResourceFactory(Endpoint.class));
dispatcher.invoke(mockRequest, mockResponse);
System.out.println(mockResponse.getStatus());
String response = mockResponse.getContentAsString();
System.out.println(response);
}
}
当我运行该代码时,我得到以下输出:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<request>
<names>info</names>
<values xsi:type="additionalInfo" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<value>Got The Info</value>
</values>
</request>
log4j:WARN No appenders could be found for logger (org.jboss.resteasy.plugins.providers.DocumentProvider).
log4j:WARN Please initialize the log4j system properly.
Exception in thread "main" org.jboss.resteasy.spi.UnhandledException: java.lang.RuntimeException: Expected org.problem.AdditionalInfo but instead got com.sun.org.apache.xerces.internal.dom.ElementNSImpl
at org.jboss.resteasy.core.ExceptionHandler.handleApplicationException(ExceptionHandler.java:76)
at org.jboss.resteasy.core.ExceptionHandler.handleException(ExceptionHandler.java:212)
at org.jboss.resteasy.core.SynchronousDispatcher.writeException(SynchronousDispatcher.java:149)
at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:372)
at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:179)
at org.problem.TestProblem.main(TestProblem.java:41)
Caused by: java.lang.RuntimeException: Expected org.problem.AdditionalInfo but instead got com.sun.org.apache.xerces.internal.dom.ElementNSImpl
at org.problem.Endpoint.doProblem(Endpoint.java:22)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:137)
at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTarget(ResourceMethodInvoker.java:280)
at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:234)
at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:221)
at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:356)
... 2 more
如果我从用于创建请求正文的 JAXBContext 初始化中删除 AdditionalInfo,那么它会更早地爆炸:
Exception in thread "main" javax.xml.bind.MarshalException
- with linked exception:
[com.sun.istack.SAXException2: class org.problem.AdditionalInfo nor any of its super class is known to this context.
javax.xml.bind.JAXBException: class org.problem.AdditionalInfo nor any of its super class is known to this context.]
at com.sun.xml.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:326)
at com.sun.xml.bind.v2.runtime.MarshallerImpl.marshal(MarshallerImpl.java:251)
at javax.xml.bind.helpers.AbstractMarshallerImpl.marshal(AbstractMarshallerImpl.java:116)
at org.problem.TestProblem.main(TestProblem.java:31)
Caused by: com.sun.istack.SAXException2: class org.problem.AdditionalInfo nor any of its super class is known to this context.
javax.xml.bind.JAXBException: class org.problem.AdditionalInfo nor any of its super class is known to this context.
at com.sun.xml.bind.v2.runtime.XMLSerializer.reportError(XMLSerializer.java:247)
at com.sun.xml.bind.v2.runtime.XMLSerializer.reportError(XMLSerializer.java:262)
at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:653)
at com.sun.xml.bind.v2.runtime.property.ArrayElementNodeProperty.serializeItem(ArrayElementNodeProperty.java:69)
at com.sun.xml.bind.v2.runtime.property.ArrayElementProperty.serializeListBody(ArrayElementProperty.java:172)
at com.sun.xml.bind.v2.runtime.property.ArrayERProperty.serializeBody(ArrayERProperty.java:159)
at com.sun.xml.bind.v2.runtime.ClassBeanInfoImpl.serializeBody(ClassBeanInfoImpl.java:361)
at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsSoleContent(XMLSerializer.java:593)
at com.sun.xml.bind.v2.runtime.ClassBeanInfoImpl.serializeRoot(ClassBeanInfoImpl.java:342)
at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsRoot(XMLSerializer.java:494)
at com.sun.xml.bind.v2.runtime.MarshallerImpl.write(MarshallerImpl.java:323)
... 3 more
Caused by: javax.xml.bind.JAXBException: class org.problem.AdditionalInfo nor any of its super class is known to this context.
at com.sun.xml.bind.v2.runtime.JAXBContextImpl.getBeanInfo(JAXBContextImpl.java:593)
at com.sun.xml.bind.v2.runtime.XMLSerializer.childAsXsiType(XMLSerializer.java:648)
... 11 more
看起来问题可能是 RESTEasy 没有正确初始化 JAXBContext。
文档说“基于您正在编组/解组的类,RESTEasy 将默认为每个类类型创建和缓存 JAXBContext 实例”。
这似乎是一个错误的说法。
假设它不正确,解决方法是什么?我需要编写自己的 ContextResolver 吗?有没有办法告诉 RESTEasy 它需要能够序列化哪些根元素?
即使我告诉它,名称/值映射的重点是能够序列化任何 arg(前提是它是根元素),因此必须通知 RESTEasy 每个新的 arg 类型是一个维护难题,并且破坏了名称/值对的要点。
【问题讨论】: