[英]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配置中,我將binder注冊如下:
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
本身不應該是代理嗎?
我怎么能繞過這個?
HttpServletRequest本身不應該是代理嗎?
是的,但是因為你試圖將魔術注釋目標注入過濾器(它是在應用程序啟動時實例化的單例provide()
,所以調用工廠的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();
}
}
}
好的,所以上面的解決方案不起作用。 似乎即使使用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
類的參數的參數注入的@QueryParam
。
用作該基礎結構一部分的類是您正在使用的AbstractValueFactoryProvider
和ParamInjectionResolver
。 但是這些類只是澤西島用來保持DRY的真正的輔助類,因為要注入許多不同類型的參數。 但是這些類只是需要實現來處理這個用例的主要契約的擴展,即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 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"));
}
}
也可以看看:
InjectionResolver
更多信息,請參閱此處和此處
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.