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作品中很好的资源,注册为一个实例,所以我怎么能做出这种行为可能对我的用例?
因此,当您实例化资源以使其成为单例时,Jersey会尝试在启动时执行所有注入.这意味着,试图访问的任何对象,本质上是请求范围,将会失败...... 除非 ......对象是可代理.
有些对象可以由Jersey代理,这是设计和规范.例如HttpHeaders,UriInfo和SecurityContext,此处列出的其他一些内容.虽然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)
只有在您不需要参数注入时才会这样.
也可以看看