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

Jersey 2.x Custom Injection Annotation With Attributes

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

我已经做了一些研究,但似乎无法全神贯注地思考如何在泽西岛重新创建(或替换,我想)这样的场景 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)));

是的,在 2.x 中,Jersey 使自定义注入的创建变得有点复杂。您需要了解 Jersey 2.x

自定义注入的一些主要组件

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

幸运的是,我们可以扩展一些抽象的 classes(文档也没有提及),这会让生活变得更轻松。我必须搜索一下 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。如果你对下面发生的事情感兴趣引擎盖,你可以在我链接的源代码中找到 class)

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 class 中使用克隆方法并创建一个带有一些参数的新 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 示例,了解代码在实现相同注入时发生了怎样的变化。