【问题标题】:Jersey/HK2 - injecten of HttpServletRequest inside ContainerRequestFilter via annotated injectionJersey/HK2 - 通过注解注入在 ContainerRequestFilter 内注入 HttpServletRequest
【发布时间】:2016-09-09 12:32:25
【问题描述】:

我有一个注释@MagicAnnotation,它允许我将参数注入到我的资源中。实现如下:

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

public class MagicResolver extends ParamInjectionResolver<MagicAnnotation> {
    public MagicResolver() {
        super(MagicProvider.class);
    }
}

public class MagicProvider extends AbstractValueFactoryProvider {
    @Inject
    public MagicProvider(final MultivaluedParameterExtractorProvider provider, final ServiceLocator locator) {
        super(provider, locator, Parameter.Source.UNKNOWN);
    }

    @Override
    protected Factory<?> createValueFactory(final Parameter parameter) {
        return new MagicFactory();
    }
}

public class MagicFactory extends AbstractContainerRequestValueFactory<String> {
    @Context
    private HttpServletRequest request;

    @Override
    public String provide() {
        return request.getParameter("value");
    }
}

在我的 JAX-RS 配置中,我将绑定器注册如下:

public class MagicBinder extends AbstractBinder {
    @Override
    protected void configure() {
        bind(MagicProvider.class).to(ValueFactoryProvider.class).in(Singleton.class);
        bind(MagicResolver.class).to(new TypeLiteral<InjectionResolver<MagicAnnotation>>() {
        }).in(Singleton.class);
    }
}

register(new MagicBinder());

这很好用。使用示例:

@Path("/magic")
public class SomeTest {
    @MagicAnnotation
    private String magic;

    @GET
    public Response test() {
        return Response.ok(magic).build();
    }
}

现在,我想在 ContainerRequestFilter 中使用 @MagicAnnotation。我尝试如下:

@Provider
public class MagicFilter implements ContainerRequestFilter {
    @MagicAnnotation
    private String magic;

    @Override
    public void filter(final ContainerRequestContext context) {
        if (!"secret".equals(magic)) {
            throw new NotFoundException();
        }
    }
}

这在初始化期间给出以下信息:

java.lang.IllegalStateException: Not inside a request scope

经过一番调试,我发现HttpServletRequestMagicFactory中的注入是问题所在。我猜HttpServletRequest 是一个请求上下文类(每个 HTTP 请求都不同),HK2 无法为该类创建代理。 HttpServletRequest 本身不应该是代理吗?

我该如何解决这个问题?

