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

时间:2013-03-24 01:14:20

标签: java spring spring-mvc spring-aop

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

例如,

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

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

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

N.B @Autowired字段也是一个代理,它使用@Transactional注释,因此Spring代理它。

修改的AOP溶液很好地工作,用于截取方法调用,但我不能找到一种方法来访问HttpServletRequestHttpServletResponse的对象,如果他们AREN已经是方法论证了。

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

4 个答案:

答案 0 :(得分:7)

看看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注释的方法, ALSO HttpServletRequest作为参数。此外,它会将此HttpServletRequest绑定到Aspect方法中作为传入参数。

我了解您可能同时关注HttpServletRequestHttpServletResponse,因此您应该能够修改args表达式以接受请求和响应。

答案 1 :(得分:6)

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

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

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

<mvc:interceptors>
    <bean id="customInterceptor" class="com.example.interceptors.CustomInterceptor"/>
</mvc:interceptors>

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

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

答案 2 :(得分:2)

我认为不是,但我认为您可以在创建代理后自动装配代理。

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;

    }

}

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

答案 3 :(得分:2)

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

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

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