我有点难以理解为什么 @Context
依赖注入(inject)返回 $Proxy(random number) 实例集合而不是 HttpServletRequest 或 HttpServletResponse。
我正在使用 Glassfish 3.1.2.2 及其版本的 Jersey(Jersey:1.11.1),我的应用程序构建为 EAR 应用程序。
我有一个简单的@Remote 接口(interface),我在其中注释我的方法和 REST 服务工作没有任何问题,但是当我尝试访问 HttpServletRequest 信息时它只会导致问题。
我在我的 session bean 中注释了私有(private)字段:
@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);
当我在一个条目之后尝试调试该方法时,我可以看到全局请求和响应对象为空,而本地方法实例为 $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
在单例创建时注入(inject)单例。
为了解决这个问题,Jersey 注入(inject)了一个代理。所以从概念上讲,结果更像是
@Singleton
@Path("..")
public class SomeResource {
@Context
private ProxyHttpServletRequest proxyRequest;
}
当请求进来时,实际的 HttpServletRequest
被放入 scope context 的 ThreadLocal
中。这不是一个精确的实现,但您可以将范围上下文想象成类似
public class RequestScopeContext {
private static final ThreadLocal<HttpServletReqest> 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) {
...
}
}
与此不同的是请求不再注入(inject)到字段中,因此不需要代理。 HttpServletRequest
仅在调用该方法时才需要。所以 Jersey 将注入(inject)实际请求而不是代理。
请注意,此模式并不仅仅针对 Jersey。任何涉及 DI 和作用域的框架在尝试将较小作用域对象注入(inject)较宽作用域对象时都会使用类似的代理模式。
致 OP: 现在这不是您的确切问题。上面的回答更可能让大多数对代理有疑问的人受益。
在您的例子中,您说您正在尝试将 HttpServletRequest
注入(inject)到 @Remote
EJB 中。一方面,我什至不知道那是或是可能的。我的猜测是 EJB 引擎从不设置 ThreadLocal
。因此,当 Jersey 尝试调用它时,上下文中没有任何内容。
@Context
注释适用于 JAX-RS 组件,不适用于 EJB。我个人不知道如何将 HttpServletRequest
注入(inject)到 EJB 中,因为我对 EJB 的工作不多。因此,任何面临您确切问题的人都需要搜索如何那个。但就像我说的,我认为搜索这个问题的人不是大多数。我想他们只是想知道为什么请求是代理而不是实际请求。所以这个答案更适合他们。
关于java - @Context 返回代理而不是 HttpServletRequest(代理范围内没有线程本地值),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13198550/