【问题讨论】:

    标签: java dependency-injection jersey hk2


    【解决方案1】:

    HttpServletRequest 本身不应该已经是代理了吗?

    是的,但是因为您试图将魔术注释目标注入过滤器(这是在应用程序启动时实例化的单例),所以调用了工厂的 provide() 方法,该方法调用了 @987654328 @。而且因为启动时没有请求,所以会出现“不在请求范围内”错误。

    最简单的解决方法是使用javax.inject.Provider 懒惰地检索注入的对象。这样,在您通过调用 Provider#get() 请求对象之前不会调用工厂。

    @Provider
    public class MagicFilter implements ContainerRequestFilter {
        @MagicAnnotation
        private Provider<String> magic;
    
        @Override
        public void filter(final ContainerRequestContext context) {
                               // Provider#get()
            if (!"secret".equals(magic.get())) {
                throw new NotFoundException();
            }
        }
    }
    

    更新

    好的,所以上面的解决方案不起作用。似乎即使使用Provider,仍然调用工厂。

    我们需要做的是使magic 值成为代理。但是String不能被代理,所以我做了一个包装器。

    public class MagicWrapper {
        private String value;
    
        /* need to proxy */
        public MagicWrapper() {   
        }
    
        public MagicWrapper(String value) {
            this.value = value;
        }
    
        public String get() {
            return this.value;
        }
    }
    

    现在进行一些重组。首先我们应该了解的是所需的组件。您当前用于参数注入的模式是 Jersey 源中用于处理 @PathParam@QueryParam 等参数的参数注入的模式。

    用作该基础架构一部分的类是您正在使用的AbstractValueFactoryProviderParamInjectionResolver。但是这些类只是 Jersey 用来保持 DRY 的真正帮助类,因为有许多不同类型的参数要注入。但是这些类只是处理这个用例需要实现的主要合约的扩展,即ValueFactoryProviderInjectResolver。所以我们可以通过直接实现这些合约来重构我们的用例,而不是使用 Jersey 的“帮助”基础设施。这允许我们在需要的地方创建代理。

    要为我们的MagicWrapper 创建代理,我们只需在AbstractBinder 中为其配置Factory 作为代理

    @Override
    public void configure() {
        bindFactory(MagicWrapperFactory.class)
            .to(MagicWrapper.class)
            .proxy(true)
            .proxyForSameScope(false)
            .in(RequestScoped.class);
    }
    

    proxy 的调用使对象可代理,对proxyForSameScope(false) 的调用确保当它在请求范围内时,它是实际对象,而不是代理。在这里并没有太大的区别。唯一真正重要的是致电proxy()

    现在要处理自定义注解注入,我们需要一个InjectionResolver。这就是它的工作。

    public class MagicInjectionResolver implements InjectionResolver<MagicAnnotation> {
    
        @Inject @Named(InjectionResolver.SYSTEM_RESOLVER_NAME)
        private InjectionResolver<Inject> systemResolver;
    
        @Override
        public Object resolve(Injectee injectee, ServiceHandle<?> handle) {
            if (injectee.getRequiredType() == MagicWrapper.class) {
                return systemResolver.resolve(injectee, handle);
            }
            return null;
        }
    
        @Override
        public boolean isConstructorParameterIndicator() { return false; }
    
        @Override
        public boolean isMethodParameterIndicator() { return true; }
    }
    

    如上所述,您当前使用的ParamInjectionResolver 只是InjectionResolver 的一个实现,它更加简化,但不适用于这种情况。所以我们只是自己实现它。除了检查类型之外,我们实际上并没有做任何事情,因此我们只处理MagicWrappers 的注入。然后我们只是将工作委托给系统InjectionResolver

    现在我们需要 Jersey 用于方法参数注入的组件,即ValueFactoryProvider

    public class MagicValueFactoryProvider implements ValueFactoryProvider {
    
        @Inject
        private ServiceLocator locator;
    
        @Override
        public Factory<?> getValueFactory(Parameter parameter) {
            if (parameter.isAnnotationPresent((MagicAnnotation.class))) {
                final MagicWrapperFactory factory = new MagicWrapperFactory();
                locator.inject(factory);
                return factory;
            }
            return null;
        }
    
        @Override
        public PriorityType getPriority() {
            return Priority.NORMAL;
        }
    }
    

    这里我们只是返回工厂,就像您在AbstractValueFactoryProvider 中所做的一样。唯一的区别是,我们需要显式地注入它,以便它获得HttpServletRequest。这与 Jersey 在AbstractValueFactoryProvider 中所做的相同。

    就是这样。下面是使用Jersey Test Framework 的完整示例。像任何其他 JUnit 测试一样运行它。

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    import java.util.logging.Logger;
    
    import javax.inject.Inject;
    import javax.inject.Named;
    import javax.inject.Singleton;
    import javax.servlet.http.HttpServletRequest;
    import javax.ws.rs.GET;
    import javax.ws.rs.Path;
    import javax.ws.rs.container.ContainerRequestContext;
    import javax.ws.rs.container.ContainerResponseContext;
    import javax.ws.rs.container.ContainerResponseFilter;
    import javax.ws.rs.core.Context;
    import javax.ws.rs.core.Response;
    import javax.ws.rs.ext.Provider;
    
    import org.glassfish.hk2.api.Factory;
    import org.glassfish.hk2.api.Injectee;
    import org.glassfish.hk2.api.InjectionResolver;
    import org.glassfish.hk2.api.ServiceHandle;
    import org.glassfish.hk2.api.ServiceLocator;
    import org.glassfish.hk2.api.TypeLiteral;
    import org.glassfish.hk2.utilities.binding.AbstractBinder;
    import org.glassfish.jersey.filter.LoggingFilter;
    import org.glassfish.jersey.process.internal.RequestScoped;
    import org.glassfish.jersey.server.ResourceConfig;
    import org.glassfish.jersey.server.model.Parameter;
    import org.glassfish.jersey.server.spi.internal.ValueFactoryProvider;
    import org.glassfish.jersey.servlet.ServletContainer;
    import org.glassfish.jersey.test.DeploymentContext;
    import org.glassfish.jersey.test.JerseyTest;
    import org.glassfish.jersey.test.ServletDeploymentContext;
    import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory;
    import org.glassfish.jersey.test.spi.TestContainerFactory;
    import org.junit.Test;
    
    import static junit.framework.Assert.assertEquals;
    
    /**
     * See http://stackoverflow.com/q/39411704/2587435
     * 
     * Run like any other JUnit test. Only one require dependency
     * <dependency>
     *   <groupId>org.glassfish.jersey.test-framework.providers</groupId>
     *   <artifactId>jersey-test-framework-provider-grizzly2</artifactId>
     *   <version>${jersey2.version}</version>
     *   <scope>test</scope>
     * </dependency>
     *
     * @author Paul Samsotha
     */
    public class InjectionTest extends JerseyTest {
    
        @Path("test")
        public static class TestResource {
            @GET
            public String get(@MagicAnnotation MagicWrapper magic) {
                return magic.get();
            }
        }
    
        @Provider
        public static class MagicFilter implements ContainerResponseFilter {
    
            @MagicAnnotation
            private MagicWrapper magic;
    
            @Override
            public void filter(ContainerRequestContext request, ContainerResponseContext response) {
                response.getHeaders().add("X-Magic-Header", magic.get());
            }
        }
    
        @Override
        public ResourceConfig configure() {
            return new ResourceConfig()
                .register(TestResource.class)
                .register(MagicFilter.class)
                .register(new LoggingFilter(Logger.getAnonymousLogger(), true))
                .register(new AbstractBinder() {
                    @Override
                    public void configure() {
                        bindFactory(MagicWrapperFactory.class)
                                .to(MagicWrapper.class)
                                .proxy(true)
                                .proxyForSameScope(false)
                                .in(RequestScoped.class);
                        bind(MagicInjectionResolver.class)
                                .to(new TypeLiteral<InjectionResolver<MagicAnnotation>>(){})
                                .in(Singleton.class);
                        bind(MagicValueFactoryProvider.class)
                                .to(ValueFactoryProvider.class)
                                .in(Singleton.class);
                    }
                });
        }
    
        @Override
        public TestContainerFactory getTestContainerFactory() {
            return new GrizzlyWebTestContainerFactory();
        }
    
        @Override
        public DeploymentContext configureDeployment() {
            return ServletDeploymentContext.forServlet(new ServletContainer(configure())).build();
        }
    
        @Retention(RetentionPolicy.RUNTIME)
        @Target({ElementType.PARAMETER, ElementType.FIELD})
        public static @interface MagicAnnotation {
        }
    
        public static class MagicWrapper {
            private String value;
    
            /* need to proxy */
            public MagicWrapper() {   
            }
    
            public MagicWrapper(String value) {
                this.value = value;
            }
    
            public String get() {
                return this.value;
            }
        }
    
        public static class MagicWrapperFactory implements Factory<MagicWrapper> {
            @Context
            private HttpServletRequest request;
    
            @Override
            public MagicWrapper provide() {
                return new MagicWrapper(request.getParameter("value"));
            }
    
            @Override
            public void dispose(MagicWrapper magic) {}
        }
    
        public static class MagicValueFactoryProvider implements ValueFactoryProvider {
    
            @Inject
            private ServiceLocator locator;
    
            @Override
            public Factory<?> getValueFactory(Parameter parameter) {
                if (parameter.isAnnotationPresent((MagicAnnotation.class))) {
                    final MagicWrapperFactory factory = new MagicWrapperFactory();
                    locator.inject(factory);
                    return factory;
                }
                return null;
            }
    
            @Override
            public PriorityType getPriority() {
                return Priority.NORMAL;
            }
        }
    
        public static class MagicInjectionResolver implements InjectionResolver<MagicAnnotation> {
    
            @Inject @Named(InjectionResolver.SYSTEM_RESOLVER_NAME)
            private InjectionResolver<Inject> systemResolver;
    
            @Override
            public Object resolve(Injectee injectee, ServiceHandle<?> handle) {
                if (injectee.getRequiredType() == MagicWrapper.class) {
                    return systemResolver.resolve(injectee, handle);
                }
                return null;
            }
    
            @Override
            public boolean isConstructorParameterIndicator() { return false; }
    
            @Override
            public boolean isMethodParameterIndicator() { return true; }
        }
    
        @Test
        public void testInjectionsOk() {
            final Response response = target("test").queryParam("value", "HelloWorld")
                    .request().get();
            assertEquals("HelloWorld", response.readEntity(String.class));
            assertEquals("HelloWorld", response.getHeaderString("X-Magic-Header"));
        }
    }
    

    另请参阅:

    【讨论】:

    • 谢谢。我尝试更改为Provider&lt;String&gt;,但我仍然收到相同的not in a request scope 错误。 provide() 仍在被调用,因此出现同样的错误。我想我需要另一层抽象/代理,但我不确定如何。有什么想法吗?
    • 是的,我很害怕。在发布之前我没有测试它。我稍后会检查一下。我认为这将需要一些重组
    • 好的,谢谢。期间我会继续努力。我想我们需要让 HK2 创建一个就地代理,但我还不确定。提前致谢,非常感谢。
    猜你喜欢
    • 1970-01-01
    • 2014-12-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-03
    • 2015-04-10
    • 2021-10-07
    相关资源
    最近更新 更多