CXF: No se encontró ningún escritor de cuerpo de mensaje para la asignación automática de recursos no simples de clase


Estoy usando el cliente rest CXF que funciona bien para tipos de datos simples (por ejemplo: Cadenas, ints). Sin embargo, cuando intento usar Objetos personalizados obtengo esto:

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

Lo llamo así:

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

Aquí está el método en la interfaz:

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

La biblioteca restlet hace esto simplemente, agregando la dependencia XStream a su ruta de acceso, "solo funciona". ¿CXF es algo similar?

EDITAR #1:

He publicado esto como una mejora de características para el sistema de gestión de problemas CXF aquí . Sólo puedo esperar que esto se solucione.

Author: javamonkey79, 2011-06-11

7 answers

No está completamente fuera de la caja, pero CXF admite enlaces JSON a servicios rest. Ver cxf jax-rs json docs aquí. Todavía tendrá que hacer una configuración mínima para tener el proveedor disponible y necesita estar familiarizado con jettison si desea tener más control sobre cómo se forma el JSON.

EDITAR: Por solicitud de comentario, aquí hay un código. No tengo mucha experiencia con esto, pero el siguiente código funcionó como ejemplo en una prueba rápida sistema.

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

Notas:

  1. La lista de proveedores es donde se configura el proveedor JSON en el cliente. En particular, verá la asignación de espacios de nombres. Esto debe coincidir con lo que está en la configuración del lado del servidor. No se mucho acerca de las opciones de eliminación, así que no soy de mucha ayuda en la manipulación de todos los varios botones para controlar el proceso de clasificación.
  2. Jettison en CXF funciona clasificando XML desde un proveedor JAXB en JSON. Así que tienes que asegurarte que los objetos payload estén marcados (o configurados de otra manera) a marshall como application/xml antes de que pueda tenerlos marshall como JSON. Si usted sabe de una manera alrededor de esto (aparte de escribir su propio escritor del cuerpo del mensaje), me encantaría oír sobre él.
  3. Uso spring en el servidor para que mi configuración sea todo xml. Esencialmente, debe pasar por el mismo proceso para agregar el JSONProvider al servicio con la misma configuración de espacio de nombres. No tengo código para eso. práctico, pero me imagino que reflejará el lado del cliente bastante bien.

Esto es un poco sucio como ejemplo, pero con suerte te pondrá en marcha.

Edit2: Un ejemplo de un escritor de cuerpo de mensaje que se basa en xstream para evitar 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 );
}

Al código de ejemplo le faltan las partes para soporte de tipo de medio robusto, manejo de errores, seguridad de subprocesos, etc. Pero, debe conseguir que alrededor de la cuestión jaxb con un código mínimo.

EDITAR 3-ejemplo del lado del servidor configuración Como dije antes, mi lado del servidor está configurado por Spring. Aquí hay una configuración de ejemplo que funciona para cablear en el proveedor:

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

También he notado que en el último rev de cxf que estoy usando hay una diferencia en los tipos de medios, por lo que el ejemplo anterior en el lector/escritor de cuerpo de mensaje xstream necesita una modificación rápida donde se puede escribir/se puede escribir a:

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

EDITAR 4-configuración sin resorte Usando su contenedor servlet de choice, configure

org.apache.cxf.jaxrs.servlet.CXFNonSpringJaxrsServlet

Con al menos 2 parámetros de inicio de:

jaxrs.serviceClasses
jaxrs.providers

Donde serviceClasses es una lista separada por espacios de las implementaciones de servicios que desea vincular, como la TestApi mencionada anteriormente y los proveedores es una lista separada por espacios de los proveedores de cuerpo de mensajes, como el XstreamJsonProvider mencionado anteriormente. En tomcat puede agregar lo siguiente a 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>

Esa es prácticamente la forma más rápida de ejecutarlo sin resorte. Si no está usando un servlet container, tendría que configurar el JAXRSServerFactoryBean.Setprovider con una instancia de XstreamJsonProvider y establece la implementación del servicio a través del JAXRSServerFactoryBean.setResourceProvider método. Compruebe el CXFNonSpringJaxrsServlet.método init para ver cómo lo hacen cuando se configura en un contenedor servlet.

Eso debería ponerte en marcha sin importar tu escenario.

 41
Author: philwb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-01-28 01:31:37

Encontré este problema al actualizar de CXF 2.7.0 a 3.0.2. Esto es lo que hice para resolverlo:

Incluido lo siguiente en mi 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>

Y se agregó el siguiente proveedor

    <jaxrs:providers>
        <bean class="org.codehaus.jackson.jaxrs.JacksonJaxbJsonProvider" />
    </jaxrs:providers>
 5
Author: Ross H Mills III,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-10-31 17:00:46

Si está utilizando jaxrs: client ruta de configuración, puede optar por utilizar el JacksonJsonProvider para proporcionar

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

Necesitas incluir los artefactos jackson-mapper-asl y jackson-jaxr en tu classpath

 3
Author: Kilokahn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-05-01 14:22:36

También puede configurar CXFNonSpringJAXRSServlet (suponiendo que se use JSONProvider):

<init-param>
  <param-name>jaxrs.providers</param-name>
  <param-value>
      org.apache.cxf.jaxrs.provider.JSONProvider
      (writeXsiType=false)
  </param-value> 
</init-param>
 1
Author: Sergey Beryozkin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-04-15 18:19:33

Al crear servidor mediante programación, puede agregar escritores de cuerpo de mensaje para json/xml configurando Proveedores.

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();
 1
Author: A Kunin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-12-21 04:55:40

También puede intentar mencionar "Accept: application/json" en el encabezado del cliente rest, si espera que su objeto sea JSON en respuesta.

 0
Author: Koushik Das,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-04-02 14:16:17

Si está utilizando "cxf-rt-rs-client" versión 3.03. asegúrese de que el espacio de nombres xml y schemaLocation se declaran como se indica a continuación

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

Y asegúrese de que el cliente tenga JacksonJsonProvider o su JsonProvider personalizado

<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
Author: Purushothaman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-08-17 14:53:53