基于HttpRequest而没有单身的球衣2上下文注入

scl*_*sen 2 java rest dependency-injection dropwizard jersey-2.0

我想按字段为单个请求注入一个数据存储区,比如

@Context
protected HttpServletRequest request;
Run Code Online (Sandbox Code Playgroud)

目前我已经实现了类似的方法: Jersey 2.x自定义注入注释具有属性 如下:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER, ElementType.FIELD})
public @interface TenantDatastore {}
Run Code Online (Sandbox Code Playgroud)
public class TenantDatastoreFactory extends AbstractContainerRequestValueFactory<Datastore> {

    public TenantDatastoreFactory() {}

    @Override
    public Datastore provide() {
        ContainerRequest request = getContainerRequest();
        return DatastoreManager.getDs(request.getHeaders().get("Host")));
    }

    @Override
    public void dispose(Datastore d) {}
}
Run Code Online (Sandbox Code Playgroud)
public class TenantDatastoreFactoryProvider extends AbstractValueFactoryProvider {

    private final TenantDatastoreFactory tenantDatastoreFactory;

    @Inject
    public TenantDatastoreFactoryProvider(
            final MultivaluedParameterExtractorProvider extractorProvider,
            ServiceLocator locator,
            TenantDatastoreFactory tenantDatastoreFactory) {

        super(extractorProvider, locator, Parameter.Source.UNKNOWN);
        this.tenantDatastoreFactory = tenantDatastoreFactory;
    }

    @Override
    protected Factory<?> createValueFactory(Parameter parameter) {
         Class<?> paramType = parameter.getRawType();
         TenantDatastore annotation = parameter.getAnnotation(TenantDatastore.class);
         if (annotation != null && paramType.isAssignableFrom(Datastore.class)) {
             return tenantDatastoreFactory;
         }
         return null;
    }
}
Run Code Online (Sandbox Code Playgroud)
public class TenantDatastoreInjectionResolver extends ParamInjectionResolver {
    public TenantDatastoreInjectionResolver() {
        super(TenantDatastoreFactoryProvider.class);
    }
}
Run Code Online (Sandbox Code Playgroud)
@Path("/users")
public class User {
    @TenantDatastore
    private Datastore    ds;
    private ObjectMapper objectMapper;

    public User(ObjectMapper objectMapper) {
      this.objectMapper = objectMapper;
    }

    @GET
    public Response getUsers(){
      return Response.ok(ds.find(User.class).asList()).build();
    }
}
Run Code Online (Sandbox Code Playgroud)

并在dropwizard应用程序的运行方法中:

environment.jersey().register(new UserResource(objectMapper));

environment.jersey().getResourceConfig().register(new AbstractBinder(){
    @Override
    public void configure() {
        bind(TenantDatastoreFactory.class)
          .to(TenantDatastoreFactory.class)
          .in(Singleton.class);
        bind(TenantDatastoreFactoryProvider.class)
          .to(ValueFactoryProvider.class)
          .in(Singleton.class);
        bind(TenantDatastoreInjectionResolver.class)
          .to(new TypeLiteral<InjectionResolver<TenantDatastore>>(){})
          .in(Singleton.class);
    }
});
Run Code Online (Sandbox Code Playgroud)

我读过,你必须将资源注册为单例,如下所示:

environment.jersey().register(UserResource.class);
Run Code Online (Sandbox Code Playgroud)

但我必须将对象传递给构造函数,这对于单例是不可能的. javax.servlet.http.HttpServletRequest连同javax.ws.rs.core.Context作品中很好的资源,注册为一个实例,所以我怎么能做出这种行为可能对我的用例?

Pau*_*tha 8

因此,当您实例化资源以使其成为单例时,Jersey会尝试在启动时执行所有注入.这意味着,试图访问的任何对象,本质上是请求范围,将会失败...... 除非 ......对象是可代理.

有些对象可以由Jersey代理,这是设计和规范.例如HttpHeaders,UriInfoSecurityContext,此处列出的其他一些内容.虽然HttpServletRequest未列出,但它也是可代理的对象之一.

