【问题标题】:Jersey 2.x Custom Injection Annotation With AttributesJersey 2.x 带有属性的自定义注入注解
【发布时间】:2015-05-22 13:25:58
【问题描述】:

我正在从 DropWizard 0.7.1 迁移到 0.8.1。这包括从 Jersey 1.x 迁移到 2.x。在我的 使用 Jersey 1.18.1 的实现,我有一个实现InjectableProviderMyProvider(为简单起见更改了所有类名)。这 类将创建 MyInjectable 对象,其中包含自定义注入注释 MyTokenMyToken 包含各种属性 由MyInjectable 传递和读取。最后,在Application 类中,我注册了一个MyProvider 的新实例,如下所示。

我已经进行了一些研究,但似乎无法完全理解如何在 Jersey 2.x 中重新创建(或替换,我想)这样的场景。

这是当前的 1.18.1 实现:

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ ElementType.PARAMETER, ElementType.FIELD })
    public @interface MyToken {

        // Custom annotation containing various attributes
        boolean someAttribute() default true;
        // ...
    }

    public class MyProvider implements InjectableProvider<MyToken, Parameter> {

        // io.dropwizard.auth.Authenticator
        private final Authenticator<String, MyObject> authenticator;

        public MyProvider(Authenticator<String, MyObject> authenticator) {
            this.authenticator = authenticator;
        }

        @Override
        public ComponentScope getScope() {
            return ComponentScope.PerRequest;
        }

        @Override
        public Injectable<?> getInjectable(ComponentContext ic, MyToken t, Parameter p) {
            return new MyInjectable(authenticator, t.someAttribute());      
        }
    }

    class MyInjectable extends AbstractHttpContextInjectable<MyObject> {
        private final Authenticator<String, Session> authenticator;
        private final boolean someAttribute;

        public MyInjectable(Authenticator<String, MyObject> authenticator, boolean someAttribute) {
            this.authenticator = authenticator;
            this.someAttribute = someAttribute;
            // ... Removed a few paramters for simplicity's sake
        }

        @Override
        public MyObject getValue(HttpContext c) {
            final HttpRequestContext request = c.getRequest();
            // ... Removed code not pertaining to the question
            return myObject;
        }
    }

// Lastly, the register call in the io.dropwizard.Application class
environment.jersey().register(new MyProvider(new MyProviderValidator(someValidator)));

