CXF:找不到类的消息正文编写器 - 自动映射非简单资源 [英] CXF: No message body writer found for class - automatically mapping non-simple resources

查看:30
本文介绍了CXF:找不到类的消息正文编写器 - 自动映射非简单资源的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用 CXF 休息客户端,它适用于简单的数据类型(例如:字符串、整数).但是,当我尝试使用自定义对象时,我得到了这个:

I am using the CXF rest client which works well for simple data types (eg: Strings, ints). However, when I attempt to use custom Objects I get this:

Exception in thread "main" org.apache.cxf.interceptor.Fault: .No message body writer found for class : class com.company.datatype.normal.MyObject.
    at org.apache.cxf.jaxrs.client.ClientProxyImpl$BodyWriter.handleMessage(ClientProxyImpl.java:523)
    at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:263)
    at org.apache.cxf.jaxrs.client.ClientProxyImpl.doChainedInvocation(ClientProxyImpl.java:438)
    at org.apache.cxf.jaxrs.client.ClientProxyImpl.invoke(ClientProxyImpl.java:177)
    at $Proxy13.execute(Unknown Source)
    at com.company.JaxTestClient.main(JaxTestClient.java:26)
Caused by: org.apache.cxf.jaxrs.client.ClientWebApplicationException: .No message body writer found for class : class com.company.datatype.normal.MyObject.
    at org.apache.cxf.jaxrs.client.AbstractClient.reportMessageHandlerProblem(AbstractClient.java:491)
    at org.apache.cxf.jaxrs.client.AbstractClient.writeBody(AbstractClient.java:401)
    at org.apache.cxf.jaxrs.client.ClientProxyImpl$BodyWriter.handleMessage(ClientProxyImpl.java:515)
    ... 5 more

我是这样称呼它的:

JaxExample jaxExample = JAXRSClientFactory.create( "http://localhost:8111/", JaxExample.class );
MyObject before = ...
MyObject after = jaxExample.execute( before );

这是接口中的方法:

@POST
@Path( "execute" )
@Produces( "application/json" )
MyObject execute( MyObject myObject );

restlet 库执行此操作非常简单,通过将 XStream 依赖项添加到您的路径,它正常工作".CXF 有没有类似的东西?

The restlet library does this quite simply, by adding the XStream dependency to your path it "just works". Does CXF something similar?

编辑 #1:

我已将此作为 CXF 问题管理系统的功能改进发布此处.我只能希望这会得到处理.

I've posted this as a feature improvement to the CXF issue management system here. I can only hope this will get attended to.

推荐答案

它不是开箱即用的,但 CXF 确实支持 JSON 绑定到其余服务.请参阅 此处的 cxf jax-rs json 文档.仍然需要做一些最低限度的配置以使提供程序可用,如果您想对 JSON 的形成方式有更多的控制,则需要熟悉 jettison.

It isn't quite out of the box but CXF does support JSON bindings to rest services. See cxf jax-rs json docs here. You'll still need to do some minimal configuration to have the provider available and you need to be familiar with jettison if you want to have more control over how the JSON is formed.

根据评论请求,这里有一些代码.我对此没有太多经验,但以下代码在快速测试系统中用作示例.

Per comment request, here is some code. I don't have a lot of experience with this but the following code worked as an example in a quick test system.

//TestApi parts
@GET
@Path ( "test" )
@Produces ( "application/json" )
public Demo getDemo () {
    Demo d = new Demo ();
    d.id = 1;
    d.name = "test";
    return d;
}

//client config for a TestApi interface
List providers = new ArrayList ();
JSONProvider jsonProvider = new JSONProvider ();
Map<String, String> map = new HashMap<String, String> ();
map.put ( "http://www.myserviceapi.com", "myapi" );
jsonProvider.setNamespaceMap ( map );
providers.add ( jsonProvider );
TestApi proxy = JAXRSClientFactory.create ( url, TestApi.class, 
    providers, true );