可代理的意思是,不是注入实际对象(在有请求之前不存在),而是注入代理.在代理上进行调用时,它们会转发到当前请求中可用的实际对象.您可以尝试打印/记录该类,HttpServletRequest您将看到该类实际上是com.sun.proxy.ProxyX代替HttpServletRequestSomeImpl.这是Java 工作中动态代理的神奇之处.

你目前面临的问题是注射Datastore.它本质上是请求作用域,因为它的创建依赖于请求上下文信息,即标题.因此,在注射过程中,它无法通过此调用获取ContainerRequest工厂内部

ContainerRequest request = getContainerRequest();
Run Code Online (Sandbox Code Playgroud)

错误消息是"不在请求范围内",这非常有意义,因为当我们尝试获取它时没有请求.

那我们怎么解决这个问题呢?好吧,我们需要使Datastore代理.通常,您可以通过在绑定声明期间配置它来实现此目的

bindFactory(...).proxy(true).proxyForSameScope(false).to(...);
Run Code Online (Sandbox Code Playgroud)

proxy(true)方法使其可代理,并且proxyForSameScope(false)如果我们试图注入相同的范围,它应该不是代理,而是实际的实例.

您当前配置的一个问题是您将工厂绑定到工厂

bind(TenantDatastoreFactory.class)
  .to(TenantDatastoreFactory.class)
  .in(Singleton.class);
Run Code Online (Sandbox Code Playgroud)

这对你当前的实现是有意义的,因为你正试图将工厂注入TenantDatastoreFactoryProvider.但是我们实际需要进行代理工作的是工厂与实际绑定Datastore:

bindFactory(TenantDatastoreFactory.class)
        .proxy(true)
        .proxyForSameScope(false)
        .to(Datastore.class)
        .in(RequestScoped.class);
Run Code Online (Sandbox Code Playgroud)

所以现在我们已经取出了工厂的绑定,我们无法注入它.所以我们只需Factory要从createValueFactory方法中返回a的问题.我们不想只返回TenantDatastoreFactory实例,因为我们仍然会遇到provide调用该方法获取的相同问题Datastore.为了解决这个问题,我们可以做到以下几点

@Override
protected Factory<?> createValueFactory(Parameter parameter) {
     Class<?> paramType = parameter.getRawType();
     TenantDatastore annotation = parameter.getAnnotation(TenantDatastore.class);
     if (annotation != null && paramType.isAssignableFrom(Datastore.class)) {
         return getFactory();
     }
     return null;
}

private Factory<Object> getFactory() {
    return new Factory<Object>() {

        @Context
        Datastore datastore;

        @Override
        public Object provide() {
            return datastore;
        }

        @Override
        public void dispose(Object t) {}
    };
}
Run Code Online (Sandbox Code Playgroud)

所以我们正在创建一个Factory动态的,我们注入代理Datastore.现在,当Jersey尝试注入资源类时,它将注入代理,并且provide在启动时永远不会调用该方法.它仅在我们尝试o Datastore在请求期间实际使用时调用.

看起来多余,我们将anonymous TenantDatastoreFactory anonymous都Factory创建为运行时.但这是必要的,以使Datastore代理,并确保provide()从未在启动时调用该方法.

另一个注意事项是,如果你不需要参数注入,你可以通过取出来简化实现TenantDatastoreFactoryProvider.这仅适用于参数注入.我们所需要的只是InjectionResolver处理自定义注释,以及工厂创建Datastore.该InjectionResolver实施将需要改变如下

public class TenantDatastoreInjectionResolver 
        implements InjectionResolver<TenantDatastore> {

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

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

    @Override
    public boolean isConstructorParameterIndicator() { return false; }
    @Override
    public boolean isMethodParameterIndicator() { return false; }
}
Run Code Online (Sandbox Code Playgroud)

然后在活页夹中,取出 TenantDatastoreFactoryProvider

@Override
public void configure() {
    bindFactory(TenantDatastoreFactory.class)
            .proxy(true)
            .proxyForSameScope(false)
            .to(Datastore.class)
            .in(RequestScoped.class);
    bind(TenantDatastoreInjectionResolver.class)
            .to(new TypeLiteral<InjectionResolver<TenantDatastore>>() {
            })
            .in(Singleton.class);
}
Run Code Online (Sandbox Code Playgroud)

只有在您不需要参数注入时才会这样.

也可以看看