【问题讨论】:

    标签: java jersey jersey-2.0 dropwizard hk2


    【解决方案1】:

    是的,Jersey 在 2.x 中使自定义注入的创建变得更加复杂。您需要了解 Jersey 2.x 中自定义注入的几个主要组件

    您可以在Custom Injection and Lifecycle Management 中阅读有关自定义注入的更多信息。文档的一个缺点是缺乏对如何注入参数值的解释。您可以简单地实现InjectResolver,并且可以使用自定义注释注入字段,但是为了注入方法参数,我们需要ValueFactoryProvider

    幸运的是,我们可以扩展一些抽象类(文档也没有提及),这将使生活变得更轻松。我必须在source code of the org.glassfish.jersey.server.internal.inject package 上搜索一下才能弄清楚。

    这是一个完整的示例,可帮助您入门。

    Token(可注入对象)

    public class Token {
        private final String token;
        public Token(String token) { this.token = token; }
        public String getToken() { return token; }
    }
    

    @TokenParam(我们的注入注解)

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.PARAMETER, ElementType.FIELD})
    public @interface TokenParam {
        boolean someAttribute() default true;
    }
    

    TokenFactory(根据第一个要点实现Factory,但我们只是扩展AbstractContainerRequestValueFactory。在那里我们可以访问ContainerRequestContext。注意,所有这些HK2组件,我们可以在其中注入其他依赖,例如TokenAuthenticator,我们稍后会绑定到HK2。

    import javax.inject.Inject;
    import javax.ws.rs.WebApplicationException;
    import javax.ws.rs.core.HttpHeaders;
    import javax.ws.rs.core.Response;
    import org.glassfish.jersey.server.internal.inject.AbstractContainerRequestValueFactory;
    
    public class TokenFactory extends AbstractContainerRequestValueFactory<Token> {
        
        private final TokenAuthenticator tokenAuthenticator;
        
        @Inject
        public TokenFactory(TokenAuthenticator tokenAuthenticator) {
            this.tokenAuthenticator = tokenAuthenticator;
        }
        
        @Override
        public Token provide() {
            String auth = getContainerRequest().getHeaderString(HttpHeaders.AUTHORIZATION);
            try {
                if (tokenAuthenticator.authenticate(auth).get() == null) {
                    throw new WebApplicationException(Response.Status.FORBIDDEN);
                }
            } catch (AuthenticationException ex) {
                Logger.getLogger(TokenFactory.class.getName()).log(Level.SEVERE, null, ex);
            }
            
            return new Token("New Token");
        }  
    }
    

    TokenParamInjectionResolver(每个要点二实现InjectResolver。我只是扩展ParamInjectionResolver。如果您对幕后发生的事情感兴趣,您可以在源代码中找到该课程我链接到的代码)

    import org.glassfish.jersey.server.internal.inject.ParamInjectionResolver;
    
    public class TokenParamInjectionResolver extends ParamInjectionResolver {
        public TokenParamInjectionResolver() {
            super(TokenFactoryProvider.class);
        }
    }
    

    TokenFactoryProvider(根据第三个要点实现ValueFactoryProvider。我只是扩展AbstractValueFactoryProvider。同样,您可以查看源代码以了解底层细节)

    import javax.inject.Inject;
    import org.glassfish.hk2.api.Factory;
    import org.glassfish.hk2.api.ServiceLocator;
    import org.glassfish.jersey.server.internal.inject.AbstractValueFactoryProvider;
    import org.glassfish.jersey.server.internal.inject.MultivaluedParameterExtractorProvider;
    import org.glassfish.jersey.server.model.Parameter;
    
    public class TokenFactoryProvider extends AbstractValueFactoryProvider {
        
        private final TokenFactory tokenFactory;
        
        @Inject
        public TokenFactoryProvider(
                final MultivaluedParameterExtractorProvider extractorProvider,
                ServiceLocator locator,
                TokenFactory tokenFactory) {
            
            super(extractorProvider, locator, Parameter.Source.UNKNOWN);
            this.tokenFactory = tokenFactory;
        }
    
        @Override
        protected Factory<?> createValueFactory(Parameter parameter) {
             Class<?> paramType = parameter.getRawType();
             TokenParam annotation = parameter.getAnnotation(TokenParam.class);
             if (annotation != null && paramType.isAssignableFrom(Token.class)) {
                 return tokenFactory;
             }
             return null;
        }
    }
    

    TokenFeature(这里我们绑定了上面看到的所有组件,甚至是TokenAuthentictor,我已经省略了,但是如果你通常使用Dropwizard Authenticator。我还使用了一个Feature。我倾向于这样做来包装自定义功能的组件。这也是您可以决定所有范围的地方。请注意,某些组件必须在 Singleton 范围内)

    import javax.inject.Singleton;
    import javax.ws.rs.core.Feature;
    import javax.ws.rs.core.FeatureContext;
    import org.glassfish.hk2.api.InjectionResolver;
    import org.glassfish.hk2.api.TypeLiteral;
    import org.glassfish.hk2.utilities.binding.AbstractBinder;
    import org.glassfish.jersey.server.spi.internal.ValueFactoryProvider;
    
    public class TokenFeature implements Feature {
    
        @Override
        public boolean configure(FeatureContext context) {
            context.register(new AbstractBinder(){
                @Override
                public void configure() {
                    bind(TokenAuthenticator.class)
                            .to(TokenAuthenticator.class)
                            .in(Singleton.class);
                    bind(TokenFactory.class).to(TokenFactory.class)
                            .in(Singleton.class);
                    bind(TokenFactoryProvider.class)
                            .to(ValueFactoryProvider.class)
                            .in(Singleton.class);
                    bind(TokenParamInjectionResolver.class)
                            .to(new TypeLiteral<InjectionResolver<TokenParam>>(){})
                            .in(Singleton.class);
                }
            });
            return true;
        } 
    }
    

    最后只需注册功能

    register(TokenFeature.class);
    

    现在您应该能够将Token@TokenParam 以及您通常的实体主体注入(如果我们没有实现ValueFactoryProvider,这是不可能的

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    public String postToken(@TokenParam Token token, User user) {
        
    }
    

    更新

    对于您的特定用例来说,这是一个半-@$$ 示例。更好的方法可能是在您的Factory 类中有一个克隆方法,并使用一些参数创建一个新的TokenFactory(也许您可以从您的注解. For example, in the TokenFactory 中获得类似的东西

    public class TokenFactory extends AbstractContainerRequestValueFactory<Token> {
    
        public TokenFactory clone(boolean someAttribute) {
            return new TokenFactory(authenticator, someAttribute);
        }
    

    TokenFactoryProvider ine createValueFactory 方法中,然后调用克隆方法

    TokenParam annotation = parameter.getAnnotation(TokenParam.class);
    
    if (annotation != null && paramType.isAssignableFrom(Token.class)) {
        return tokenFactory.clone(annotation.someAttribute());
    }
    

    或者您实际上可以在方法内创建工厂。你有选择。

    更新 2

    另见

    更新 3

    从 Jersey 2.26 开始,依赖注入发生了变化。您将需要查看 this post 以了解代码在实现相同注入时的变化。

    【讨论】:

    • 你在我的另一个问题上删除了你的 cmets,所以我会在这里问。由于我试图将字段注入@BeanParam,这就是我需要ValueFactoryProvider 的原因吗?因为从InjectionResolver 周围的文档中,我得到了强烈的暗示,将一个字段注入到 BeanParam 上就足够了,因为它是一个独立的对象。无论如何,感谢您的帮助,我将尝试使用 Value Factory 小部件。
    • @PatrickM 我删除了因为我不确定(我没有做任何测试,也从未尝试注入@BeanParam bean)。但是对于方法参数,我们似乎需要使用ValueFactoryProvider。看来InjectionResolver 还不够。由于它似乎适用于方法参数,但我认为它也可能适用于您的 bean 字段。我可能错了,但试一试也无妨。
    • 我发现这个答案非常有用,但也有点令人困惑。我尝试让它工作,但总是因“没有为参数找到注入源”错误而感到高兴。我关注了github.com/jersey/jersey/blob/…的球衣源码示例我还做了一个示例项目:github.com/evadnoob/jersey2-method-param-injection
    • 可以注入@Context注解吗?我无法让它工作。我做了你所描述的除了 Resolver 之外的东西,因为 ContextInjectionResolver 已经由 jersey 框架配置
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-05-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-02
    相关资源
    最近更新 更多