java - Jersey/HK2 - 通过带注释的注入(inject)将 HttpServletRequest 注入(inject) ContainerRequestFilter 内

标签 java dependency-injection jersey hk2

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

@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 配置中,我按如下方式注册绑定(bind)器:

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

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

我该如何解决这个问题?

最佳答案

Shouldn't HttpServletRequest be already a proxy by itself?

是的,但是因为您试图将神奇的注释目标注入(inject)过滤器(这是在应用程序启动时实例化的单例),所以会调用工厂的 provide() 方法,该方法调用 HttpServletRequest。并且由于启动时没有请求,因此您会收到“不在请求范围内”错误。

最简单的修复方法就是使用 javax.inject.Provider延迟检索注入(inject)的对象。这样,在您通过调用 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 值设置为代理。但是字符串不能被代理,所以我做了一个包装器。

public class MagicWrapper {
    private String value;

    /* need to proxy */
    public MagicWrapper() {   
    }

    public MagicWrapper(String value) {
        this.value = value;
    }

    public String get() {
        return this.value;
    }
}

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

用作该基础设施一部分的类是您正在使用的 AbstractValueFactoryProviderParamInjectionResolver。但这些类只是 Jersey 用于保持干燥的真正帮助类,因为有许多不同类型的参数需要注入(inject)。但这些类只是处理此用例所需实现的主协定的扩展,即 ValueFactoryProvider 和 InjectResolver。因此,我们可以通过直接实现这些合约来重构我们的用例,而不是使用 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() 的调用。

现在要处理自定义注释注入(inject),我们需要一个 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 的实现,它更加简化,但不适用于这种情况。所以我们就自己实现吧。我们实际上并没有做任何事情,只是检查类型,以便我们只处理 MagicWrapper 的注入(inject)。然后我们将工作委托(delegate)给系统InjectionResolver

现在我们需要 Jersey 用于方法参数注入(inject)的组件,即 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 中所做的那样。唯一的区别是,我们需要显式注入(inject)它,以便它获取 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"));
    }
}

另请参阅:

关于java - Jersey/HK2 - 通过带注释的注入(inject)将 HttpServletRequest 注入(inject) ContainerRequestFilter 内,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39411704/

相关文章:

c# - 仅当值在运行时使用 Unity 为非空时,如何才能注入(inject)属性?

unit-testing - 使用 Jersey 测试框架输出 HTTP 响应?

java - Jersey/Grizzly POST 对于大 URI 失败

java - 带有查询参数的 Jersey DELETE 请求

java - ViewPager 的内存问题

java - 未找到端点映射...,使用 SpringWS、JaxB Marshaller

testing - 你如何测试 Angular 的依赖注入(inject)?

c# - 什么时候将 RegistrationBuilder 传递给 Catalog?

java exec 在 Windows 中返回 PID

java - Hazelcast Jet 丢弃空聚合结果