基于HttpRequest的jersey 2上下文注入,没有singleton



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

@Context
protected HttpServletRequest request;

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

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER, ElementType.FIELD})
public @interface TenantDatastore {}
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) {}
}
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;
    }
}
public class TenantDatastoreInjectionResolver extends ParamInjectionResolver {
    public TenantDatastoreInjectionResolver() {
        super(TenantDatastoreFactoryProvider.class);
    }
}
@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();
    }
}

在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);
    }
});

我读到,您必须将资源注册为singleton,如下所示:

environment.jersey().register(UserResource.class);

但是我必须将对象传递给构造函数,这对于singleton是不可能的。javax.servlet.http.HttpServletRequestjavax.ws.rs.core.Context在注册为实例的资源中工作得很好,那么我如何使这种行为在我的用例中成为可能呢?

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

一些对象是由Jersey代理的,这是通过设计和规范实现的。例如,此处列出的HttpHeadersUriInfoSecurityContext和其他一些。虽然未列出HttpServletRequest,但它也是可代理的对象之一。

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

您目前面临的问题是Datastore的注入。它本质上是请求范围的,因为它的创建依赖于请求上下文信息,即标头。因此,在注入过程中,它无法在该调用中获得工厂内的ContainerRequest

ContainerRequest request = getContainerRequest();

错误消息是"不在请求范围内",这是完全合理的,因为当我们试图获得它时没有请求。

那么我们该如何解决这个问题呢?我们需要使Datastore成为代理。通常,您可以通过在绑定声明期间配置它来实现这一点,例如

bindFactory(...).proxy(true).proxyForSameScope(false).to(...);

proxy(true)方法使其可代理,而proxyForSameScope(false)表示,如果我们试图注入到同一范围中,则它不应该是代理,而应该是实际实例。

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

bind(TenantDatastoreFactory.class)
  .to(TenantDatastoreFactory.class)
  .in(Singleton.class);

这对于您当前的实现是有意义的,因为您正试图将工厂注入到TenantDatastoreFactoryProvider中。但我们实际上需要使代理工作,是将工厂绑定到实际的Datastore:

bindFactory(TenantDatastoreFactory.class)
        .proxy(true)
        .proxyForSameScope(false)
        .to(Datastore.class)
        .in(RequestScoped.class);

所以现在我们已经取出了工厂的绑定,我们不能注入它。所以我们只需要从createValueFactory方法返回一个Factory的问题。我们不想只返回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) {}
    };
}

因此,我们正在动态创建一个Factory,在其中注入代理的Datastore。现在,当Jersey尝试注入资源类时,它将注入代理,并且在启动时永远不会调用provide方法。只有当我们在请求过程中尝试实际使用Datastore时,才会调用它。

我们将TenantDatastoreFactory匿名Factory都创建为运行时,这似乎是多余的。但这对于使Datastore具有代理性并确保provide()方法在启动时永远不会被调用是必要的。

另一个注意事项是,如果不需要参数注入,可以通过去掉TenantDatastoreFactoryProvider来简化实现。这仅用于参数注入。我们所需要的只是InjectionResolver来处理自定义注释,以及工厂来创建DatastoreInjectionResolver的实现需要更改如下

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; }
}

然后在活页夹中,取出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);
}

同样,这只是在不需要参数注入的情况下。

另请参见

  • 使用HK2和Jersey将请求作用域对象注入Singleton作用域对象

最新更新