我正在使用CXF rest客户端,该客户端适用于简单的数据类型(例如:字符串,整数)。但是,当我尝试使用自定义对象时,得到以下信息:

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是否类似?

编辑#1:

我将其发布为CXF问题管理系统here的功能改进。我只能希望这会得到关注。

最佳答案

它不是开箱即用的,但是CXF确实支持JSON绑定(bind)到其他服务。请参阅cxf jax-rs json docs here.。您仍然需要做一些最小的配置来使提供程序可用,并且如果您想对JSON的形成方式有更多的控制,就需要熟悉Jettison。

编辑:每个注释请求,下面是一些代码。我没有很多经验,但是以下代码在快速测试系统中作为示例。

//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;
}

笔记:
  • 提供程序列表是您在客户端代码上配置JSON提供程序的位置。特别是,您会看到 namespace 映射。这需要与您的服务器端配置相匹配。我对Jettison选项了解不多,因此在操纵所有用于控制编码过程的旋钮方面并没有太大帮助。
  • CXF中的
  • Jettison通过将XML从JAXB提供程序编码为JSON来工作。因此,您必须确保将所有有效负载对象都标记(或配置为)以编码为application/xml,然后才能将它们编码为JSON。如果您知道解决此问题的方法(除了编写自己的消息正文编写器之外),我很想听听。
  • 我在服务器上使用spring,因此我的配置中包含所有xml内容。本质上,您需要执行相同的过程以将JSONProvider添加到具有相同 namespace 配置的服务中。没有方便的代码,但我想它会很好地反射(reflect)客户端。

  • 作为示例,这有点脏,但是希望可以帮助您。

    编辑2:一个基于xstream以避免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问题。

    编辑3-示例服务器端配置
    如前所述,我的服务器端是spring配置的。这是一个示例配置,可用于连接提供程序:
    <?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进行快速修改,以将其更改为:
    return MediaType.APPLICATION_JSON_TYPE.getType ().equals ( mediaType.getType () )
        && MediaType.APPLICATION_JSON_TYPE.getSubtype ().equals ( mediaType.getSubtype () )
        && type.equals ( Demo.class );
    

    编辑4-非spring配置
    使用您选择的servlet容器,进行配置
    org.apache.cxf.jaxrs.servlet.CXFNonSpringJaxrsServlet
    

    至少有2个初始化参数:
    jaxrs.serviceClasses
    jaxrs.providers
    

    其中,serviceClasses是要绑定(bind)的服务实现的空格分隔列表,例如上面提到的TestApi,provider是消息主体提供程序的空格分隔列表,例如上面提到的XstreamJsonProvider。在tomcat中,您可以将以下内容添加到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>
    

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

    无论您遇到什么情况,这都应该使您能够前进。

    关于java - CXF:找不到类的消息正文编写器-自动映射非简单资源,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/6312030/

    10-12 01:49