CXF: No message body writer found for class - automatically mapping non-simple resources

前端 未结 10 620
野的像风
野的像风 2020-12-08 03:59

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:

Exceptio         


        
相关标签:
10条回答
  • 2020-12-08 04:53

    Step 1: Add the bean class into the dataFormat list:

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

    Step 2: Marshal the bean prior to the client call:

    <marchal id="marsh" ref="jack"/>
    
    0 讨论(0)
  • 2020-12-08 04:54

    If you are using "cxf-rt-rs-client" version 3.03. or above make sure the xml name space and schemaLocation are declared as below

    <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">
    

    And make sure the client have JacksonJsonProvider or your custom 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>
    
    0 讨论(0)
  • 2020-12-08 04:58

    I encountered this problem while upgrading from CXF 2.7.0 to 3.0.2. Here is what I did to resolve it:

    Included the following in my 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>
    

    and added the following provider

        <jaxrs:providers>
            <bean class="org.codehaus.jackson.jaxrs.JacksonJaxbJsonProvider" />
        </jaxrs:providers>
    
    0 讨论(0)
  • 2020-12-08 05:00

    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.

    EDIT: 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;
    }
    

    Notes:

    1. The providers list is where you code configure the JSON provider on the client. In particular, you see the namespace mapping. This needs to match what is on your server side configuration. I don't know much about Jettison options so I'm not much help on manipulating all of the various knobs for controlling the marshalling process.
    2. Jettison in CXF works by marshalling XML from a JAXB provider into JSON. So you have to ensure that the payload objects are all marked up (or otherwise configured) to marshall as application/xml before you can have them marshall as JSON. If you know of a way around this (other than writing your own message body writer), I'd love to hear about it.
    3. I use spring on the server so my configuration there is all xml stuff. Essentially, you need to go through the same process to add the JSONProvider to the service with the same namespace configuration. Don't have code for that handy but I imagine it will mirror the client side fairly well.

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

    Edit2: 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 );
    }
    

    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.

    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>
    

    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 );
    

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

    org.apache.cxf.jaxrs.servlet.CXFNonSpringJaxrsServlet
    

    with at least 2 init params of:

    jaxrs.serviceClasses
    jaxrs.providers
    

    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>
    

    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.

    0 讨论(0)
提交回复
热议问题