我有@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)。但是如果这些对象不是我正在拦截的方法中的参数,我就无法访问HttpServletRequest
和HttpServletResponse
个对象。
我是否可以在Spring填充其属性之前注入Spring bean创建逻辑以注入我自己的代理控制器? 我需要能够访问HttpServletRequest
和HttpServletResponse
对象,无论Controller处理程序方法是否在其定义中具有它,即。作为参数。
N.B @Autowired
字段也是一个代理,它使用@Transactional
注释,因此Spring代理它。
修改的AOP溶液很好地工作,用于截取方法调用,但我不能找到一种方法来访问HttpServletRequest
和HttpServletResponse
的对象,如果他们AREN已经是方法论证了。
我可能最终会使用HandlerInterceptorAdapter,但我希望我能用OOP来做到这一点,以免给那些不需要它的方法增加开销。
答案 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方法中作为传入参数。
我了解您可能同时关注HttpServletRequest
和HttpServletResponse
,因此您应该能够修改args
表达式以接受请求和响应。
答案 1 :(得分:6)
考虑到您在问题下的评论,您只需要HandlerInterceptor。
您需要实现该界面并将其添加到您的配置中,例如:
<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的建议进行注释。