@Context返回代理而不是HttpServletRequest(代理范围内没有线程本地值)

时间:2012-11-02 15:43:03

标签: java dependency-injection jersey glassfish-3

我在理解为什么@Context依赖注入返回$ 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());

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

格雷格

2 个答案:

答案 0 :(得分:6)

要了解此问题,您需要了解范围的工作原理。为了描述会发生什么,这是一个例子。假设你有这个

@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<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:现在这不是您的确切问题。上述答案更有可能使大多数对代理有疑问的人受益。

在您的情况下,您说您正在尝试将HttpServletRequest注入@Remote EJB。有一件事我甚至不知道那是或可能的。我的猜测是EJB引擎永远不会设置ThreadLocal。因此,当泽西岛试图调用它时,在上下文中没有任何内容。

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

答案 1 :(得分:0)

不一样的问题我得到适当的会话和上下文对象。就像这对我工作正常。

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

                 HttpServletRequest httpServletRequest = context
                              .getHttpServletRequest();

}