CXF: для класса не найден модуль записи тела сообщения - автоматическое сопоставление непростых ресурсов - PullRequest
39 голосов
/ 11 июня 2011

Я использую клиент CXF rest, который хорошо работает для простых типов данных (например, Strings, Ints). Однако, когда я пытаюсь использовать пользовательские объекты, я получаю это:

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

Библиотека рестлета делает это довольно просто, добавляя зависимость XStream к вашему пути, она «просто работает». Есть ли у CXF что-то похожее?

РЕДАКТИРОВАТЬ # 1:

Я опубликовал это как улучшение функции системы управления проблемами CXF здесь . Я могу только надеяться, что к этому придут внимание.

Ответы [ 9 ]

42 голосов
/ 14 июня 2011

Это не совсем из коробки, но CXF поддерживает привязки JSON к остальным сервисам.См. cxf jax-rs json документы здесь. Вам все равно нужно будет выполнить некоторую минимальную настройку, чтобы поставщик был доступен, и вам нужно быть знакомым с Jettison, если вы хотите иметь больше контроля над тем, как работает JSON.сформированный.

РЕДАКТИРОВАТЬ: Для запроса комментария, вот некоторый код.У меня нет большого опыта с этим, но следующий код работал в качестве примера в системе быстрого тестирования.

//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. Jettison в CXF работает путем маршаллинга XML из JAXB-провайдера в JSON.Таким образом, вы должны убедиться, что все объекты полезной нагрузки размечены (или иным образом сконфигурированы) для маршалинга как application / xml, прежде чем вы сможете сделать так, чтобы они маршалировали как JSON.Если вы знаете, как обойти это (кроме написания собственного автора тела сообщения), я хотел бы услышать об этом.
  3. Я использую Spring на сервере, поэтому в моей конфигурации есть все, что связано с XML.По сути, вам нужно пройти через тот же процесс, чтобы добавить JSONProvider в службу с той же конфигурацией пространства имен.У меня нет кода для этого, но я думаю, что он достаточно хорошо отразит на стороне клиента.

В качестве примера это немного грязно, но, надеюсь, поможет вам.

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 с минимальным кодом.

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

EDIT 4 - конфигурация без пружины Используя выбранный контейнер сервлета, настройте

org.apache.cxf.jaxrs.servlet.CXFNonSpringJaxrsServlet

по крайней мере с 2 параметрами инициализации:

jaxrs.serviceClasses
jaxrs.providers

, где serviceClasses - это список разделенных пробелами реализаций службы, которые вы хотите связать, таких как TestApi, упомянутый выше, ипоставщики - это список поставщиков тела сообщения, разделенных пробелами, например, упомянутый выше 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>

Это довольно быстрый способ запустить его без пружины.Если вы не используете контейнер сервлета, вам потребуется настроить JAXRSServerFactoryBean.setProviders с экземпляром XstreamJsonProvider и установить реализацию службы с помощью метода JAXRSServerFactoryBean.setResourceProvider.Проверьте метод CXFNonSpringJaxrsServlet.init, чтобы увидеть, как они это делают при установке в контейнере сервлета.

Это должно помочь вам в любом случае.

6 голосов
/ 31 октября 2014

Я столкнулся с этой проблемой при обновлении с 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>
3 голосов
/ 01 мая 2013

Если вы используете маршрут настройки клиента 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 в ваш путь к классам

2 голосов
/ 21 декабря 2016

При программном создании сервера вы можете добавлять средства записи тела сообщения для json / xml, устанавливая Providers.

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 голос
/ 15 апреля 2012

Вы также можете настроить CXFNonSpringJAXRSServlet (при условии использования JSONProvider):

<init-param>
  <param-name>jaxrs.providers</param-name>
  <param-value>
      org.apache.cxf.jaxrs.provider.JSONProvider
      (writeXsiType=false)
  </param-value> 
</init-param>
0 голосов
/ 21 мая 2019

Ни одно из вышеперечисленных изменений не сработало для меня.Пожалуйста, посмотрите мою рабочую конфигурацию ниже:

Зависимости:

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

Наслаждайтесь кодированием ..:)

0 голосов
/ 16 апреля 2019

В моем сценарии я столкнулся с подобной ошибкой, когда остальная часть URL без номера порта неправильно настроена для балансировки нагрузки.Я проверил остальной URL с номером порта, и эта проблема не возникала.поэтому нам пришлось обновить конфигурацию балансировки нагрузки, чтобы решить эту проблему.

0 голосов
/ 17 августа 2018

Если вы используете "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>
0 голосов
/ 02 апреля 2018

Вы также можете попробовать упомянуть «Accept: application / json» в заголовке вашего остального клиента, если вы ожидаете, что ваш объект как JSON в ответ.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...