Jersey/HK2 - 通过注解注入在 ContainerRequestFilter 中注入 HttpServletRequest
Jersey/HK2 - injecten of HttpServletRequest inside ContainerRequestFilter via annotated injection
我有一个注释 @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
经过一番调试,发现是MagicFactory
中HttpServletRequest
的注入问题。我猜 HttpServletRequest
是一个请求上下文 class(每个 HTTP 请求都不同)并且 HK2 无法为那个 class 创建代理。 HttpServletRequest
本身不应该是代理吗?
我该如何解决这个问题?
Shouldn't HttpServletRequest be already a proxy by itself?
是的,但是因为您正试图将魔术注释目标注入过滤器(这是一个在应用程序启动时实例化的单例),所以调用了工厂的 provide()
方法,这调用 HttpServletRequest
。由于启动时没有请求,您会收到 "not in a reuqest scope" 错误。
最简单的修复方法就是使用 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
值成为一个代理。但是字符串不能被代理,所以我做了一个包装器。
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
.
用作该基础结构一部分的 类 是您正在使用的 AbstractValueFactoryProvider
和 ParamInjectionResolver
。但是这些 类 只是 Jersey 用来保持 DRY 的真正助手 类,因为有许多不同类型的参数要注入。但是那些 类 只是处理此用例需要实施的主要合约的扩展,即 ValueFactoryProvider
和 InjectResolver
。所以我们可以通过直接实现这些契约来重构我们的用例,而不是使用 Jersey 的 "helper" 基础设施。这允许我们在需要的地方创建代理。
要为我们的 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
的一个实现,它更加简化,但不适用于这种情况。所以我们只好自己实现。我们实际上什么都不做,只是检查类型,以便我们只处理 MagicWrapper
s 的注入。那么我们就把工作委托给系统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
。这与球衣在 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
*
* 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"));
}
}
另请参阅:
- 有关
InjectionResolver
的更多信息,请参阅 here and here
- Injecting Request Scoped Objects into Singleton Scoped Object with HK2 and Jersey
我有一个注释 @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
经过一番调试,发现是MagicFactory
中HttpServletRequest
的注入问题。我猜 HttpServletRequest
是一个请求上下文 class(每个 HTTP 请求都不同)并且 HK2 无法为那个 class 创建代理。 HttpServletRequest
本身不应该是代理吗?
我该如何解决这个问题?
Shouldn't HttpServletRequest be already a proxy by itself?
是的,但是因为您正试图将魔术注释目标注入过滤器(这是一个在应用程序启动时实例化的单例),所以调用了工厂的 provide()
方法,这调用 HttpServletRequest
。由于启动时没有请求,您会收到 "not in a reuqest scope" 错误。
最简单的修复方法就是使用 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
值成为一个代理。但是字符串不能被代理,所以我做了一个包装器。
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
.
用作该基础结构一部分的 类 是您正在使用的 AbstractValueFactoryProvider
和 ParamInjectionResolver
。但是这些 类 只是 Jersey 用来保持 DRY 的真正助手 类,因为有许多不同类型的参数要注入。但是那些 类 只是处理此用例需要实施的主要合约的扩展,即 ValueFactoryProvider
和 InjectResolver
。所以我们可以通过直接实现这些契约来重构我们的用例,而不是使用 Jersey 的 "helper" 基础设施。这允许我们在需要的地方创建代理。
要为我们的 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
的一个实现,它更加简化,但不适用于这种情况。所以我们只好自己实现。我们实际上什么都不做,只是检查类型,以便我们只处理 MagicWrapper
s 的注入。那么我们就把工作委托给系统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
。这与球衣在 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
*
* 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"));
}
}
另请参阅:
- 有关
InjectionResolver
的更多信息,请参阅 here and here - Injecting Request Scoped Objects into Singleton Scoped Object with HK2 and Jersey