基于没有单例的 HttpRequest 的 jersey 2 上下文注入

jersey 2 context injection based upon HttpRequest without singleton

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

@Context
protected HttpServletRequest request;

目前我已经实现了类似的方法: 如下:

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

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

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

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

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

一些 objects 由 Jersey 代理,这是设计和规范。例如 HttpHeadersUriInfoSecurityContext 和其他一些 here。尽管未列出 HttpServletRequest,但它也是可代理的 object 之一。

可代理的意思是,不是注入实际的 object(在有请求之前不存在),而是注入代理。在代理上进行调用时,它们会被转发到当前请求中可用的实际 object。您可以尝试 print/log HttpServletRequest 的 class,您会发现 class 实际上是 com.sun.proxy.ProxyX 而不是 HttpServletRequestSomeImpl。这就是Java的dynamic proxies的魔力在起作用。

您当前面临的问题是Datastore的注入。它本质上是请求范围的,因为它的创建依赖于请求上下文信息,即 headers。因此,在注入期间,此调用无法在您的工厂

中获取 ContainerRequest
ContainerRequest request = getContainerRequest();

错误消息是"Not inside a request scope",这很合理,因为当我们尝试获取它时没有请求。

那么我们该如何解决这个问题呢?好吧,我们需要使 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 方法 returning 一个 Factory 的问题。我们不想只是 return 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 尝试注入资源 class 时,它会注入代理,并且 provide 方法在启动时永远不会被调用。它仅在我们尝试在请求期间实际使用 Datastore 时调用。

这似乎是多余的,我们同时创建了 TenantDatastoreFactory 匿名 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; }
}

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

同样,这仅适用于不需要参数注入的情况。

另请参阅