14

我有一个字段和@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可以在其上设置属性,比如自动装配字段。

我尝试使用 aInstantiationAwareBeanPostProcessor但该解决方案效果不佳,因为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)。但是,如果这些不是我正在拦截的方法中的参数,我将无权访问HttpServletRequestand对象。HttpServletResponse

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

NB@Autowired字段也是一个代理,它带有注释,@Transactional因此 Spring 代理它。

编辑: AOP 解决方案可以很好地拦截方法调用,但是如果它们还不是方法参数,我找不到访问HttpServletRequestand对象的方法。HttpServletResponse

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

4

4 回答 4

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 将参数绑定到您的方面方法。这可以通过多种方式完成,但您所追求的可能是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注释的方法也将aHttpServletRequest作为参数。HttpServletRequest此外,它会将 this作为传入参数绑定到 Aspect 方法中。

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

于 2013-03-24T01:27:03.167 回答
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,有request、response和HandlerMethod。要检查方法是否被注释,只需试试这个:

HandlerMethod method = (HandlerMethod) handler;
OnlyIfXYZ customAnnotation = method.getMethodAnnotation(OnlyIfXYZ.class);
于 2013-03-28T13:24:59.650 回答
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;

    }

}

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

于 2013-03-24T19:52:11.340 回答
2

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

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

仅供参考:如果您不构建 API,请按照 @nicholas.hauschild 的建议执行注释方法。

于 2013-03-27T11:51:58.737 回答