在Jersey 2中使用默认的Providers / MessageBodyWriters

时间:2015-01-06 20:17:59

标签: rest jax-rs server jersey-2.0 java-6

刚开始使用泽西岛时,我一直试图在最新的泽西文档&#39; building responses&#39;中重现这个简单的例子。据我了解,此部分应说明如何使用ResponseResponseBuilder轻松返回与Entity<T>结合使用的响应内容。

现在,文档指出默认支持多种数据类型(此处为:&#39; Representations and Java types&#39;)。其中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可能是此提供程序的Jersey 1实现,而我在Jersey 2库中找到的最接近的相关类是jersey-common中org.glassfish.jersey.message.internal中的类。在许多提供商中有StringMessageProvider,在我看来,这似乎是一个潜在的预期提供商。

我已经查看了这个问题,虽然有很多人在错误地尝试使用自定义提供程序时遇到此问题,但我发现默认的OOTB提供程序无法正常工作..

我已经检查了我的libs,现在我的pom(以及其他)中有以下依赖项:

  • Jersey的容器servlet的芯
  • Jersey的客户端
  • Jersey的共同
  • Jersey的服务器

我已经在线查看了,但这似乎就是我所需要的,尽管我还没有确定在罐子里找到了适合String和JAXB / JSON的提供程序类。

上下文

  • Maven项目
  • with tomcat servlet-api 6.0.29
  • 所有提到的球衣库的版本2.6
  • Eclipse kepler
  • 使用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提供程序。但是,它似乎不会以某种方式被拾取。

2 个答案:

答案 0 :(得分:23)

第一期:

javax.ws.rs.client.Entity是一个客户端类。 JAX-RS规范没有说明它在服务器端的用法。但我可以通过许多不同的测试确认,结果将与您所看到的相似(至少使用Jersey)。使用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();

只是为了完整性,client side usage可能看起来像

Response response = webTarget.request().post(Entity.text("Hello World"));

效果很好。

第二期:

直到(我相信)Jersey 2.9,jersey-media-json-jackson模块未自动配置。因此,对于2.6,我们需要通过web.xmlApplication子类中的包扫描来设置配置。无论哪种方式,都需要web.xml 。正如here所述,关于2.x servlet环境,Tomcat 6是。

  

在Servlet 2.5环境中,您必须在Web应用程序的web.xml部署描述符文件中显式声明Jersey容器Servlet。

因此,要扫描JSON提供程序类,应在jersey.config.server.provider.packages init-param中指定包。一个示例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

您还可以看到Creating a New Project from Maven Archetype

答案 1 :(得分:1)

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

另外,我认为您需要注册Jackson功能:

@ApplicationPath("")
public class Application extends ResourceConfig {
    public Application() {
        register( JacksonFeature.class );
    }
}

我应该注意,这都是使用Jersey 2.11配置的。