您的位置:首页 > 编程语言 > Java开发

Spring源码分析--@Autowired注入的不是代理对象,导致事务回滚失败(@Transactional无效)

2017-02-13 16:57 1041 查看

结论:不要在@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中依赖的对象改成不需要事务控制的进行代替。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  spring 对象 事务 源码