基于没有单例的 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.HttpServletRequest
和 javax.ws.rs.core.Context
在注册为实例的资源中工作得很好,那么我怎样才能使我的用例能够实现这种行为?
因此,当您实例化资源以使其成为单例时,Jersey 会尝试在启动时进行所有注入。这意味着尝试访问任何固有请求范围的 object 将失败... UNLESS... object 是 proxiable .
一些 objects 由 Jersey 代理,这是设计和规范。例如 HttpHeaders
、UriInfo
、SecurityContext
和其他一些 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);
}
同样,这仅适用于不需要参数注入的情况。
另请参阅
我想按字段为单个请求注入数据存储,例如
@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.HttpServletRequest
和 javax.ws.rs.core.Context
在注册为实例的资源中工作得很好,那么我怎样才能使我的用例能够实现这种行为?
因此,当您实例化资源以使其成为单例时,Jersey 会尝试在启动时进行所有注入。这意味着尝试访问任何固有请求范围的 object 将失败... UNLESS... object 是 proxiable .
一些 objects 由 Jersey 代理,这是设计和规范。例如 HttpHeaders
、UriInfo
、SecurityContext
和其他一些 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);
}
同样,这仅适用于不需要参数注入的情况。
另请参阅