在实现中设置JAX-RS响应头而不在接口中暴露HttpServletResponse

时间:2014-12-25 13:33:59

标签: java rest jax-rs httpresponse

我有一个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响应,即使多个线程同时使用单例类?还有其他解决方案吗?

3 个答案:

答案 0 :(得分:5)

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

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

即使peeskillet表示Jersey的半官方列表没有列出HttpServletResponse作为可代理类型之一,但当我追踪代码时,至少RESTEasy似乎正在创建一个代理({ {1}})。所以据我所知,单线成员变量的线程安全注入似乎正在发生。

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

答案 1 :(得分:2)

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

HttpHeaders, Request, UriInfo, SecurityContext

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

  

特定请求对象存在异常,甚至可以注入构造函数或类字段。对于这些对象,运行时将注入能够同时服务更多请求的代理。这些请求对象为HttpHeadersRequestUriInfoSecurityContext。可以使用@Context注释注入这些代理。

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

现在上面提到的那些类型对你来说并不是很重要,因为它们都是请求上下文而没有设置响应。

一个想法是使用javax.ws.rs.container.ContainerResponseFilterHttpHeaders来设置临时请求标头。您可以通过传递给ContainerRequestContext方法的filter访问该标头。然后只需通过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 {

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


<强>资源:

答案 2 :(得分:-2)

@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"
}