@Context返回代理而不是HttpServletRequest(代理范围内没有线程本地值)
理解为什么@Context
dependency injection返回$ Proxy(随机数)实例的集合而不是HttpServletRequest或HttpServletResponse时,我有点问题。
我正在使用Glassfish 3.1.2.2及其版本的Jersey(Jersey:1.11.1),我的应用程序是作为EAR应用程序构建的。
我有简单的@Remote接口,我在那里注释我的方法和REST服务没有任何问题,但是当我尝试访问HttpServletRequest信息时,它只会导致问题。
我的会话bean中有注释的私有字段:
@Context private HttpServletRequest request; @Context private HttpServletResponse response;
并创建方法签名以包含@Context作为参数集:
@POST @Path("authenticate") @Consumes(MediaType.APPLICATION_FORM_URLENCODED) @Produces(MediaType.APPLICATION_JSON) public Response authenticate(@FormParam("username") String username, @FormParam("password") String password, @Context HttpServletRequest request, @Context HttpServletResponse response);
当我尝试在条目之后调试方法时,我可以看到全局请求和响应对象为null,其中本地方法实例是$ ProxyXXX类型。
问题是我无法访问(或者我不确定)这些对象。 根据网络上的教程,他们应该可以使用,但是当我尝试访问这些时,这就是抛出:
WARNING: StandardWrapperValve[RestDataService]: PWC1406: Servlet.service() for servlet RestDataService threw exception java.lang.IllegalStateException: No thread local value in scope for proxy of class $Proxy245 at com.sun.jersey.server.impl.ThreadLocalInvoker.invoke(ThreadLocalInvoker.java:93) at $Proxy245.getContextPath(Unknown Source)
这就是我试图调用这些(在这个例子中我只是调用getContextPath)
@Override public Response authenticate(String username, String password, HttpServletRequest request, HttpServletResponse response) { System.out.println("just a test: " + request.getContextPath());
我在这里想念的是什么? 有没有人遇到类似的问题?
格雷格
要了解此问题,您需要了解范围的工作原理。 为了描述会发生什么,这是一个例子。 假设你有这个
@Singleton @Path("..") public class SomeResource { @Context private HttpServletRequest request; }
这种情况造成的问题是为每个请求生成了一个HttpServletRequest
,但是资源类只创建了一次,因为它是一个单例,所以在创建时,最初没有HttpServletRequest
注入单例。
为了解决这个问题,Jersey注入了一个代理。 从概念上讲,结果更像是
@Singleton @Path("..") public class SomeResource { @Context private ProxyHttpServletRequest proxyRequest; }
当请求进入时,实际的HttpServletRequest
被放入范围上下文中的ThreadLocal
中。 这不是一个确切的实现,但你可以想象一个范围上下文
public class RequestScopeContext { private static final ThreadLocal request = new ThreadLocal<>(); public static HttpServletRequest get() { .. } public static void setRequest(HttpServletRequest request) { .. } }
当请求进入时, HttpServletRequest
被设置到上下文中。 当调用SomeResource
的HttpServletRequest
,它实际上是在代理对象上进行的,该代理对象从ThreadLocal
获取请求并转发调用。 从概念上讲,你可以想象它看起来像
class ProxyHttpServletRequest { public String getContextPath() { HttpServletRequest request = RequestScopeContext.get(); return request.getContextPath(); } }
现在假设我们有这个
@Singleton @Path("..") public class SomeResource { @GET public Response get(@Context HttpServletRequest request) { ... } }
与此不同的是请求不再注入字段,因此不需要代理。 只有在调用方法时才需要HttpServletRequest
。 所以泽西岛将注入实际的请求,而不是代理。
需要注意的是,这种模式并不仅仅针对泽西岛。 任何框架都是DI并且涉及范围,在尝试将较小范围的对象注入更广泛的范围对象时将使用类似的代理模式。
对OP:现在这不是你的确切问题。 上述答案更有可能使大多数对代理有疑问的人受益。
在您的情况下,您说您正在尝试将HttpServletRequest
注入到@Remote
EJB中。 有一件事我甚至不知道那是或是可能的。 我的猜测是EJB引擎从不设置ThreadLocal
。 因此,当泽西试图调用它时,上下文中没有任何内容。
@Context
注释适用于JAX-RS组件,而不适用于EJB。 我个人而言,不知道如何将HttpServletRequest
注入到EJB中,因为我对EJB不起作用。 因此,任何面对您确切问题的人都需要搜索如何做到这一点 。 但就像我说的那样,我不认为大多数人都是在搜索这个问题。 我想他们只是想知道为什么请求是代理而不是实际请求。 所以这个答案更适合他们。
不一样的问题我得到适当的会话和上下文对象。这样对我来说工作正常。
public login login(request Request,@Context MessageContext context) { HttpServletRequest httpServletRequest = context .getHttpServletRequest();
}