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

扫码关注公众号:Java 技术驿站

发送:vip
将链接复制到本浏览器,永久解锁本站全部文章

【公众号:Java 技术驿站】 【加作者微信交流技术,拉技术群】
免费领取 2000+ 道 Java 面试题

结论:不要在@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中依赖的对象改成不需要事务控制的进行代替。


来源:http://ddrv.cn/a/88268

赞(0) 打赏
版权归原创作者所有,任何形式的转载请联系博主:daming_90:Java 技术驿站 » Spring源码分析–@Autowired注入的不是代理对象,导致事务回滚失败(@Transactional无效)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