6

我在理解为什么@Context依赖注入返回 $Proxy(random number) 实例而不是 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); 

当我尝试在输入后调试该方法时,我可以看到全局请求和响应对象为空,而本地方法实例为 $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());

我在这里想念什么?有没有人遇到过类似的问题?

格雷格

4

2 回答 2

9

要理解这个问题,您需要了解作用域的工作原理。为了描述发生了什么,这里有一个例子。假设你有这个

@Singleton
@Path("..")
public class SomeResource {

    @Context
    private HttpServletRequest request;
}

这种情况带来的问题是,HttpServletRequest每次请求都会生成一个,但是资源类只创建一次,因为它是一个单例,所以最初HttpServletRequest创建时没有注入单例。

为了解决这个问题,Jersey 注入了一个代理。所以从概念上讲,结果更像

@Singleton
@Path("..")
public class SomeResource {

    @Context
    private ProxyHttpServletRequest proxyRequest;
}

当请求进来时,实际HttpServletRequest被放入ThreadLocal一个范围 context 中。这不是一个精确的实现,但您可以将范围上下文想象为

public class RequestScopeContext {
    private static final ThreadLocal<HttpServletReqest> request
            = new ThreadLocal<>();

    public static HttpServletRequest get() { .. }
    public static void setRequest(HttpServletRequest request) { .. }
}

当请求进入时,HttpServletRequest被设置到上下文中。HttpServletRequest当对内部进行调用时SomeResource,它实际上是在代理对象上进行的,该对象从 中获取请求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:现在这不是您的确切问题。上述答案更有可能使大多数对代理有疑问的人受益。

在您的情况下,您说您正在尝试将其注入HttpServletRequestEJB @Remote。一方面,我什至不知道那可能的。我的猜测是 EJB 引擎从不设置ThreadLocal. 因此,当泽西试图调用它时,上下文中没有任何内容。

@Context注释适用于 JAX-RS 组件,而不适用于 EJB 。我个人不知道如何将 anHttpServletRequest注入 EJB,因为我对 EJB 的工作不多。因此,任何面临您确切问题的人都需要搜索如何做到这一点。但就像我说的,我不认为这是搜索这个问题的大多数人。我想他们只是想知道为什么请求是代理而不是实际请求。所以这个答案更适合他们。

于 2015-10-04T18:57:53.077 回答
0

我得到正确的会话和上下文对象不是同一个问题。就像这对我来说很好。

   public login login(request Request,@Context MessageContext context) {

                 HttpServletRequest httpServletRequest = context
                              .getHttpServletRequest();

}

于 2015-10-04T17:58:05.717 回答