当前位置: 首页 > 面试题库 >

基于HttpRequest的jersey 2上下文注入,不带单例

孟健
2023-03-14
问题内容

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

@Context
protected HttpServletRequest request;

当前,我已经对此实现了类似的方法: 带有属性的Jersey2.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应用程序的run方法中:

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会尝试在启动时进行所有注入。这意味着尝试访问本质上属于请求范围的任何对象都将失败… 除非
…该对象是 可代理的

泽西岛(Jersey)使某些对象成为代理对象,这是根据设计和规范进行的。例如HttpHeadersUriInfoSecurityContext,和其他一些上市这里。尽管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);

因此,现在我们已经取消了工厂的绑定,我们无法注入它。因此,我们只需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) {}
    };
}

因此,我们正在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将请求范围对象注入到单例范围对象中


 类似资料:
  • 我正在使用Spring 4.0.1,希望使用enum实现一个singleton。我的单例对象需要我在属性文件中定义的服务器名。如何将这个值注入到singleton对象中? 每个服务器名称将有一个单例对象。 请帮忙

  • 那么如何解决这个问题呢?为什么为空。

  • 问题内容: 有没有办法使此代码有效? LogonControl.java AuditHandler.java Endgame是,每次调用login()时,也会调用带有适当的audittype的audit()。 我想AOP可能是解决这个问题的方法,但是我希望它尽可能简单(我看过的AspectJ教程通常都有非常复杂的注释)。 注意:我不需要预先定义调用audit的方法,我正在为可扩展的框架编写它,而其

  • 我目前正在考虑处理OpenGL上下文重新创建。 我使用GLFW库创建OpenGL上下文,GL3W用于扩展加载器。 我的问题很简单:在重建上下文时,我是否应该考虑再次调用gl3wInit()?

  • PrimeFaces 4.0、Glassfish 3.1.2、Mojarra 2.1.6 我创建了一个p:dataTable和一个对dataTable的contextMenu引用。dataTable的第一列包含p:commandLink。问题是,如果我用鼠标右键单击commandLink,则该行不会被选中。如何选择实际行?我用Javascript尝试了一下,但没有结果。 我想,我应该使用Prime

  • 我不能为我的生活,弄清楚为什么这将不调用用户输入正确?用户应该给出一个范围的上限和下限,然后当他们提交时,他们会得到一个介于这两个数字之间的随机数。我假设问题是我调用“.value”的时候,但我不知道有什么其他的方法,所以任何帮助都是非常感谢的。