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中依赖的对象改成不需要事务控制的进行代替。
相关文章推荐
- SpringAOP导致@Autowired依赖注入失败
- 因Spring AOP导致@Autowired依赖注入失败的解决方法
- springboot使用aop拦截controller干一些事导致service们@Autowired全部注入失败
- Spring重复扫描导致事务失败的解决方案及深入分析
- 在Mybatis-spring中由于默认Autowired导致不能配置多个数据源的问题分析及解决
- Spring AOP源码分析(生成代理对象)
- Spring AOP 源码分析 - 创建代理对象
- spring@Transactional注解事务不回滚不起作用无效的问题处理
- 在Mybatis-spring中由于默认Autowired导致不能配置多个数据源的问题分析及解决
- spring @Autowired注入失败
- Spring源码分析----建立AopProxy代理对象和AOP拦截器的调用
- ShiroDbRealm 导致spring 事务配置无效原因分析
- Spring事务--非注解--自动代理对象[BeanNameAutoProxy]
- 特定需求下动态代理导致的Spring事务不能回滚
- spring注解源码分析--how does autowired works?
- Spring AOP 源码分析——创建代理对象
- 初识SpringBoot @Autowired注入失败
- 特定需求下动态代理导致的Spring事务不能回滚
- servlet中利用spring的注解@Autowired自动注入service失败,优雅的解决方法
- Quartz Job未实例化导致Spring @Autowired 注入为null