web-dev-qa-db-fra.com

CXF: aucun rédacteur de corps de message trouvé pour la classe - mappant automatiquement des ressources non simples

J'utilise le client de repos CXF qui fonctionne bien pour les types de données simples (par exemple: Strings, ints). Cependant, lorsque j'essaie d'utiliser des objets personnalisés, je reçois ceci:

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

J'appelle ça comme ça:

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

Voici la méthode dans l'interface:

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

La bibliothèque restlet fait cela très simplement, en ajoutant la dépendance XStream à votre chemin, cela "fonctionne". CXF a-t-il quelque chose de similaire?

EDIT # 1:

J'ai posté ceci comme une amélioration des fonctionnalités du système de gestion des problèmes CXF ici . Je ne peux qu'espérer que cela sera traité.

36
javamonkey79

CXF prend en charge les liaisons JSON avec les services de repos. Voir cxf jax-rs json docs ici. Vous aurez toujours besoin de faire une configuration minimale pour que le fournisseur soit disponible et vous devez être familiarisé avec le largage si vous voulez avoir plus de contrôle sur la manière dont le JSON est formé.

EDIT: Par demande de commentaire, voici du code. Je n'ai pas beaucoup d'expérience avec cela, mais le code suivant a fonctionné à titre d'exemple dans un système de test rapide.

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

Remarques:

  1. La liste des fournisseurs vous permet de configurer le fournisseur JSON sur le client. En particulier, vous voyez le mappage d'espace de noms. Cela doit correspondre à ce qui se trouve sur votre configuration côté serveur. Je ne connais pas grand chose aux options de Jettison, je ne vous aide donc pas beaucoup à manipuler tous les boutons pour contrôler le processus de triage.
  2. Jettison dans CXF fonctionne en organisant du XML depuis un fournisseur JAXB vers JSON. Vous devez donc vous assurer que les objets de charge utile sont tous marqués (ou autrement configurés) pour être marshall en tant qu'application/xml avant de pouvoir les avoir en tant que JRC. Si vous connaissez un moyen de contourner le problème (autre que d'écrire votre propre rédacteur de corps de message), j'aimerais en entendre parler.
  3. J'utilise spring sur le serveur pour que ma configuration soit entièrement basée sur XML. Essentiellement, vous devez suivre le même processus pour ajouter JSONProvider au service avec la même configuration d’espace de nom. Vous n'avez pas de code pour cela, mais j'imagine que cela reflétera assez bien le côté client.

Ceci est un peu sale à titre d’exemple, mais nous espérons pouvoir vous aider.

Edit2: Exemple d'un rédacteur de corps de message basé sur xstream pour éviter 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 );
}

Il manque dans l'exemple de code les éléments nécessaires à la prise en charge de types de supports robustes, à la gestion des erreurs, à la sécurité des threads, etc. Cependant, il devrait vous éviter le problème de jaxb avec un code minimal.

EDIT 3 - Exemple de configuration côté serveur Comme je l'ai dit précédemment, mon côté serveur est configuré par ressort. Voici un exemple de configuration qui fonctionne pour relier le fournisseur:

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

J'ai également noté que, dans la dernière version de cxf que j'utilise, il existe une différence entre les types de média. L'exemple ci-dessus du lecteur/écrivain du corps du message xstream nécessite une modification rapide, où isWritable/isReadable devient:

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

EDIT 4 - configuration sans ressort À l'aide du conteneur de servlet de votre choix, configurez

org.Apache.cxf.jaxrs.servlet.CXFNonSpringJaxrsServlet

avec au moins 2 paramètres init de:

jaxrs.serviceClasses
jaxrs.providers

où serviceClasses est une liste séparée par des espaces des implémentations de services que vous voulez lier, telle que TestApi mentionnée ci-dessus et le fournisseur est une liste séparée par des espaces de fournisseurs de corps de message, tel que XstreamJsonProvider mentionné ci-dessus. Dans Tomcat, vous pouvez ajouter les éléments suivants au fichier 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>

C'est à peu près le moyen le plus rapide de le faire sans printemps. Si vous n'utilisez pas de conteneur de servlet, vous devez configurer JAXRSServerFactoryBean.setProviders avec une instance de XstreamJsonProvider et définir l'implémentation du service via la méthode JAXRSServerFactoryBean.setResourceProvider. Vérifiez la méthode CXFNonSpringJaxrsServlet.init pour voir comment elle le fait lors de la configuration dans un conteneur de servlet.

Cela devrait vous faire avancer, peu importe votre scénario.

42
philwb

J'ai rencontré ce problème lors de la mise à niveau de CXF 2.7.0 à 3.0.2. Voici ce que j'ai fait pour le résoudre:

Inclus ce qui suit dans mon 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>

et ajouté le fournisseur suivant

    <jaxrs:providers>
        <bean class="org.codehaus.jackson.jaxrs.JacksonJaxbJsonProvider" />
    </jaxrs:providers>
5
Ross H Mills III

Si vous utilisez jaxrs: itinéraire client de configuration, vous pouvez choisir d’utiliser JacksonJsonProvider pour fournir 

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

Vous devez inclure les artefacts jackson-mapper-asl et jackson-jaxr dans votre chemin d'accès aux classes.

3
Kilokahn

Lorsque vous créez un serveur par programme, vous pouvez ajouter des rédacteurs de corps de message pour json/xml en définissant des fournisseurs.

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();
2
A Kunin

Vous pouvez également configurer CXFNonSpringJAXRSServlet (en supposant que JSONProvider est utilisé):

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

Si vous utilisez "cxf-rt-rs-client" version 3.03. ou ci-dessus, assurez-vous que l'espace de nom xml et schemaLocation sont déclarés comme suit

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

Et assurez-vous que le client a JacksonJsonProvider ou votre JsonProvider personnalisé 

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

Vous pouvez également essayer de mentionner "Accepter: application/json" dans l'en-tête de votre client restant, si vous attendez que votre objet soit JSON en réponse.

0
Koushik Das