球衣2上下文注入基于HttpRequest没有单例 [英] jersey 2 context injection based upon HttpRequest without singleton

查看:164
本文介绍了球衣2上下文注入基于HttpRequest没有单例的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

  @Context 
保护HttpServletRequest请求;

目前我已经实现了类似的方法:
用户界面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();
返回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定位器,
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&&&¶mType.isAssignableFrom(Datastore.class)){
return tenantDatastoreFactory;
}
返回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 非常工作在一个资源中,注册为一个实例,那么如何使我的用户可以使这个行为成为可能?

解决方案

所以当你实例化资源使其成为单身人士,泽西岛尝试在启动时执行所有注入。这意味着尝试访问本质上要求作用域的任何对象将失败... 无关 ...对象是可以



有些对象是由泽西做出的,这是设计和规格。例如 HttpHeaders UriInfo SecurityContext 其他人则列出了此处。虽然没有列出 HttpServletRequest ,但它也是可以预见的对象之一。



proxiable是代替注入实际的对象(不存在,直到有请求),注入代理。当在代理上进行呼叫时,它们将转发到当前请求中可用的实际对象。您可以尝试打印/记录 HttpServletRequest 的类,您将看到该类实际上是 com.sun.proxy.ProxyX 而不是 HttpServletRequestSomeImpl 。这是Java的动态代理的魔力



您目前遇到的问题是注入 Datastore 。它本质上是请求范围的,因为它是依赖于请求上下文信息,即头部的创建。所以在注入期间,这个调用失败,在你的工厂内获得 ContainerRequest

  ContainerRequest request = getContainerRequest(); 

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



那么我们如何解决这个问题?那么我们需要使 Datastore 可以自由。
通常,您可以通过在绑定声明期间进行配置,例如

  bindFactory(..) 。)代理(真).proxyForSameScope(假)。为了(...); 

proxy(true) proxiable,而 proxyForSameScope(false)表示如果我们试图注入到同一个范围内,它不应该是一个代理,而是实际的实例。



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

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

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

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

所以现在我们已经取消了工厂的绑定,我们不能注入。所以我们只需要从 createValueFactory 方法返回一个 Factory 的问题。我们不想只返回 TenantDatastoreFactory 实例,因为我们仍然面临同样的问题,其中提供方法是要求获取 Datastore 。为了解决这个问题,我们可以执行以下

  @Override 
protected Factory<?> createValueFactory(Parameter parameter){
Class<?> paramType = parameter.getRawType();
TenantDatastore annotation = parameter.getAnnotation(TenantDatastore.class);
if(annotation!= null&&&¶mType.isAssignableFrom(Datastore.class)){
return getFactory();
}
返回null;
}

private Factory< Object> getFactory(){
return new Factory< Object>(){

@Context
Datastore datastore;

@Override
public Object provide(){
return datastore;
}

@Override
public void dispose(Object t){}
};
}

所以我们创建一个工厂动态地注入代理的 Datastore 。现在当泽西试图注入资源类时,它将注入代理,并且$ code>提供方法从未在启动时调用。只有当我们尝试在实际使用请求期间使用 Datastore 时才会调用。



似乎有多余的我们同时具有作为运行时创建的匿名 Factory TenantDatastoreFactory 。但这是必要的,使 Datastore proxiable,并确保$ code> provide()方法从未在启动时调用。另一个注意事项是,如果不需要参数注入,可以通过取出 TenantDatastoreFactoryProvider 。这只适用于参数注入。我们需要的只是 InjectionResolver 来处理自定义注释,工厂要创建 Datastore InjectionResolver 实现需要更改如下

  public class TenantDatastoreInjectionResolver 
实现了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);
}
返回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);
}

只有在不需要参数注入的情况下,这才是。 >

另请参阅




I want to inject a Datastore for a single request by field, like

@Context
protected HttpServletRequest request;

Currently I have implemented a similar approach to this: Jersey 2.x Custom Injection Annotation With Attributes as follows:

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

And in the run method of the dropwizard application:

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

I read, that you have to register the resource as a singleton, like this:

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

but I have to pass objects to the constructor, which isn't possible with a singleton. javax.servlet.http.HttpServletRequest along with javax.ws.rs.core.Context works very well in a resource, registered as an instance, so how can I make this behavior possible for my usecase?

解决方案

So when you instantiate the resource to make it a singleton, Jersey tries to do all the injection on startup. This means that attempt to access any object that is inherently request scoped, will fail... UNLESS... the object is proxiable.

Some objects are made proxiable by Jersey, and this is by design, and by specification. For example HttpHeaders, UriInfo, SecurityContext, and a few others listed here. Though HttpServletRequest is not listed, it is also one of the objects that are proxiable.

What it means to be proxiable is that instead of injecting the actual object (which doesn't exist until there is request), a proxy is injected. When calls are made on the proxy, they get forwarded to the actual object that is available in the current request. You can try to print/log the class of the HttpServletRequest and you will see that the class is actually com.sun.proxy.ProxyX instead of HttpServletRequestSomeImpl. This is the magic of Java's dynamic proxies at work.

The current problem you are facing is the injection of Datastore. It is inherently request scoped because it's creation in dependent on request context information, i.e. the headers. So during injection, it fails on this call to obtain the ContainerRequest inside your factory

ContainerRequest request = getContainerRequest();

The error message being "Not inside a request scope", which makes perfect sense as there is no request when we try to obtain it.

So how can we fix this? Well we need to make the Datastore proxiable. Generally, the way you can do this is by configuring it during binding declaration, for example

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

The proxy(true) method makes it proxiable, and the proxyForSameScope(false) says that if we are trying to inject into the same scope, it should not be a proxy, but the actual instance.

One problem with your current configuration is that you are binding the factory to the factory

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

This makes sense for your current implementation, as you are trying to inject the factory into the TenantDatastoreFactoryProvider. But what we actually need to make the proxying work, is for the factory to be binded to the actual Datastore:

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

So now we have taken out the binding of the factory, we can't inject it. So we just have to problem of returning a Factory from the createValueFactory method. We don't want to just return TenantDatastoreFactory instance because we'll still face the same problem where the provide method is called to get the Datastore. To get around this, we can do the following

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

So we are creating a Factory dynamically, where we inject the proxied Datastore. Now when Jersey tries to inject the resource class, it will inject the proxy, and the provide method is never called on start up. It is only called when we try o actually use the Datastore during the request.

It may seem redundant, that we have both the TenantDatastoreFactory and the anonymous Factory created as runtime. But this is necessary to make the Datastore proxiable and make sure the provide() method is never called on startup.

Another note, is that if you don't require parameter injection, you could've simplified the implementation by taking out the TenantDatastoreFactoryProvider. This is only required for parameter injection. All we need is the InjectionResolver to handle the custom annotation, and the factory to create the Datastore. The InjectionResolver implementation would need to be changed as follows

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

Then in the binder, just take out the 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);
}

Again this is only if you don't require parameter injection.

See Also

这篇关于球衣2上下文注入基于HttpRequest没有单例的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