Demo d = proxy.getDemo ();
if ( d != null ) {
    System.out.println ( d.id + ":" + d.name );
}

//the Demo class
@XmlRootElement ( name = "demo", namespace = "http://www.myserviceapi.com" )
@XmlType ( name = "demo", namespace = "http://www.myserviceapi.com", 
    propOrder = { "name", "id" } )
@XmlAccessorType ( XmlAccessType.FIELD )
public class Demo {

    public String name;
    public int id;
}

注意事项:

  1. 提供者列表是您在客户端上对 JSON 提供者进行编码配置的地方.特别是,您会看到命名空间映射.这需要与您的服务器端配置相匹配.我不太了解 Jettison 选项,因此在操纵所有用于控制编组过程的各种旋钮方面我没有太多帮助.
  2. CXF 中的 Jettison 通过将来自 JAXB 提供程序的 XML 编组为 JSON 来工作.因此,您必须确保有效负载对象都已标记(或以其他方式配置)以编组为 application/xml,然后才能将它们编组为 JSON.如果您知道解决此问题的方法(除了编写您自己的消息正文作者),我很想听听.
  3. 我在服务器上使用 spring,所以我的配置都是 xml 的东西.本质上,您需要通过相同的过程将 JSONProvider 添加到具有相同命名空间配置的服务.没有方便的代码,但我想它会很好地反映客户端.

这个例子有点脏,但希望能让你继续前进.

This is a bit dirty as an example but will hopefully get you going.

Edit2:基于 xstream 的消息正文编写器示例,可避免 jaxb.

An example of a message body writer that is based on xstream to avoid jaxb.

@Produces ( "application/json" )
@Consumes ( "application/json" )
@Provider
public class XstreamJsonProvider implements MessageBodyReader<Object>,
    MessageBodyWriter<Object> {

@Override
public boolean isWriteable ( Class<?> type, Type genericType, 
    Annotation[] annotations, MediaType mediaType ) {
    return MediaType.APPLICATION_JSON_TYPE.equals ( mediaType ) 
        && type.equals ( Demo.class );
}

@Override
public long getSize ( Object t, Class<?> type, Type genericType, 
    Annotation[] annotations, MediaType mediaType ) {
    // I'm being lazy - should compute the actual size
    return -1;
}

@Override
public void writeTo ( Object t, Class<?> type, Type genericType, 
    Annotation[] annotations, MediaType mediaType, 
    MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream ) 
    throws IOException, WebApplicationException {
    // deal with thread safe use of xstream, etc.
    XStream xstream = new XStream ( new JettisonMappedXmlDriver () );
    xstream.setMode ( XStream.NO_REFERENCES );
    // add safer encoding, error handling, etc.
    xstream.toXML ( t, entityStream );
}

@Override
public boolean isReadable ( Class<?> type, Type genericType, 
    Annotation[] annotations, MediaType mediaType ) {
    return MediaType.APPLICATION_JSON_TYPE.equals ( mediaType ) 
        && type.equals ( Demo.class );
}

@Override
public Object readFrom ( Class<Object> type, Type genericType, 
    Annotation[] annotations, MediaType mediaType, 
    MultivaluedMap<String, String> httpHeaders, InputStream entityStream ) 
    throws IOException, WebApplicationException {
    // add error handling, etc.
    XStream xstream = new XStream ( new JettisonMappedXmlDriver () );
    return xstream.fromXML ( entityStream );
}
}

//now your client just needs this
List providers = new ArrayList ();
XstreamJsonProvider jsonProvider = new XstreamJsonProvider ();
providers.add ( jsonProvider );
TestApi proxy = JAXRSClientFactory.create ( url, TestApi.class, 
    providers, true );

Demo d = proxy.getDemo ();
if ( d != null ) {
    System.out.println ( d.id + ":" + d.name );
}

示例代码缺少强大的媒体类型支持、错误处理、线程安全等部分.但是,它应该可以让您用最少的代码解决 jaxb 问题.

The sample code is missing the parts for robust media type support, error handling, thread safety, etc. But, it ought to get you around the jaxb issue with minimal code.

