Использование провайдеров по умолчанию /MessageBodyWriters в Джерси 2
Просто начиная с Джерси, я пытался воспроизвести простой пример из последней документации Джерси " Ответы на строительство". Эта часть, насколько я понимаю, должна показать, как Response
а также ResponseBuilder
может быть использован для простого возврата ответа в сочетании с Entity<T>
для содержания ответа.
Теперь в документации говорится, что по умолчанию поддерживаются несколько типов данных (здесь: " Представления и типы Java"). String
премьер среди них, соответствующий любому типу СМИ.
Из всех вариантов, которые я пробовал, следующее является самым простым:
@POST
public Response post() {
URI createdUri;
try {
createdUri = new URI("http://test.lan");
} catch (final URISyntaxException e) {
throw new WebApplicationException(e);
}
return Response.created(createdUri).entity(Entity.text("someContent")).build();
}
Я всегда получал одну и ту же ошибку (полная трассировка стека ниже) при вызове запроса: org.glassfish.jersey.message.internal.MessageBodyProviderNotFoundException: MessageBodyWriter not found for media type=text/plain, type=class javax.ws.rs.client.Entity, genericType=class javax.ws.rs.client.Entity.
Я полагаю, это говорит о том, что подходящего поставщика не найдено для этого универсального типа сущности. Тем не менее, строка должна поддерживаться OOTB?
я нашел это StringMessageProvider
это, вероятно, реализация этого провайдера на Джерси 1, и наиболее близкие родственные классы, которые я нашел в моих библиотеках на Джерси 2, являются классами в org.glassfish.jersey.message.internal
в майке Среди многих провайдеров есть StringMessageProvider
, который представляется мне как потенциальный предполагаемый поставщик для этого.
Я посмотрел на проблему, и хотя есть много людей, которые получают это, когда неправильно пытаются использовать пользовательский провайдер, я не нашел ничего о неработающих OOTB-провайдерах по умолчанию.
Я проверил свои библиотеки и сейчас у меня есть следующие зависимости в моем pom (среди прочих):
- Джерси-контейнер-сервлет-жильный
- Джерси-клиент
- Джерси-общий
- Джерси-сервер
Я посмотрел онлайн, но, похоже, это все, что мне нужно, хотя я с уверенностью не нашел подходящие классы провайдеров для String и JAXB/JSON в банках.
контекст
- Maven проект
- с сервлетом api tomcat 6.0.29
- Версия 2.6 всех упомянутых библиотек Джерси
- Затмение Кеплер
- Использование плагина tomcat6 maven для запуска встроенного tomcat (пока работает нормально)
Запрос Fiddler, используемый для тестирования
POST HTTP/1.1
User-Agent: Fiddler
Host: 127.0.0.1
Content-Length: 0
И снова попробовал несколько вариантов.
Полная трассировка стека
06-Jan-2015 21:13:54 org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor aroundWriteTo
SEVERE: MessageBodyWriter not found for media type=text/plain, type=class javax.ws.rs.client.Entity, genericType=class javax.ws.rs.client.Entity.
06-Jan-2015 21:13:54 org.apache.catalina.core.StandardWrapperValve invoke
SEVERE: Servlet.service() for servlet TestService threw exception
org.glassfish.jersey.message.internal.MessageBodyProviderNotFoundException: MessageBodyWriter not found for media type=text/plain, type=class javax.ws.rs.client.Entity, genericType=class javax.ws.rs.client.Entity.
at org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor.aroundWriteTo(WriterInterceptorExecutor.java:247)
at org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:162)
at org.glassfish.jersey.server.internal.JsonWithPaddingInterceptor.aroundWriteTo(JsonWithPaddingInterceptor.java:103)
at org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:162)
at org.glassfish.jersey.server.internal.MappableExceptionWrapperInterceptor.aroundWriteTo(MappableExceptionWrapperInterceptor.java:88)
at org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:162)
at org.glassfish.jersey.message.internal.MessageBodyFactory.writeTo(MessageBodyFactory.java:1154)
at org.glassfish.jersey.server.ServerRuntime$Responder.writeResponse(ServerRuntime.java:571)
at org.glassfish.jersey.server.ServerRuntime$Responder.processResponse(ServerRuntime.java:378)
at org.glassfish.jersey.server.ServerRuntime$Responder.process(ServerRuntime.java:368)
at org.glassfish.jersey.server.ServerRuntime$1.run(ServerRuntime.java:262)
at org.glassfish.jersey.internal.Errors$1.call(Errors.java:271)
at org.glassfish.jersey.internal.Errors$1.call(Errors.java:267)
at org.glassfish.jersey.internal.Errors.process(Errors.java:315)
at org.glassfish.jersey.internal.Errors.process(Errors.java:297)
at org.glassfish.jersey.internal.Errors.process(Errors.java:267)
at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:319)
at org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:236)
at org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:1028)
at org.glassfish.jersey.servlet.WebComponent.service(WebComponent.java:373)
at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:381)
at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:344)
at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:219)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:293)
at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:859)
at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:602)
at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:489)
at java.lang.Thread.run(Thread.java:662)
РЕДАКТИРОВАТЬ
Та же самая ошибка (для application/json) происходит теперь, когда я аннотировал класс @XmlRootElement
и попробуйте вернуть его в соответствии с документами Джерси:
@GET
@Produces(MediaType.APPLICATION_JSON)
public Foo sampleFoo() {
Foo foo = new Foo();
return foo;
}
куда Foo
помечен @XmlRootElement
,
Я также добавил в качестве зависимости jersey-media-json-jackson, который, как я вижу, содержит явного поставщика JSONJaxb. Тем не менее, кажется, что это не так.
3 ответа
Первая проблема:
javax.ws.rs.client.Entity
это класс на стороне клиента. Спецификация JAX-RS ничего не говорит о его использовании на стороне сервера. Но я могу подтвердить многими различными тестами, что результат будет похож на то, что вы видите (по крайней мере, с Джерси). С Resteasy, он просто отправит Entity.toString()
Так как это не работает ни для Resteasy, ни для Jersey, я не буду говорить, что это ошибка, но возможна ошибка в документации Jersey, в которой приведен пример ее использования следующим образом:
@POST
@Consumes("application/xml")
public Response post(String content) {
URI createdUri = ...
String createdContent = create(content);
return Response.created(createdUri)
.entity(Entity.text(createdContent)).build();
}
Вышесказанное не удалось и для меня. Но вы не ошиблись, сказав
... по умолчанию поддерживается несколько типов данных
как они есть. Чтобы ваш пример работал, просто измените Entity.text("someContent")
просто "someContent"
return Response.created(createdUri).entity("someContent").build();
И просто для полноты, использование на стороне клиента может выглядеть примерно так
Response response = webTarget.request().post(Entity.text("Hello World"));
который работает просто отлично.
Второй выпуск:
До (я считаю) Джерси 2.9, jersey-media-json-jackson
модуль не настроен автоматически. Таким образом, с 2.6 нам нужно настроить конфигурацию либо путем сканирования пакетов в web.xml
или в Application
подкласс. В любом случае, web.xml
требуется. Как указано здесь в отношении среды сервлетов 2.x, которой является Tomcat 6.
В среде Servlet 2.5 вы должны явно объявить сервлет Jersey-контейнера в файле дескриптора развертывания web.xml вашего веб-приложения.
Поэтому для поиска классов провайдера JSON необходимо указать пакет в jersey.config.server.provider.packages
INIT-пары. Пример web.xml будет примерно таким
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<servlet>
<servlet-name>Jersey Web Application</servlet-name>
<servlet-class>
org.glassfish.jersey.servlet.ServletContainer
</servlet-class>
<init-param>
<param-name>jersey.config.server.provider.packages</param-name>
<param-value>
thepackage.of.your.resources,
org.codehaus.jackson.jaxrs <!-- Jackson providers -->
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Jersey Web Application</servlet-name>
<url-pattern>/api/*</url-pattern>
</servlet-mapping>
</web-app>
Вам также разрешено использовать Application
подкласс (который ResourceConfig
продолжается от). Нам просто нужно указать это в web.xml
, Пример конфигурации может быть что-то вроде
public class MyApplication extends ResourceConfig {
public MyApplication() {
register(JacksonFeature.class);
packages("thepackage.of.your.resources");
}
}
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<servlet>
<servlet-name>MyApplication</servlet-name>
<servlet-class>
org.glassfish.jersey.servlet.ServletContainer
</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>jersey2.tomcat6.MyApplication</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>MyApplication</servlet-name>
<url-pattern>/api/*</url-pattern>
</servlet-mapping>
</web-app>
Примечание. Все это было протестировано в той же среде, кроме Eclipse. Я использую Netbeans, хотя это не должно иметь никакого значения. Кроме того, единственные зависимости Maven, в которых я нуждался, были
<dependencies>
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet-core</artifactId>
<version>${jersey.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>${jersey.version}</version>
</dependency>
</dependencies>
<jersey.version>2.6</jersey.version>
С другой стороны, чтобы упростить разработку, я просто создал простой архетип Maven со следующими координатами
GroupId: org.glassfish.jersey.archetypes
ArtifactId: jersey-quickstart-webapp
Version: 2.6
Вы также можете увидеть Создание нового проекта из Maven Archetype
Что касается формата text/plain, это работает?
return Response.created(createdUri).type(MediaType.TEXT_PLAIN).entity("someContent").build();
Для вывода JSON у меня есть эти зависимости
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
а также реализация Jaxb. Любой подойдет, пользуюсь
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
</dependency>
Я также определяю провайдера сопоставления объектов, хотя я не уверен на 100%, что это требуется (если вы не хотите настраивать):
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.fasterxml.jackson.module.jaxb.JaxbAnnotationIntrospector;
@Provider
public class ObjectMapperProvider implements ContextResolver<ObjectMapper> {
ObjectMapper mapper;
public ObjectMapperProvider() {
mapper = new ObjectMapper();
mapper.configure( SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false );
mapper.configure( SerializationFeature.INDENT_OUTPUT, true );
mapper.configure( SerializationFeature.WRITE_NULL_MAP_VALUES, true );
mapper.configure( SerializationFeature.WRITE_EMPTY_JSON_ARRAYS, true );
mapper.setAnnotationIntrospector( new JaxbAnnotationIntrospector(TypeFactory.defaultInstance()) );
}
@Override
public ObjectMapper getContext(Class<?> type) {
return mapper;
}
}
Кроме того, я думаю, что вам нужно зарегистрировать функцию Джексона:
@ApplicationPath("")
public class Application extends ResourceConfig {
public Application() {
register( JacksonFeature.class );
}
}
Должен отметить, что все это настроено с использованием Jersey 2.11.
В моем случае я добавил зависимость jersey-media-json-jackson. Это сработало для меня.
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>2.20</version>
</dependency>