在 Jersey 2 中使用默认 Providers/MessageBodyWriters

Using default Providers/MessageBodyWriters in Jersey 2

刚开始使用 Jersey,我一直在尝试重现最新 Jersey 文档“building responses”中的简单示例。据我了解,这部分应该展示如何使用 ResponseResponseBuilder 轻松地 return 结合 Entity<T> 响应内容的响应。

现在,文档指出默认支持多种数据类型(此处:“Representations and Java types”)。 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.

我相信它是说找不到适合此实体通用类型的提供程序。不过,String应该支持OOTB吧?

我发现 StringMessageProvider 可能是该提供程序的 Jersey 1 实现,而我在 Jersey 2 库中找到的最相关的 classes 是 classes org.glassfish.jersey.message.internal 在球衣中很常见。在众多供应商中,有一个 StringMessageProvider,在我看来它像是一个潜在的预期供应商。

我已经查过这个问题,虽然有很多人在错误地尝试使用自定义提供程序时遇到这个问题,但我没有发现默认的 OOTB 提供程序不起作用..

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

我在网上看过,但这似乎就是我所需要的,虽然我还不确定找到正确的提供程序 class 字符串和 JAXB/JSON 在罐子里。

上下文

用于测试的 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.run(ServerRuntime.java:262)
    at org.glassfish.jersey.internal.Errors.call(Errors.java:271)
    at org.glassfish.jersey.internal.Errors.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 注释了 class 并尝试在 Jersey 的方法中 return 它文档:

@GET
@Produces(MediaType.APPLICATION_JSON) 
public Foo sampleFoo() {
    Foo foo = new Foo();

    return foo;
}

其中 Foo 被注释为 @XmlRootElement

我还添加了 jersey-media-json-jackson 作为依赖项,我可以看到它包含一个明确的 JSONJaxb 提供程序。不过怎么好像没捡到。

就 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 配置的。

第一期:

javax.ws.rs.client.Entity 是客户端class。 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();
}

以上对我来说也失败了。但是你说的没错

...several data types are by default supported

原样。要使您的示例正常工作,只需将 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 subclass 中的包扫描来设置配置。无论哪种方式,都需要 web.xml 。正如 here 关于 2.x servlet 环境所述,Tomcat 6 是。

In Servlet 2.5 environment, you have to explicitly declare the Jersey container Servlet in your Web application's web.xml deployment descriptor file.

因此,要扫描 JSON 提供程序 classes,您应该在 jersey.config.server.provider.packages 初始化参数中指定包。一个例子 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 subclass(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

在我的例子中,我添加了 jersey-media-json-jackson 依赖项。它对我有用。

<dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-json-jackson</artifactId>
    <version>2.20</version>
</dependency>