结论:不要在@Configuration类中的@Bean中直接注入需要成为代理对象的对象
问题分析
我们都知道Spring的事务控制是使用AOP实现的,所以@Autowired注入的对象必须是一个代理对象(类似:$Proxy89@10644)。
但是我们在使用@Bean进行配置的时候,很可能写出如下代码:
@Bean(name = "myShiroRealm")
public SysUserRealm myShiroRealm() {
SysUserRealm realm = new SysUserRealm();
realm.setCacheManager(getEhCacheManager());
return realm;
}
@Bean(name = "securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(SysUserRealm sysUserRealm) {
EhCacheManager ehCacheManager = getEhCacheManager();
DefaultWebSecurityManager dwsm = new DefaultWebSecurityManager();
dwsm.setRealm(sysUserRealm);
dwsm.setCacheManager(ehCacheManager);
return dwsm;
}
这是一段配置Shiro的代码,简单点说就是,我们定义了2个Bean,下面的Bean依赖上面的Bean。看起来没什么问题,
程序也正常运行良好。再看一下SysUserRealm的代码:
public class SysUserRealm extends AuthorizingRealm {
@Autowired
private SysUserService sysUserService;
@Autowired
private CustomCredentialsMatcher customCredentialsMatcher;
...
程序运行,sysUserService也能被正常的注入,但是注入的是普通对象,并不是代理对象,而我在测试事务
控制的时候发现,回滚失败。通过查找创建该Bean的调用栈跟踪到如下Spring创建Bean源码:
AbstractAutowireCapableBeanFactory.java
@Override
public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
throws BeansException {
Object result = existingBean;
for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) {
result = beanProcessor.postProcessAfterInitialization(result, beanName);
if (result == null) {
return result;
}
}
return result;
}
getBeanPostProcessors()返回的是List(),在Bean创建的时候进行处理,因为此时
Object result = existingBean;
这行代码已经是通过类的默认构造器进行了实例化的,得到的就是普通对象。再通过一系列的BeanPostProcessor处理,就会得到代理对象的,
其中关键的BeanPostProcessor就是AnnotationAwareAspectJAutoProxyCreator(postProcessAfterInitialization方法源码位于它的父类AbstractAutoProxyCreator中)
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (!this.earlyProxyReferences.contains(cacheKey)) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
// 再进入wrapIfNecessary方法
/**
* Wrap the given bean if necessary, i.e. if it is eligible for being proxied.
* @param bean the raw bean instance
* @param beanName the name of the bean
* @param cacheKey the cache key for metadata access
* @return a proxy wrapping the bean, or the raw bean instance as-is
*/
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
if (beanName != null && this.targetSourcedBeans.contains(beanName)) {
return bean;
}
if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
return bean;
}
if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
// Create proxy if we have advice.
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
可以看到这里是创建代理对象的关键,如果没有这个BeanPostProcessor就不能创建代理对象。通过和其他的类进行比较,发现
如果直接把依赖对象写在方法参数中,会导致该对象创建过早,那时候BeanPostProcessor中还没有AnnotationAwareAspectJAutoProxyCreator,
所以拿到的就是普通对象。
而如果要获得代理对象也很简单只需要这样:
// @Bean(name = "myShiroRealm") 注意这里注释掉了
public SysUserRealm myShiroRealm() {
SysUserRealm realm = new SysUserRealm();
realm.setCacheManager(getEhCacheManager());
return realm;
}
@Bean(name = "securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(EhCacheManager ehCacheManager) { // 注意这里不接受SysUserRealm参数
DefaultWebSecurityManager dwsm = new DefaultWebSecurityManager();
dwsm.setRealm(myShiroRealm());
dwsm.setCacheManager(ehCacheManager);
return dwsm;
}
因为我们接受了EhCacheManager参数,所以该对象也只能是普通对象,不是代理对象,可以从日志中看到
2017-02-13 16:06:27.271 INFO 6996 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'getEhCacheManager' of type [class org.apache.shiro.cache.ehcache.EhCacheManager] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
到此问题解决。
更新:2017年2月14日 17:38:56
因为
// @Bean(name = "myShiroRealm") 注意这里注释掉了
public SysUserRealm myShiroRealm() {
@Bean注释掉了,导致SysUserRealm中的@Autowired没有被解析,所以就没有注入相应的对象。
哎~现在只能将SysUserRealm中依赖的对象改成不需要事务控制的进行代替。