在实现中设置JAX-RS响应头而不在接口中暴露HttpServletResponse
我有一个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响应头而不在资源接口中引入该依赖?
我看到一篇文章建议我将HttpServletResponse注入一个类成员。 但是,如果我的资源服务实现是单例,这将如何工作? 是否使用某种代理与线程本地或某些东西来确定正确的servlet响应,即使多个线程同时使用单例类? 还有其他解决方案吗?
正确的答案似乎是在实现的成员变量中注入HttpServletResponse
,正如我所指出的那样, 另一篇post已经指出。
@Context //injected response proxy supporting multiple threads private HttpServletResponse servletResponse;
即使peeskillet指出Jersey的半官方列表没有将HttpServletResponse
列为可代理类型之一,但当我追踪代码时,至少RESTEasy似乎正在创建代理( org.jboss.resteasy.core.ContextParameterInjector$GenericDelegatingProxy@xxxxxxxx
)。 所以据我所知,单线程成员变量的线程安全注入似乎正在发生。
所以注入HttpServletResponse
似乎是不行的。 只有某些可代理的类型可以注入单例。 我相信完整清单如下:
HttpHeaders, Request, UriInfo, SecurityContext
这在JAX-RS规范中有所指出,但在泽西参考指南中有更清楚的解释
特定请求对象存在exception,甚至可以将其注入构造函数或类字段。 对于这些对象,运行时将注入能够同时服务更多请求的代理。 这些请求对象是
HttpHeaders
,Request
,UriInfo
,SecurityContext
。 可以使用@Context
注释注入这些代理。
SecurityContext
可能是Jersey特定的,因为它没有在规范中说明,但我不确定。
现在上面提到的那些类型并没有真正为你做太多,因为它们都是请求上下文,没有任何设置响应。
一个想法是使用javax.ws.rs.container.ContainerResponseFilter
,以及HttpHeaders
来设置临时请求标头。 您可以通过传递给filter
方法的ContainerRequestContext
访问该标头。 然后只需通过ContainerResponseContext
设置响应头,也传递给filter
方法。 如果标头不是特定于该资源方法的上下文,那么它甚至更容易。 只需在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"); } }
只是因为只有单例类通过这个filter,我们可以简单地使用@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 {
这是我能想到处理这种情况的唯一方法。
资源:
- filter和拦截器
- 名称绑定
@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表示forms,其状态代码为200,标题header-name
的值为header-value
。 它看起来应该是这样的:
{ "bar-field": "bar-field-value", "bar-field-2": "bar-field-2" }