#java #web-services #cxf
#java #веб-службы #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 к вашему path, это «просто работает». Есть ли в CXF что-то подобное?
ПРАВКА # 1:
Я разместил это как улучшение функции в системе управления проблемами CXF здесь. Я могу только надеяться, что на это обратят внимание.
Ответ №1:
Это не совсем из коробки, но CXF поддерживает привязки JSON к службам rest. Смотрите Документы 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;
}
Примечания:
- Список поставщиков — это то место, где вы программируете настройку поставщика JSON на клиенте. В частности, вы видите сопоставление пространства имен. Это должно соответствовать конфигурации на стороне вашего сервера. Я мало что знаю о параметрах сброса, поэтому я не очень помогаю в манипулировании всеми различными регуляторами для управления процессом сортировки.
- Сброс в CXF работает путем преобразования XML от поставщика JAXB в JSON. Таким образом, вы должны убедиться, что все объекты полезной нагрузки помечены (или иным образом настроены) для маршаллинга как application / xml, прежде чем вы сможете использовать их маршаллинг как JSON. Если вы знаете способ обойти это (кроме написания собственного средства записи тела сообщения), я был бы рад услышать об этом.
- Я использую 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 )
amp;amp; 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 )
amp;amp; 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 — пример конфигурации на стороне сервера Как я уже говорил ранее, моя серверная часть настроена на spring. Вот пример конфигурации, которая работает для подключения к провайдеру:
<?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 () )
amp;amp; MediaType.APPLICATION_JSON_TYPE.getSubtype ().equals ( mediaType.getSubtype () )
amp;amp; type.equals ( Demo.class );
ПРАВКА 4 — конфигурация без spring
Используя выбранный вами контейнер сервлета, настройте
org.apache.cxf.jaxrs.servlet.CXFNonSpringJaxrsServlet
с параметрами инициализации не менее 2 из:
jaxrs.serviceClasses
jaxrs.providers
где serviceClasses — это разделенный пробелом список реализаций сервиса, которые вы хотите связать, таких как упомянутый выше TestApi, а providers — разделенный пробелом список поставщиков тела сообщения, таких как упомянутый выше 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>
Это в значительной степени самый быстрый способ запустить его без spring. Если вы не используете контейнер сервлета, вам нужно будет настроить JAXRSServerFactoryBean.setProviders с экземпляром XstreamJsonProvider и установить реализацию сервиса с помощью метода JAXRSServerFactoryBean.setResourceProvider. Проверьте метод CXFNonSpringJaxrsServlet.init, чтобы увидеть, как они это делают при настройке в контейнере сервлета.
Это должно заставить вас работать независимо от вашего сценария.
Комментарии:
1. Я видел это, но в примере используется Spring, чего я бы хотел избежать. У вас есть Java-код, чтобы заставить это работать? 1
2. Это то, чего я боялся… для того, чтобы создавать объекты по проводам с помощью CXF, вы должны разметить классы аннотациями JAXB. Однако в моем примере… изменение классов для добавления аннотаций JAXB не является вариантом. Таким образом, это не то, что я бы количественно определил как «автоматическое сопоставление», поскольку вам нужно модифицировать свои классы, чтобы заставить их маршалировать. Библиотека restlet делает это по умолчанию через xstream (вы просто добавляете jar в свой path) — я надеялся, что в CXF есть что-то подобное.
3. Понятно; итак, ваша реальная проблема в том, что вам нужен поставщик тела сообщения json, который не основан на базовом поставщике jaxb. На самом деле, это, вероятно, упрощает задачу. Вы можете быстро создать провайдер на основе xstream и просто использовать его. Я вскоре опубликую некоторый пример кода, который у меня есть для этого.
4. Прежде всего, я ценю ваши усилия помочь мне решить эту проблему. Во-вторых, я ожидал бы, что нечто подобное будет существовать в библиотеке «из коробки» — но, возможно, я ожидаю слишком многого. На мой взгляд, это кажется тяжелым и беспорядочным, что нужно было бы реализовать MessageBody[Reader Writer] для каждого ресурса. Если кто-нибудь не предложит лучшее решение, я приму ваше решение и предоставлю вам вознаграждение в конце временной шкалы, однако я все еще не считаю это оптимальным решением. Спасибо!
5. Рад помочь, если смогу. Лично я бы не ожидал многого от готового поставщика json; слишком много возможностей для того, чтобы он был полезен в целом. Во многих из этих случаев клиент и сервер должны были бы знать совсем немного друг о друге, чтобы работать должным образом, что могло бы сделать разметку jaxb или другие методы намного более выполнимыми. И последнее, вам не нужно иметь средство чтения / записи тела сообщения для каждого ресурса. Вы можете легко изменить isWritable / Readable, чтобы не заботиться о типе класса, оставляя вам сортировку xstream / jettison по умолчанию для всех объектов. Менее 100 LOC, дешево.
Ответ №2:
Я столкнулся с этой проблемой при обновлении с 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>
Комментарии:
1. Можете ли вы поделиться тем, что вы сделали для регистрации класса provider в CXF 2.7?
2. что такое xmlns для jaxrs?
Ответ №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 в ваш classpath
Ответ №4:
При программном создании сервера вы можете добавить средства записи тела сообщения для 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();
Ответ №5:
Вы также можете настроить CXFNonSpringJAXRSServlet (при условии, что используется JsonProvider):
<init-param>
<param-name>jaxrs.providers</param-name>
<param-value>
org.apache.cxf.jaxrs.provider.JSONProvider
(writeXsiType=false)
</param-value>
</init-param>
Ответ №6:
Ни одно из вышеупомянутых изменений не сработало для меня. Пожалуйста, посмотрите мою рабочую конфигурацию ниже:
Зависимости:
<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>
Наслаждайтесь кодированием .. 🙂
Ответ №7:
Шаг 1: Добавьте класс bean в dataFormat
список:
<dataFormats>
<json id="jack" library="Jackson" prettyPrint="true"
unmarshalTypeName="{ur bean class path}" />
</dataFormats>
Шаг 2: Маршалируйте компонент перед вызовом клиента:
<marchal id="marsh" ref="jack"/>
Ответ №8:
Вы также можете попробовать указать «Accept: application / json» в заголовке вашего rest-клиента, если вы ожидаете, что ваш объект в ответ будет представлен в виде JSON.
Комментарии:
1. На самом деле это не помогает @Koushik Das
Ответ №9:
Если вы используете «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>
Ответ №10:
В моем сценарии я столкнулся с аналогичной ошибкой, когда rest URL без номера порта неправильно настроен для балансировки нагрузки. Я проверил rest URL с помощью portnumber, и эта проблема не возникла. поэтому нам пришлось обновить конфигурацию балансировки нагрузки, чтобы решить эту проблему.
Ответ №11:
Наличие обоих org.codehaus.зависимости jackson и com.fasterxml.jackson в одном проекте вызовут проблему «Нет средства записи тела сообщения» независимо от аннотаций.
В моем случае компонент был маршалирован com.fasterxml.jackson -> jackson-jaxrs-json-provider и разархивирован org.codehaus.jackson -> jackson-jaxrs
Итак, удаляем / обновляем все ссылки из org.codehaus.джексон в com.fasterxml.jackson.jaxrs исправил эту проблему.
Я обновил его в cxf-servlet.xml, pom.xml и во всех импортируемых классах Java.
pom.xml
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-rs-extension-providers</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId>
<version>2.13.4</version>
</dependency>
Поставщик в cxf-servlet.xml
<jaxrs:providers>
<bean class="com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider" />
</jaxrs:providers>