【问题标题】:Set JAX-RS response headers in implementation without exposing HttpServletResponse in interface在实现中设置 JAX-RS 响应标头而不在接口中暴露 HttpServletResponse
【发布时间】:2015-02-23 04:58:35
【问题描述】:

我有一个 RESTful 服务器实现以及一个供客户端进行调用的库,所有这些都使用 JAX-RS。服务器组件分为接口FooResource和实现FooResourceService。

为了让客户端和服务器库共享 RESTful 路径和其他定义,我想将FooResource 接口拆分到自己的项目中:

@Path(value = "foo")
public interface FooResource {

  @GET
  public Bar getBar(@PathParam(value = "{id}") int id) {

我想在响应中设置一些标题。一种简单的方法是在方法签名中使用@Context HttpServletResponse:

  public Bar getBar(@PathParam(value = "{id}") int id, @Context HttpServletResponse servletResponse) {

但问题是这会在接口中暴露实现细节。更具体地说,它突然需要我的 REST 定义项目(在客户端和服务器库之间共享)来引入 javax.servlet-api 依赖项——这是客户端不需要(或不需要)的东西。

我的 RESTful 资源服务实现如何设置 HTTP 响应标头而不在资源接口中引入该依赖项?

我看到one post 建议我将 HttpServletResponse 作为类成员注入。但是,如果我的资源服务实现是单例的,这将如何工作?它是否使用某种带有线程局部变量的代理,或者即使单例类被多个线程同时使用,也能找出正确的 servlet 响应?还有其他解决方案吗?

【问题讨论】:

    标签: java rest jax-rs httpresponse


    【解决方案1】:

    正确的答案似乎是在实现的成员变量中注入一个HttpServletResponse,正如我注意到another post 所指出的那样。

    @Context  //injected response proxy supporting multiple threads
    private HttpServletResponse servletResponse;
    

    尽管 peeskillet 表示 Jersey 的半官方列表并未将 HttpServletResponse 列为可代理类型之一,但当我跟踪代码时,至少 RESTEasy 似乎正在创建代理 (org.jboss.resteasy.core.ContextParameterInjector$GenericDelegatingProxy@xxxxxxxx )。据我所知,单例成员变量的线程安全注入似乎正在发生。

    另见https://stackoverflow.com/a/10076327/421049。

    【讨论】:

      【解决方案2】:

      所以注入HttpServletResponse 似乎不行。只有某些可代理的类型可以注入到单例中。我相信完整的列表如下:

      HttpHeaders, Request, UriInfo, SecurityContext
      

      这在 JAX-RS 规范中有所指出,但在 Jersey 参考指南中解释得更清楚

      对于特定的请求对象存在例外,这些对象甚至可以注入到构造函数或类字段中。对于这些对象,运行时将注入能够同时处理更多请求的代理。这些请求对象是HttpHeaders、Request、UriInfo、SecurityContext。这些代理可以使用@Context 注解注入。

      SecurityContext 可能是泽西岛特有的,因为规范中没有说明,但我不确定。

      现在上面提到的那些类型对你来说并没有多大作用,因为它们都是请求上下文,没有什么可以设置响应。

      一个想法是使用javax.ws.rs.container.ContainerResponseFilter 和HttpHeaders 来设置临时请求标头。您可以通过传递给filter 方法的ContainerRequestContext 访问该标头。然后只需通过ContainerResponseContext设置响应头,也传递给filter方法。如果标头不是特定于该资源方法的上下文,那么它就更容易了。只需在过滤器中设置标题即可。

      但是假设头部依赖于资源方法的执行。然后你可以做类似的事情

      @Singleton
      @Path("/singleton")
      public class SingletonResource {
      
          @Context
          javax.ws.rs.core.HttpHeaders headers;
      
          @GET
          public String getHello() {
      
              String result = resultFromSomeCondition(new Object());
              headers.getRequestHeaders().putSingle("X-HELLO", result);
              return "Hello World";
          }
      
          private String resultFromSomeCondition(Object condition) {
              return "World";
          }
      }
      

      那么ContainerResponseFilter 可能看起来像这样

      @Provider
      public class SingletonContainerResponseFilter 
                                  implements ContainerResponseFilter {
      
          @Override
          public void filter(ContainerRequestContext crc, 
                  ContainerResponseContext crc1) throws IOException {
              String header = crc.getHeaderString("X-HELLO");
              crc1.getHeaders().putSingle("X-HELLO", "World");
          } 
      }
      

      只有单例类通过这个过滤器,我们可以简单地使用@NameBinding注解

      import java.lang.annotation.ElementType;
      import java.lang.annotation.Retention;
      import java.lang.annotation.RetentionPolicy;
      import java.lang.annotation.Target;
      import javax.ws.rs.NameBinding;
      
      @NameBinding
      @Target(ElementType.TYPE)
      @Retention(RetentionPolicy.RUNTIME)
      public @interface SingletonHeader {}
      
      ...
      
      @SingletonHeader
      public class SingletonResource {
      
      ...
      
      @SingletonHeader
      public class SingletonContainerResponseFilter 
                              implements ContainerResponseFilter {
      

      这是我能想到的处理这种情况的唯一方法。


      资源:

      【讨论】:

      • 在这里查看我的另一个answer。我试过了,它似乎可以与 RESTEasy 一起使用。
      • 因此,当您使用 @Singleton 注释进行包扫描时,它可以工作,但是当您在 Application 类中注册 getSingletons() 时,它就会失败(我得到的 NPE)。在后一种情况下,对于泽西岛,我只会得到一个例外,比如“不在请求范围内”。 @Singleton 在泽西岛仍然失败。对于遇到此问题的任何其他人,请提供更多 FYI。
      • 有道理,如果容器要处理注入,为什么在getSingletons() 中实例化它不起作用。
      • 奇怪...我在getSingletons() 中使用它没有问题。事实上,我什至委托PicoContainer 来进行实际的实例化,因此在返回实例后必须进行任何包装。
      • 是的,在 RESTEasy 中,至少安装在容器中的是实际的 FooResourceService,但它的私有成员已被注入代理。
      【解决方案3】:
      @Path("/foo")
      public interface FooResource {
      
          @GET
          @Path("{id}")
          public Response getBar(@PathParam("id") int id) {
              Bar bar = new Bar();
              //Do some logic on bar
              return Response.ok().entity(bar).header("header-name", "header-value").build()
          }
      }
      

      返回 bar 实例的 JSON 表示,状态码为 200,标头 header-name,值为 header-value。它看起来应该是这样的:

      {
          "bar-field": "bar-field-value",
          "bar-field-2": "bar-field-2"
      }
      

      【讨论】:

      • 我不想在签名中返回Response。我想在签名中返回Bar。
      • 我的浏览器没有做软件开发。我的开发人员是。我想创建一个干净的 API,在语义上指示返回的内容。我想将 JAX-RS 特定项目归类为注释和实现代码。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-07-23
      • 1970-01-01
      • 1970-01-01
      • 2021-06-09
      • 2017-10-13
      • 2012-11-12
      相关资源
      最近更新 更多