Dropwizard + Jersey:"不在请求范围内"在创建自定义注释时

时间:2015-06-03 19:46:04

标签: java annotations jersey dropwizard

我有一个简单的Dropwizard 0.8.1 REST服务,可以使用Jersey 2.17。在REST / Jetty服务的上游,我有一些身份验证服务,它将一些很好的授权信息添加到传递给我的Dropwizard应用程序的HTTP Header。

我会喜欢能够在我的资源中创建一个自定义注释,隐藏所有杂乱的头部解析到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,发现其他几个有同样问题的人出现了(?)以获得一些满足感,但我似乎无法避免得到一个&#34 ;不在请求范围内&#34;当我尝试这样做时,堆栈跟踪。

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

继他们的示例(但在我的Dropwizard应用程序中)之后,我试图获得一个&#34; @ SessionInject&#34;我的资源中的注释,但它也会因为#34;而不是在请求范围内#34;每次堆栈跟踪。我在这里做错了什么?

资源:

  @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中的日志记录语句永远不会被触发,所以我不认为Factory正确识别DropWizard。

2)如果我将注释更改为参数而不是字段并将注释的使用移动到这样的fetch()方法签名中,它不会抛出堆栈跟踪(但是httpSession是仍然无效,大概是因为工厂没有开火......)

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

3)如果我注册&#34;它似乎并不重要。使用environment.jersey()。register()或environment.jersey()。getResourceConfig()。register()...它们似乎做同样的事情。

你看到任何明显的问题吗?提前谢谢!

1 个答案:

答案 0 :(得分:2)

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

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

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

    执行前者,它将资源设置为单例范围。后者在请求范围内(除非另有说明 - 见下文)

  2. 加载资源模型时,它会看到TaskResource是单例,HttpServletRequest位于请求范围内。工厂在每个请求范围内的那个或那个。我猜其中一个。

  3. 我认为它实际上可能范围问题,如错误消息中所述,但我非常确定的是,在运行时,它将通过线程本地代理处理,因为范围较小。

    您可以通过将TaskResource注册为类,然后使用TaskResource注释@Singleton来修复此问题。这是因为你实际上希望资源类是一个单例。如果没有,那么就离开@Singleton

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

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

    更新

    对于参数注入,您可能需要查看this post

    更新2

    另见