Jersey / HK2

编程入门 行业动态 更新时间:2024-10-25 10:20:49
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是一个请求上下文类(它对每个HTTP请求都不相同),HK2无法为该类创建代理。 HttpServletRequest本身不应该是代理吗?

我怎样才能解决这个问题?

I have an annotation @MagicAnnotation which allows me to inject parameters into my resources. The implementation is as following:

@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"); } }

In my JAX-RS configuration, I register the binder as following:

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());

This works great. An example of usage:

@Path("/magic") public class SomeTest { @MagicAnnotation private String magic; @GET public Response test() { return Response.ok(magic).build(); } }

Now, I want to use @MagicAnnotation inside a ContainerRequestFilter. I tried as following:

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

This gives the following during initialization:

java.lang.IllegalStateException: Not inside a request scope

After some debugging, I found out that the injection of HttpServletRequest in MagicFactory is the problem. I guess that HttpServletRequest is a request-contextual class (it is different on every HTTP request) and HK2 is unable to create a proxy for that class. Shouldn't HttpServletRequest be already a proxy by itself?

How can I get around this?

最满意答案

HttpServletRequest本身不应该是代理吗?

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

最简单的解决方法就是使用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(); } } }

UPDATE

好的,所以上述解决方案将无法正常工作。 看来,即使使用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这样的参数的参数注入的@QueryParam 。

用作该基础结构一部分的类是AbstractValueFactoryProvider和您正在使用的ParamInjectionResolver 。 但是这些类只是泽西岛用来保持干燥的辅助类,因为有很多不同类型的参数需要注入。 但是这些类只是为了处理这个用例而需要实现的主要契约的扩展,即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() 。

现在要处理自定义注释注入,我们需要一个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的注射。 然后,我们将工作委托给系统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测试框架的完整示例。 像其他任何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")); } }

也可以看看:

有关InjectionResolver更多信息,请参阅此处和此处 使用HK2和Jersey将请求作用域对象注入到Singleton作用域对象中

Shouldn't HttpServletRequest be already a proxy by itself?

Yes it is, but because you are trying to inject the magic annotation target into a filter (which is a singleton that gets instantiated at application start), the provide() method of the factory gets called, which calls the HttpServletRequest. And because there is no request on startup, you get the "not in a reuqest scope" error.

The easiest fix is just to use javax.inject.Provider to lazily retrieve the injected object. This way, the factory isn't called until you request for the object, by calling 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(); } } }

UPDATE

Ok, so the above solution won't work. It seems that even using Provider, the factory is still called.

What we need to do is make the magic value a proxy. But a String can't be proxied, so I made a wrapper.

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

Now for some restructuring. First thing we should understand is the required components. The pattern you are currently using for parameter injection is the pattern used in the Jersey source to handle parameter injection for params like @PathParam and @QueryParam.

The classes used as part of that infrastructure is the AbstractValueFactoryProvider and the ParamInjectionResolver that you are using. But these classes are only really helper classes that Jersey uses to keep it DRY, as there are many different types of params to inject. But those classes are only extension of the main contracts that need to be implemented to handle this use case, namely ValueFactoryProvider and InjectResolver. So we can restructure our use case by directly implementing those contracts, instead of using Jersey's "helper" infrastructure. This allows us to create the proxy where needed.

To create the proxy for our MagicWrapper, we just configure the Factory for it in the AbstractBinder as a proxy

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

The call to proxy makes the object proxiable, and the call to proxyForSameScope(false) ensures that when it is in a request scope, it is the actual object, instead of a proxy. Doesn't really make much of a difference here. Only really one that matters is the call to proxy().

Now to handle custom annotation injection, we need an InjectionResolver. That's its job.

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; } }

As stated above, the ParamInjectionResolver you are currently using, is just an implementation of InjectionResolver which is more simplified, but won't work for this case. So we just implement it ourselves. We don't really do anything but check the type so that we only handle injections for MagicWrappers. Then we just delegate the work to the system InjectionResolver.

Now we need the component that Jersey uses for method parameter injection, which is the 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; } }

Here we are just returning the factory, just like you did in the AbstractValueFactoryProvider. Only difference is, we need to explicitly inject it, so that it gets the HttpServletRequest. This is the same thing the Jersey does in the AbstractValueFactoryProvider.

And that's it. Below is a complete example using Jersey Test Framework. Run it like any other JUnit test.

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")); } }

See Also:

For more about InjectionResolver see here and here Injecting Request Scoped Objects into Singleton Scoped Object with HK2 and Jersey

更多推荐

本文发布于:2023-08-04 00:19:00,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1405191.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:Jersey

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!