【问题标题】:Dropwizard + Jersey : "Not inside a request scope" when creating custom annotationDropwizard + Jersey:创建自定义注释时“不在请求范围内”
【发布时间】:2015-06-03 19:46:04
【问题描述】:

我有一个简单的 Dropwizard 0.8.1 REST 服务,它引入了 Jersey 2.17。在 REST/Jetty 服务的上游,我有一些身份验证服务,可以向传递给我的 Dropwizard 应用程序的 HTTP 标头添加一些不错的授权信息。

我希望 喜欢 能够在我的 Resource 中创建一个自定义注释,以隐藏所有混乱的 header-parsing-to-POJO 垃圾。像这样的:

 @Path("/v1/task")
 @Produces(MediaType.APPLICATION_JSON)
 @Consumes(MediaType.APPLICATION_JSON)
 public class TaskResource {

      @UserContext                               // <-- custom/magic annotation
      private UserContextData userContextData;   // <-- holds all authorization info

      @GET
      public Collection<Task> fetch() {
           // use the userContextData to differentiate what data to return
      }

我花了最后一天环顾 stackoverflow,发现其他几个人有同样的问题并出现(?)以获得一些满足感,但我似乎无法避免得到“不在请求范围内”尝试执行此操作时的堆栈跟踪。

所以我隐藏了所有更改并尝试直接实现泽西文档第 22.1 和 22.2 节中提供的示例:https://jersey.java.net/documentation/2.17/ioc.html

按照他们的示例(但在我的 Dropwizard 应用程序中),我试图在我的资源中获取“@SessionInject”注释,但它也每次都会出现“不在请求范围内”堆栈跟踪。我在这里做错了什么?

资源:

  @Path("/v1/task")
  @Produces(MediaType.APPLICATION_JSON)
  @Consumes(MediaType.APPLICATION_JSON)
  public class TaskResource {

       private final TaskDAO taskDAO;

       @Context
       private HttpServletRequest httpRequest;

       @SessionInject
       private HttpSession httpSession;

       public TaskResource(TaskDAO taskDAO) {
           this.taskDAO = taskDAO;
       }

       @GET
       public Collection<Task> fetch(@SessionInject HttpSession httpSession) {              
           if (httpSession != null) {
                logger.info("TOM TOM TOM httpSession isn't null: {}", httpSession);
           }
           else {
                logger.error("TOM TOM TOM httpSession is null");
           }
           return taskDAO.findAllTasks();
       }

SessionInjectResolver:

package com.foo.admiral.integration.jersey;

import com.foo.admiral.integration.core.SessionInject;
import javax.inject.Inject;
import javax.inject.Named;

import javax.servlet.http.HttpSession;
import org.glassfish.hk2.api.Injectee;

import org.glassfish.hk2.api.InjectionResolver;
import org.glassfish.hk2.api.ServiceHandle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class SessionInjectResolver implements InjectionResolver<SessionInject> {

    private static final Logger logger = LoggerFactory.getLogger(HttpSessionFactory.class);

    @Inject
    @Named(InjectionResolver.SYSTEM_RESOLVER_NAME)
    InjectionResolver<Inject> systemInjectionResolver;

    @Override
    public Object resolve(Injectee injectee, ServiceHandle<?> handle) {
        if (HttpSession.class == injectee.getRequiredType()) {
            return systemInjectionResolver.resolve(injectee, handle);
        }

        return null;
    }

    @Override
    public boolean isConstructorParameterIndicator() {
        return false;
    }

    @Override
    public boolean isMethodParameterIndicator() {
        return false;
    }
}

HttpSessionFactory:

package com.foo.admiral.integration.jersey;

import javax.inject.Inject;
import javax.inject.Singleton;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.glassfish.hk2.api.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Singleton
public class HttpSessionFactory implements Factory<HttpSession> {

    private static final Logger logger = LoggerFactory.getLogger(HttpSessionFactory.class);
    private final HttpServletRequest request;

    @Inject
    public HttpSessionFactory(HttpServletRequest request) {
        logger.info("Creating new HttpSessionFactory with request");
        this.request = request;
    }

    @Override
    public HttpSession provide() {
        logger.info("Providing a new session if one does not exist");
        return request.getSession(true);
    }

    @Override
    public void dispose(HttpSession t) {
    }
}

注释:

package com.foo.admiral.integration.core;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface SessionInject {
}

最后是 Dropwizard Application 类中的绑定:

@Override
public void run(TodoConfiguration configuration, Environment environment) throws Exception {
    ...

    environment.jersey().register(new AbstractBinder() {
        @Override
        protected void configure() {
            bindFactory(HttpSessionFactory.class).to(HttpSession.class);

            bind(SessionInjectResolver.class)
                    .to(new TypeLiteral<InjectionResolver<SessionInject>>() { })
                    .in(Singleton.class);
        }
    });

旧堆栈跟踪:

Caused by: java.lang.IllegalStateException: Not inside a request scope.
at jersey.repackaged.com.google.common.base.Preconditions.checkState(Preconditions.java:149)
at org.glassfish.jersey.process.internal.RequestScope.current(RequestScope.java:233)
at org.glassfish.jersey.process.internal.RequestScope.findOrCreate(RequestScope.java:158)
at org.jvnet.hk2.internal.MethodInterceptorImpl.invoke(MethodInterceptorImpl.java:74)
at org.jvnet.hk2.internal.MethodInterceptorInvocationHandler.invoke(MethodInterceptorInvocationHandler.java:62)
at com.sun.proxy.$Proxy72.getSession(Unknown Source)
at com.foo.admiral.integration.jersey.HttpSessionFactory.provide(HttpSessionFactory.java:29)
at com.foo.admiral.integration.jersey.HttpSessionFactory.provide(HttpSessionFactory.java:14)

一些可能有用的线索:

1) 我注意到我的 HttpSessionFactory 中的日志记录语句永远不会被触发,所以我认为工厂没有被正确识别到 DropWizard。

2) 如果我将注释更改为 Parameter 而不是 Field 并将注释的使用移动到 fetch( ) 方法签名中,它不会抛出堆栈跟踪(但 httpSession 仍然为 null ,大概是因为工厂没有开火……)

 public Collection<Task> fetch(@SessionInject HttpSession httpSession) {

3) 如果我用 environment.jersey().register() 或 environment.jersey().getResourceConfig().register()“注册”活页夹似乎并不重要......他们似乎这样做同样的事情。

您是否发现任何明显的问题?提前致谢!

【问题讨论】:

  • 我希望能够在我的资源中创建一个自定义注释来隐藏所有混乱的 header-parsing-to-POJO 垃圾你是什么意思?这个凌乱的头文件解析到 POJO 垃圾是什么?日志中是否有消息(或消息组)或类似内容?
  • 我的意思是,有一个带有大 JSON blob 的 HTTP 标头,几乎每个资源和端点都需要它。我宁愿不将@HttpHeader 参数传递给每个端点方法签名,我宁愿在“幕后”进行所有 Header-JSON-to-POJO 解析。
  • 您是如何将TaskResource 注册为实例或.class 的?

标签: java annotations jersey dropwizard


【解决方案1】:

这是一种奇怪的行为。但是看起来正在发生的是以下情况

  1. 您已将TaskResource 注册为实例,而不是.class。这一点我很确定(尽管你没有提到)。

    register(new TaskResource()); 
    /* instead of */ 
    register(TaskResource.class);
    

    做前者,它将资源设置在单例范围内。后者在请求范围内(除非另有注释 - 见下文)

  2. 在加载资源模型时,它会看到 TaskResource 是单例,并且 HttpServletRequest 在请求范围内。无论是那个还是工厂都在每个请求的范围内。我猜是两者之一。

我认为它实际上可能是 一个范围问题,如错误消息中所述,但我很确定在运行时,它将由线程本地代理处理,因为范围较小。

您可以通过将TaskResource 注册为一个类,然后用@Singleton 注释TaskResource 来修复它。这是如果您确实确实希望资源类是单例的。如果没有,那么请不要使用@Singleton

对我来说奇怪的是,当资源在启动时显式实例化时它在启动时失败,但在框架在第一个请求上加载时工作(当您将其注册为类时会发生这种情况) .它们都仍在单例范围内。

您可能需要考虑的一件事是您是否真的希望资源成为单例。您确实必须担心单例的线程安全问题,并且还有一些其他限制。就个人而言,我更喜欢将它们保留在请求范围内。您必须进行一些性能测试,看看是否会对您的应用程序产生很大影响。

更新

对于参数注入,你可能想看看this post

更新 2

另见

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-02-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-24
    • 2010-10-03
    • 1970-01-01
    相关资源
    最近更新 更多