CXF:没有为类找到消息正文编写器 - 自动映射非简单资源

时间:2011-06-10 21:01:20

标签: java web-services cxf

我正在使用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。我只能希望这会得到照顾。

10 个答案:

答案 0 :(得分:42)

它不是开箱即用,但CXF确实支持JSON绑定到休息服务。请参阅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;
}

注意:

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

    Edit2:基于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 - 示例服务器端配置 正如我之前所说,我的服务器端是弹簧配置的。以下是一个用于在提供程序中连接的示例配置:

    <?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 - 非弹簧配置 使用您选择的servlet容器,配置

    org.apache.cxf.jaxrs.servlet.CXFNonSpringJaxrsServlet
    

    至少有2个init参数:

    jaxrs.serviceClasses
    jaxrs.providers
    

    其中serviceClasses是您想要绑定的服务实现的空格分隔列表,例如上面提到的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>
    

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

    无论你的情况如何,都应该让你去。

答案 1 :(得分:6)

从CXF 2.7.0升级到3.0.2时遇到此问题。以下是我为解决这个问题所做的工作:

在我的pom.xml中包含以下内容

    <dependency>
        <groupId>org.apache.cxf</groupId>
        <artifactId>cxf-rt-rs-extension-providers</artifactId>
        <version>3.0.2</version>
    </dependency>

    <dependency>
        <groupId>org.codehaus.jackson</groupId>
        <artifactId>jackson-jaxrs</artifactId>
        <version>1.9.0</version>
    </dependency>

并添加了以下提供商

    <jaxrs:providers>
        <bean class="org.codehaus.jackson.jaxrs.JacksonJaxbJsonProvider" />
    </jaxrs:providers>

答案 2 :(得分:3)

如果您使用的是jaxrs:客户端配置路径,您可以选择使用JacksonJsonProvider来提供

<jaxrs:client id="serviceId"
    serviceClass="classname"
    address="">
    <jaxrs:providers>
        <bean class="org.codehaus.jackson.jaxrs.JacksonJsonProvider">
            <property name="mapper" ref="jacksonMapper" />
        </bean>
    </jaxrs:providers>
</jaxrs:client>

<bean id="jacksonMapper" class="org.codehaus.jackson.map.ObjectMapper">
</bean>

您需要在类路径中包含jackson-mapper-asl和jackson-jaxr工件

答案 3 :(得分:2)

以编程方式创建服务器时,可以通过设置提供程序为json / xml添加邮件正文编写器。

JAXRSServerFactoryBean bean = new JAXRSServerFactoryBean();
bean.setAddress("http://localhost:9000/");

List<Object> providers = new ArrayList<Object>();
providers.add(new JacksonJaxbJsonProvider());
providers.add(new JacksonJaxbXMLProvider());
bean.setProviders(providers);

List<Class< ? >> resourceClasses = new ArrayList<Class< ? >>();
resourceClasses.add(YourRestServiceImpl.class);
bean.setResourceClasses(resourceClasses);

bean.setResourceProvider(YourRestServiceImpl.class, new SingletonResourceProvider(new YourRestServiceImpl()));

BindingFactoryManager manager = bean.getBus().getExtension(BindingFactoryManager.class);
JAXRSBindingFactory restFactory = new JAXRSBindingFactory();
restFactory.setBus(bean.getBus());
manager.registerBindingFactory(JAXRSBindingFactory.JAXRS_BINDING_ID, restFactory);

bean.create();

答案 4 :(得分:1)

您还可以配置CXFNonSpringJAXRSServlet(假设使用JSONProvider):

<init-param>
  <param-name>jaxrs.providers</param-name>
  <param-value>
      org.apache.cxf.jaxrs.provider.JSONProvider
      (writeXsiType=false)
  </param-value> 
</init-param>

答案 5 :(得分:1)

步骤1:将Bean类添加到dataFormat列表中:

<dataFormats>
    <json id="jack" library="Jackson" prettyPrint="true"
          unmarshalTypeName="{ur bean class path}" /> 
</dataFormats>

第2步:在客户端调用之前将bean封送:

<marchal id="marsh" ref="jack"/>

答案 6 :(得分:0)

您可以尝试提及&#34;接受:application / json&#34;在你的rest客户端头文件中,如果你期望你的对象作为JSON响应。

答案 7 :(得分:0)

如果您使用的是“ cxf-rt-rs-client”版本3.03。或更高版本,请确保xml名称空间和schemaLocation声明如下

<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:jaxrs-client="http://cxf.apache.org/jaxrs-client"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
    http://cxf.apache.org/jaxrs http://cxf.apache.org/schemas/jaxrs.xsd http://cxf.apache.org/jaxrs-client http://cxf.apache.org/schemas/jaxrs-client.xsd">

并确保客户端具有JacksonJsonProvider或您的自定义JsonProvider

<jaxrs-client:client id="serviceClient" address="${cxf.endpoint.service.address}" serviceClass="serviceClass">
        <jaxrs-client:headers>
            <entry key="Accept" value="application/json"></entry>
        </jaxrs-client:headers>
        <jaxrs-client:providers>
            <bean class="org.codehaus.jackson.jaxrs.JacksonJsonProvider">
            <property name="mapper" ref="jacksonMapper" />
        </bean>
        </jaxrs-client:providers>
</jaxrs-client:client>

答案 8 :(得分:0)

在我的场景中,当没有端口号的剩余URL未正确配置以进行负载平衡时,我遇到了类似的错误。我用端口号验证了其余URL,并且没有发生此问题。因此我们必须更新负载平衡配置以解决此问题。

答案 9 :(得分:0)

以上所有更改均不适用于我。 请在下面查看我的工作配置:

依赖项:

            <dependency>
                <groupId>org.apache.cxf</groupId>
                <artifactId>cxf-rt-frontend-jaxrs</artifactId>
                <version>3.3.2</version>
            </dependency>
            <dependency>
                <groupId>org.apache.cxf</groupId>
                <artifactId>cxf-rt-rs-extension-providers</artifactId>
                <version>3.3.2</version>
            </dependency>
            <dependency>
                <groupId>org.codehaus.jackson</groupId>
                <artifactId>jackson-jaxrs</artifactId>
                <version>1.9.13</version>
            </dependency>
            <dependency>
                <groupId>org.codehaus.jettison</groupId>
                <artifactId>jettison</artifactId>
                <version>1.4.0</version>
            </dependency>

web.xml:

     <servlet>
        <servlet-name>cfxServlet</servlet-name>
        <servlet-class>org.apache.cxf.jaxrs.servlet.CXFNonSpringJaxrsServlet</servlet-class>
        <init-param>
            <param-name>javax.ws.rs.Application</param-name>
            <param-value>com.MyApplication</param-value>
        </init-param>
        <init-param>
            <param-name>jaxrs.providers</param-name>
            <param-value>org.codehaus.jackson.jaxrs.JacksonJsonProvider</param-value>
        </init-param>
        <init-param>
            <param-name>jaxrs.extensions</param-name>
            <param-value> 
            json=application/json 
        </param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>cfxServlet</servlet-name>
        <url-pattern>/v1/*</url-pattern>
    </servlet-mapping>

享受编码..:)