我想按字段为单个请求注入数据存储,如
@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.HttpServletRequest
和javax.ws.rs.core.Context
在注册为实例的资源中工作得很好,那么我如何使这种行为在我的用例中成为可能呢?
因此,当您实例化资源以使其成为单例时,Jersey会尝试在启动时进行所有注入。这意味着尝试访问任何固有请求范围的对象都将失败除非。。。对象是可代理。
一些对象是由Jersey代理的,这是通过设计和规范实现的。例如,此处列出的HttpHeaders
、UriInfo
、SecurityContext
和其他一些。虽然未列出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
来处理自定义注释,以及工厂来创建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);
}
同样,这只是在不需要参数注入的情况下。
另请参见
- 使用HK2和Jersey将请求作用域对象注入Singleton作用域对象