@Inject 不能在 HK2 管理的 MethodInterceptor 中工作
@Inject not working in MethodInterceptor managed by HK2
我目前正在重构 Jersey Web 应用程序,并希望将一些横切关注点捆绑到它们自己的 classes 中,并使用注释来拦截方法。例如,有很多方法我需要检查用户是否是他想要更改的实体的所有者(在我的例子中,这是一个项目)。因此,在拦截器中,我需要进行数据库调用,尽管注入适当的 DAO 是最好的方法。
目前我的拦截器是这样的:
public class ProjectOwnerCheckInterceptor implements MethodInterceptor {
@Inject
private EntityManager em;
@Inject
private UserProvider userProvider;
@Inject
private RMUserDAO rmUserDAO;
@Inject
private ProjectDAO projectDAO;
public ProjectOwnerCheckInterceptor() {
// TODO Auto-generated constructor stub
}
@Override
public Object invoke(MethodInvocation arg0) throws Throwable {
// First of all let's get the annotation
ProjectOwnerCheck check = arg0.getMethod().getAnnotation(ProjectOwnerCheck.class);
// if there is no check, then just proceed!
if (check == null)
arg0.proceed();
long projectId = (long) arg0.getArguments() [check.projectIdIndex()];
// Handling ownership!!
Project project = getProjectOrThrow(projectId);
return arg0.proceed();
}
}
自定义注释很简单。我需要添加一些小信息,方法中的 entityId 必须检查哪个参数位置是因为参数数量和类型不同:
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD })
public @interface ProjectOwnerCheck {
int projectIdIndex() default -1;
}
为了 Jersey/HK2 知道如何处理拦截器,我创建了一个实现 InterceptionService 的过滤器:
public class HK2InterceptorFilter implements InterceptionService {
private final static MethodInterceptor PROJECT_CHECK_METHOD_INTERCEPTOR = new ProjectOwnerCheckInterceptor();
private final static List<MethodInterceptor> PROJECT_CHECK_METHOD_LIST = Collections
.singletonList(PROJECT_CHECK_METHOD_INTERCEPTOR);
public HK2InterceptorFilter() {
// TODO Auto-generated constructor stub
}
@Override
public Filter getDescriptorFilter() {
return BuilderHelper.allFilter();
}
@Override
public List<MethodInterceptor> getMethodInterceptors(Method method) {
if (method.isAnnotationPresent(ProjectOwnerCheck.class))
return PROJECT_CHECK_METHOD_LIST;
return null;
}
@Override
public List<ConstructorInterceptor> getConstructorInterceptors(Constructor<?> constructor) {
// TODO Auto-generated method stub
return null;
}
}
我在我的 JerseyApplication 中绑定那个过滤器 class:
register(new AbstractBinder() {
@Override
protected void configure() {
try {
bind(HK2InterceptorFilter.class).to(InterceptionService.class).in(Singleton.class);
bind(getPasswordStorage()).to(PasswordStorage.class);
bind(getDocumentService()).to(DocumentService.class);
bind(UserManagementAccessor.getUserProvider()).to(UserProvider.class);
} catch (Exception e) {
throw new InternalServerErrorException(e);
}
}
});
在我的拦截器中设置断点时,我可以看到它已正确实例化并调用了方法。但是我完全想念的是我需要进行检查的所有@Inject 字段。我是不是遗漏了什么,或者这在 HK2 中是不可能的。我曾经与 Guice 一起工作并且它正在工作(我是 - 由于该应用程序的代码库非常大,但时间有限 - 绑定到 HK2:))。
提前感谢您的帮助!
PS:
我正在使用 Jersey 2.17
问题是拦截器永远不会经历 DI 生命周期,因为它不是容器中的服务。您正在自己实例化它。当您在 DI 框架中执行此操作时,大多数情况下您可以期待此结果。
您可以 做的是使用 HK2 容器 ServiceLocator
自己显式注入它。您将定位器注入 InterceptionService
,然后调用 locator.inject(interceptor)
。此方法是显式注入 any 任意对象的通用方法。所以你可以把它改成
private final List<MethodInterceptor> PROJECT_CHECK_METHOD_LIST;
@Inject
public HK2InterceptorFilter(ServiceLocator locator) {
final MethodIntercator i = new ProjectOwnerCheckInterceptor();
locator.inject(i)
PROJECT_CHECK_METHOD_LIST = Collections.singletonList(i);
}
您将要面对的另一个问题是拦截器是单例,但您尝试注入的所有服务看起来都在请求范围内。这是一个问题,因为他们需要根据每个请求进行更改。为此,我们可以做的就是让他们成为代理人。我们可以通过在绑定
中链接几个方法来简单地做到这一点
bind(getPasswordStorage())
.to(PasswordStorage.class)
.proxy(true)
.proxyForSameScope(false)
.in(RequestScoped.class);
另请参阅: Injecting Request Scoped Objects into Singleton Scoped Object with HK2 and Jersey
下面是使用 Jersey Test Framework 的完整示例。
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
import java.util.logging.Logger;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
import org.aopalliance.intercept.ConstructorInterceptor;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.glassfish.hk2.api.Filter;
import org.glassfish.hk2.api.InterceptionService;
import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.hk2.utilities.BuilderHelper;
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.test.JerseyTest;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
/**
* Stack Overflow question
*
* Run this like any other JUnit test. One one required test dependency:
*
* <dependency>
* <groupId>org.glassfish.jersey.test-framework.providers</groupId>
* <artifactId>jersey-test-framework-provider-inmemory</artifactId>
* <version>${jersey2.version}</version>
* </dependency>
*
* @author Paul Samsotha
*/
public class InterceptionTest extends JerseyTest {
public static interface HeaderProvider {
String getXCustomHeader();
}
public static class HeaderProviderImpl implements HeaderProvider {
@Context
private HttpHeaders headers;
@Override
public String getXCustomHeader() {
return headers.getHeaderString("X-Custom-Header");
}
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public static @interface Intercept {
}
public static class MyMethodInterceptor implements MethodInterceptor {
@Inject
private HeaderProvider provider;
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
return provider.getClass().getName() + ":" + provider.getXCustomHeader();
}
}
public static class InterceptionHandler implements InterceptionService {
private final List<MethodInterceptor> interceptors;
@Inject
public InterceptionHandler(ServiceLocator locator) {
final MethodInterceptor interceptor = new MyMethodInterceptor();
locator.inject(interceptor);
interceptors = Collections.singletonList(interceptor);
}
@Override
public Filter getDescriptorFilter() {
return BuilderHelper.allFilter();
}
@Override
public List<MethodInterceptor> getMethodInterceptors(Method method) {
if (method.isAnnotationPresent(Intercept.class)) {
return interceptors;
}
return null;
}
@Override
public List<ConstructorInterceptor> getConstructorInterceptors(Constructor<?> c) {
return null;
}
}
public static class Binder extends AbstractBinder {
@Override
protected void configure() {
bind(InterceptionHandler.class)
.to(InterceptionService.class)
.in(Singleton.class);
bind(HeaderProviderImpl.class)
.to(HeaderProvider.class)
.proxy(true)
.proxyForSameScope(false)
.in(RequestScoped.class);
}
}
@Path("intercept")
public static class TestResource {
@GET
@Intercept
public String get() {
return null;
}
}
@Override
public ResourceConfig configure() {
return new ResourceConfig(TestResource.class)
.register(new Binder())
.register(new LoggingFilter(Logger.getAnonymousLogger(), true));
}
@Test
public void shouldReturnHeaderAndProxyClass() {
Response response = target("intercept").request()
.header("X-Custom-Header", "Value1")
.get();
assertThat(response.getStatus(), is(200));
String entity = response.readEntity(String.class);
response.close();
assertThat(entity, containsString("Value1"));
assertThat(entity, containsString("Proxy"));
// Change header to make sure we aren't getting the same HttpHeaders instance
response = target("intercept").request()
.header("X-Custom-Header", "Value2")
.get();
assertThat(response.getStatus(), is(200));
entity = response.readEntity(String.class);
response.close();
assertThat(entity, containsString("Value2"));
assertThat(entity, containsString("Proxy"));
}
}
我目前正在重构 Jersey Web 应用程序,并希望将一些横切关注点捆绑到它们自己的 classes 中,并使用注释来拦截方法。例如,有很多方法我需要检查用户是否是他想要更改的实体的所有者(在我的例子中,这是一个项目)。因此,在拦截器中,我需要进行数据库调用,尽管注入适当的 DAO 是最好的方法。
目前我的拦截器是这样的:
public class ProjectOwnerCheckInterceptor implements MethodInterceptor {
@Inject
private EntityManager em;
@Inject
private UserProvider userProvider;
@Inject
private RMUserDAO rmUserDAO;
@Inject
private ProjectDAO projectDAO;
public ProjectOwnerCheckInterceptor() {
// TODO Auto-generated constructor stub
}
@Override
public Object invoke(MethodInvocation arg0) throws Throwable {
// First of all let's get the annotation
ProjectOwnerCheck check = arg0.getMethod().getAnnotation(ProjectOwnerCheck.class);
// if there is no check, then just proceed!
if (check == null)
arg0.proceed();
long projectId = (long) arg0.getArguments() [check.projectIdIndex()];
// Handling ownership!!
Project project = getProjectOrThrow(projectId);
return arg0.proceed();
}
}
自定义注释很简单。我需要添加一些小信息,方法中的 entityId 必须检查哪个参数位置是因为参数数量和类型不同:
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD })
public @interface ProjectOwnerCheck {
int projectIdIndex() default -1;
}
为了 Jersey/HK2 知道如何处理拦截器,我创建了一个实现 InterceptionService 的过滤器:
public class HK2InterceptorFilter implements InterceptionService {
private final static MethodInterceptor PROJECT_CHECK_METHOD_INTERCEPTOR = new ProjectOwnerCheckInterceptor();
private final static List<MethodInterceptor> PROJECT_CHECK_METHOD_LIST = Collections
.singletonList(PROJECT_CHECK_METHOD_INTERCEPTOR);
public HK2InterceptorFilter() {
// TODO Auto-generated constructor stub
}
@Override
public Filter getDescriptorFilter() {
return BuilderHelper.allFilter();
}
@Override
public List<MethodInterceptor> getMethodInterceptors(Method method) {
if (method.isAnnotationPresent(ProjectOwnerCheck.class))
return PROJECT_CHECK_METHOD_LIST;
return null;
}
@Override
public List<ConstructorInterceptor> getConstructorInterceptors(Constructor<?> constructor) {
// TODO Auto-generated method stub
return null;
}
}
我在我的 JerseyApplication 中绑定那个过滤器 class:
register(new AbstractBinder() {
@Override
protected void configure() {
try {
bind(HK2InterceptorFilter.class).to(InterceptionService.class).in(Singleton.class);
bind(getPasswordStorage()).to(PasswordStorage.class);
bind(getDocumentService()).to(DocumentService.class);
bind(UserManagementAccessor.getUserProvider()).to(UserProvider.class);
} catch (Exception e) {
throw new InternalServerErrorException(e);
}
}
});
在我的拦截器中设置断点时,我可以看到它已正确实例化并调用了方法。但是我完全想念的是我需要进行检查的所有@Inject 字段。我是不是遗漏了什么,或者这在 HK2 中是不可能的。我曾经与 Guice 一起工作并且它正在工作(我是 - 由于该应用程序的代码库非常大,但时间有限 - 绑定到 HK2:))。
提前感谢您的帮助!
PS:
我正在使用 Jersey 2.17
问题是拦截器永远不会经历 DI 生命周期,因为它不是容器中的服务。您正在自己实例化它。当您在 DI 框架中执行此操作时,大多数情况下您可以期待此结果。
您可以 做的是使用 HK2 容器 ServiceLocator
自己显式注入它。您将定位器注入 InterceptionService
,然后调用 locator.inject(interceptor)
。此方法是显式注入 any 任意对象的通用方法。所以你可以把它改成
private final List<MethodInterceptor> PROJECT_CHECK_METHOD_LIST;
@Inject
public HK2InterceptorFilter(ServiceLocator locator) {
final MethodIntercator i = new ProjectOwnerCheckInterceptor();
locator.inject(i)
PROJECT_CHECK_METHOD_LIST = Collections.singletonList(i);
}
您将要面对的另一个问题是拦截器是单例,但您尝试注入的所有服务看起来都在请求范围内。这是一个问题,因为他们需要根据每个请求进行更改。为此,我们可以做的就是让他们成为代理人。我们可以通过在绑定
中链接几个方法来简单地做到这一点bind(getPasswordStorage())
.to(PasswordStorage.class)
.proxy(true)
.proxyForSameScope(false)
.in(RequestScoped.class);
另请参阅: Injecting Request Scoped Objects into Singleton Scoped Object with HK2 and Jersey
下面是使用 Jersey Test Framework 的完整示例。
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
import java.util.logging.Logger;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
import org.aopalliance.intercept.ConstructorInterceptor;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.glassfish.hk2.api.Filter;
import org.glassfish.hk2.api.InterceptionService;
import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.hk2.utilities.BuilderHelper;
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.test.JerseyTest;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
/**
* Stack Overflow question
*
* Run this like any other JUnit test. One one required test dependency:
*
* <dependency>
* <groupId>org.glassfish.jersey.test-framework.providers</groupId>
* <artifactId>jersey-test-framework-provider-inmemory</artifactId>
* <version>${jersey2.version}</version>
* </dependency>
*
* @author Paul Samsotha
*/
public class InterceptionTest extends JerseyTest {
public static interface HeaderProvider {
String getXCustomHeader();
}
public static class HeaderProviderImpl implements HeaderProvider {
@Context
private HttpHeaders headers;
@Override
public String getXCustomHeader() {
return headers.getHeaderString("X-Custom-Header");
}
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public static @interface Intercept {
}
public static class MyMethodInterceptor implements MethodInterceptor {
@Inject
private HeaderProvider provider;
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
return provider.getClass().getName() + ":" + provider.getXCustomHeader();
}
}
public static class InterceptionHandler implements InterceptionService {
private final List<MethodInterceptor> interceptors;
@Inject
public InterceptionHandler(ServiceLocator locator) {
final MethodInterceptor interceptor = new MyMethodInterceptor();
locator.inject(interceptor);
interceptors = Collections.singletonList(interceptor);
}
@Override
public Filter getDescriptorFilter() {
return BuilderHelper.allFilter();
}
@Override
public List<MethodInterceptor> getMethodInterceptors(Method method) {
if (method.isAnnotationPresent(Intercept.class)) {
return interceptors;
}
return null;
}
@Override
public List<ConstructorInterceptor> getConstructorInterceptors(Constructor<?> c) {
return null;
}
}
public static class Binder extends AbstractBinder {
@Override
protected void configure() {
bind(InterceptionHandler.class)
.to(InterceptionService.class)
.in(Singleton.class);
bind(HeaderProviderImpl.class)
.to(HeaderProvider.class)
.proxy(true)
.proxyForSameScope(false)
.in(RequestScoped.class);
}
}
@Path("intercept")
public static class TestResource {
@GET
@Intercept
public String get() {
return null;
}
}
@Override
public ResourceConfig configure() {
return new ResourceConfig(TestResource.class)
.register(new Binder())
.register(new LoggingFilter(Logger.getAnonymousLogger(), true));
}
@Test
public void shouldReturnHeaderAndProxyClass() {
Response response = target("intercept").request()
.header("X-Custom-Header", "Value1")
.get();
assertThat(response.getStatus(), is(200));
String entity = response.readEntity(String.class);
response.close();
assertThat(entity, containsString("Value1"));
assertThat(entity, containsString("Proxy"));
// Change header to make sure we aren't getting the same HttpHeaders instance
response = target("intercept").request()
.header("X-Custom-Header", "Value2")
.get();
assertThat(response.getStatus(), is(200));
entity = response.readEntity(String.class);
response.close();
assertThat(entity, containsString("Value2"));
assertThat(entity, containsString("Proxy"));
}
}