编辑 3 - 示例服务器端配置正如我之前所说,我的服务器端是 spring 配置的.以下是用于连接提供程序的示例配置:

EDIT 3 - sample server side configuration As I said before, my server side is spring configured. Here is a sample configuration that works to wire in the provider:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jaxrs="http://cxf.apache.org/jaxrs"
xmlns:cxf="http://cxf.apache.org/core"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://cxf.apache.org/jaxrs http://cxf.apache.org/schemas/jaxrs.xsd
    http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd">

<import resource="classpath:META-INF/cxf/cxf.xml" />

<jaxrs:server id="TestApi">
    <jaxrs:serviceBeans>
        <ref bean="testApi" />
    </jaxrs:serviceBeans>
    <jaxrs:providers>
        <bean id="xstreamJsonProvider" class="webtests.rest.XstreamJsonProvider" />
    </jaxrs:providers>
</jaxrs:server>

<bean id="testApi" class="webtests.rest.TestApi">
</bean>

</beans>

我还注意到,在我使用的最新版本的 cxf 中,媒体类型有所不同,因此上面关于 xstream 消息正文读取器/写入器的示例需要快速修改,其中 isWritable/isReadable 更改为:

I have also noted that in the latest rev of cxf that I'm using there is a difference in the media types, so the example above on the xstream message body reader/writer needs a quick modification where isWritable/isReadable change to:

return MediaType.APPLICATION_JSON_TYPE.getType ().equals ( mediaType.getType () )
    && MediaType.APPLICATION_JSON_TYPE.getSubtype ().equals ( mediaType.getSubtype () )
    && type.equals ( Demo.class );

编辑 4 - 非弹簧配置使用您选择的 servlet 容器,配置

EDIT 4 - non-spring configuration Using your servlet container of choice, configure

org.apache.cxf.jaxrs.servlet.CXFNonSpringJaxrsServlet

至少有 2 个初始化参数:

with at least 2 init params of:

jaxrs.serviceClasses
jaxrs.providers

其中serviceClasses是你想要绑定的服务实现的空格分隔列表,比如上面提到的TestApi,providers是一个空格分隔的消息体提供者列表,比如上面提到的XstreamJsonProvider.在 tomcat 中,您可以将以下内容添加到 web.xml:

where the serviceClasses is a space separated list of the service implementations you want bound, such as the TestApi mentioned above and the providers is a space separated list of message body providers, such as the XstreamJsonProvider mentioned above. In tomcat you might add the following to web.xml:

<servlet>
    <servlet-name>cxfservlet</servlet-name>
    <servlet-class>org.apache.cxf.jaxrs.servlet.CXFNonSpringJaxrsServlet</servlet-class>
    <init-param>
        <param-name>jaxrs.serviceClasses</param-name>
        <param-value>webtests.rest.TestApi</param-value>
    </init-param>
    <init-param>
        <param-name>jaxrs.providers</param-name>
        <param-value>webtests.rest.XstreamJsonProvider</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

这几乎是在没有弹簧的情况下运行它的最快方法.如果您不使用 servlet 容器,则需要使用 XstreamJsonProvider 的实例配置 JAXRSServerFactoryBean.setProviders,并通过 JAXRSServerFactoryBean.setResourceProvider 方法设置服务实现.检查 CXFNonSpringJaxrsServlet.init 方法以了解它们在 servlet 容器中设置时是如何执行的.

That is pretty much the quickest way to run it without spring. If you are not using a servlet container, you would need to configure the JAXRSServerFactoryBean.setProviders with an instance of XstreamJsonProvider and set the service implementation via the JAXRSServerFactoryBean.setResourceProvider method. Check the CXFNonSpringJaxrsServlet.init method to see how they do it when setup in a servlet container.

无论您的情况如何,这都应该让您继续前进.

That ought to get you going no matter your scenario.

这篇关于CXF:找不到类的消息正文编写器 - 自动映射非简单资源的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