Jersey 2.x使用带有属性的自定义注入注释

12

我正在从DropWizard 0.7.1迁移到0.8.1。这包括从Jersey 1.x迁移到2.x。在我的实现中,使用的是Jersey 1.18.1,我有一个实现了InjectableProviderMyProvider类(为简单起见更改了所有类名)。这个类将创建包含自定义注入注释MyTokenMyInjectable对象。MyToken包含传递和由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)));
1个回答

28

在Jersey 2.x中,定制注入的创建变得更加复杂了。有几个主要组件需要了解:

您可以在Custom Injection and Lifecycle Management中了解更多关于自定义注入的内容。文档的一个缺点是缺乏如何注入参数值的说明。您可以简单地实现InjectResolver,并能够将其注入到具有自定义注释的字段中,但是为了将其注入到方法参数中,我们需要使用ValueFactoryProvider

幸运的是,我们可以扩展一些抽象类(文档也未提及),这将使生活变得更加轻松。我必须在org.glassfish.jersey.server.internal.inject包的源代码中搜索一段时间,以尝试弄清楚所有内容。

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

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);
现在,您应该能够使用@TokenParam注入Token,以及您平常的实体主体(如果我们没有实现ValueFactoryProvider,这是不可能的)。

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

更新

对于您特定的用例来说,这只是半吊子示例。更好的方法可能是在您的Factory类中拥有一个克隆方法,并使用一些参数(也许您可以从注释中获取)创建一个新的TokenFactory。例如,在TokenFactory中你可以有类似这样的内容:

public class TokenFactory extends AbstractContainerRequestValueFactory<Token> {

    public TokenFactory clone(boolean someAttribute) {
        return new TokenFactory(authenticator, someAttribute);
    }

TokenFactoryProvidercreateValueFactory方法中,然后调用克隆方法。

TokenParam annotation = parameter.getAnnotation(TokenParam.class);

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

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

更新2

参见

更新3

从Jersey 2.26开始,依赖注入已更改。您将想查看此帖子以获取实现此相同注入的代码更改的示例。


你在我的另一个问题中删除了你的评论,所以我会在这里问。因为我正在尝试将一个字段注入到@BeanParam上,这就是为什么我需要ValueFactoryProvider的原因吗?因为从InjectionResolver周围的文档中,我得到了强烈的暗示,注入一个字段到BeanParam上就足够了,因为它是一个独立的对象。无论如何,感谢您的帮助,我将尝试使用Value Factory小部件。 - Patrick M
@PatrickM 我删除了,因为我不确定(我没有进行任何测试,也从未尝试过将内容注入到@BeanParam bean中)。但是对于方法参数,似乎我们需要使用ValueFactoryProvider。看起来InjectionResolver还不够用。由于它似乎适用于方法参数,所以我认为它也可能适用于您的bean字段。我可能错了,但是尝试一下也无妨。 - Paul Samsotha
我发现这个答案非常有用,但也有点令人困惑。我尝试让它工作,但总是遇到“找不到参数的注入源”的错误。我遵循了Jersey源代码示例:https://github.com/jersey/jersey/blob/927b4d0605aeb119d15e9623dab65c8aec2c8e20/core-server/src/main/java/org/glassfish/jersey/server/internal/inject/HeaderParamValueFactoryProvider.java 我还制作了一个示例项目:https://github.com/evadnoob/jersey2-method-param-injection - David
使用@Context注释进行注入是否可行?我无法使其工作。我执行了您描述的确切操作,除了Resolver之外,因为ContextInjectionResolver已由jersey框架配置。 - user1745356

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接