Spring – 拦截bean创建和注入自定义代理

我有一个带有@Autowired字段和处理程序方法的@Controller我想用自定义注释进行注释。

例如,

 @Controller public class MyController{ @Autowired public MyDao myDao; @RequestMapping("/home") @OnlyIfXYZ public String onlyForXYZ() { // do something return "xyz"; } } 

其中@OnlyIfXYZ是自定义注释的示例。 我以为我会拦截Controller bean的创建,传递我自己的CGLIB代理,Spring可以在其上设置属性,比如autowired字段。

我尝试使用InstantiationAwareBeanPostProcessor但该解决方案不能很好地工作,因为postProcessBeforeInstantiation()会使进程的其余部分短路。 我尝试使用postProcessAfterInitialization() ,如下所示

 public class MyProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { // Here the bean autowired fields are already set return bean; } @Override public Object postProcessAfterInitialization(Object aBean, String aBeanName) throws BeansException { Class clazz = aBean.getClass(); // only for Controllers, possibly only those with my custom annotation on them if (!clazz.isAnnotationPresent(Controller.class)) return aBean; Object proxy = Enhancer.create(clazz, new MyMethodInterceptor()); Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { field.setAccessible(true); try { // get the field and copy it over to the proxy Object objectToCopy = field.get(aBean); field.set(proxy, objectToCopy); } catch (IllegalArgumentException | IllegalAccessException e) { return aBean; } } return proxy; } } 

这个解决方案使用reflection将目标bean的所有字段复制到代理bean(对我来说有点hacky)。 但是如果那些不是我正在拦截的方法中的参数,我就无法访问HttpServletRequestHttpServletResponse对象。

是否有另一个回调我可以注入Spring bean创建逻辑,以在Spring填充其属性之前注入我自己的代理控制器? 我需要能够访问HttpServletRequestHttpServletResponse对象,无论Controller处理程序方法是否在其定义中具有它,即。 作为论点。

NB @Autowired字段也是一个代理,它用@Transactional注释,所以Spring代理它。

编辑: AOP解决方案很好地拦截方法调用,但我找不到一种方法来访问HttpServletRequestHttpServletResponse对象,如果它们不是方法参数。

我可能最终会使用HandlerInterceptorAdapter,但我希望我能用OOP来做到这一点,以免给那些不需要它的方法增加开销。

考虑到您在问题下的评论,您只需要HandlerInterceptor。

http://static.springsource.org/spring/docs/3.2.x/javadoc-api/org/springframework/web/servlet/HandlerInterceptor.html

您需要实现该接口并将其添加到您的配置中,例如:

    

此接口提供方法preHanlde,它具有请求,响应和HandlerMethod。 要检查方法是否已注释,请尝试以下操作:

 HandlerMethod method = (HandlerMethod) handler; OnlyIfXYZ customAnnotation = method.getMethodAnnotation(OnlyIfXYZ.class); 

看看Spring AOP吧 。 它拥有您所追求的设施。 对于您的示例,您可以执行以下操作:

 @Aspect @Component public class MyAspect { @Around("@annotation(path.to.your.annotation.OnlyIfXYZ)") public Object onlyIfXyz(final ProceedingJoinPoint pjp) throws Exception { //do some stuff before invoking methods annotated with @OnlyIfXYZ final Object returnValue = pjp.proceed(); //do some stuff after invoking methods annotated with @OnlyIfXYZ return returnValue; } } 

值得注意的是,Spring只会将代理应用于属于其应用程序上下文的类。 (在你的例子中出现的情况)

您还可以使用Spring AOP将参数绑定到aspect方法。 这可以通过各种方式完成,但您所追求的可能是args(paramName)

 @Aspect @Component public class MyAspect2 { @Around("@annotation(path.to.your.annotation.OnlyIfXYZ) && " + "args(..,request,..)") public Object onlyIfXyzAndHasHttpServletRequest(final ProceedingJoinPoint pjp, final HttpServletRequest request) throws Exception { //do some stuff before invoking methods annotated with @OnlyIfXYZ //do something special with your HttpServletRequest final Object returnValue = pjp.proceed(); //do some stuff after invoking methods annotated with @OnlyIfXYZ //do more special things with your HttpServletRequest return returnValue; } } 

这方面应该是你所追求的一部分。 它将代理使用@OnlyIfXYZ注释的方法,该方法也将HttpServletRequest作为参数。 此外,它将此HttpServletRequest绑定到Aspect方法作为传入参数。

我知道您可能同时使用HttpServletRequestHttpServletResponse ,因此您应该能够修改args表达式以接受请求和响应。

我认为不是,但我认为你可以在创建后自动assembly代理。

 public class MyProcessor extends InstantiationAwareBeanPostProcessorAdapter implements BeanFactoryAware { private AutowireCapableBeanFactory beanFactory; @Override public Object postProcessBeforeInstantiation(Class beanClass, String beanName) throws BeansException { // This is where I thought I would do it, but it then skips setting fields alltogether if (beanClass.isAnnotationPresent(Controller.class)) { Object proxy = Enhancer.create(beanClass, new MyInterceptor()); // autowire beanFactory.autowireBean(proxy); return proxy; } return null; } @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { this.beanFactory = (AutowireCapableBeanFactory) beanFactory; } } 

另一种方法是在postProcessAfterInitialization方法中创建一个Spring AOP代理(使用ProxyFactory )。 这样AbstractAutoProxyCreator可能很有用。 请参阅BeanNameAutoProxyCreator作为示例。 但imho,一个注释切入点(尼古拉斯回答)做同样的事情并且更简单。

InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation将使bean创建方法短路。 应用的唯一处理是postProcessAfterInitialization 。 这意味着,自动assembly不会发生,因为永远不会调用AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues 。 因此,您应该在postProcessAfterInitialization方法中手动注入或自动装入代理bean的属性。

问题:在postProcessAfterInitialization方法中移动代理逻辑是否会影响您的业务需求? 如果没有,我建议你在那里进行代理。

仅供参考:如果您没有构建API,请按照@ nicholas.hauschild的建议进行注释。