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

结论:不要在@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 Cloud
    原文地址: https://blog.csdn.net/tragedyxd/article/details/55050520
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