AOP切面时BeanPostProcessor返回Bean未被CGlib代理

背景

为了实现基于注解的AOP缓存方案,需要在Service层的方法上进行AspectJ的切面注解来实现缓存。试验后发现在其他Service类中可以正常的被切面管理起来,但是在内部(相当于this)时,这种横切没有生效,因此引入了在BeanPostProcessor的postProcessAfterInitialization方法中将spring调用CGlib代理之后的内赋值给了自身的一个引用,从而使得调用内部this时也可以使用到切面来管理。
但是有一个匪夷所思的问题就出现了,使用了4个ServiceImpl来进行操作,但是其中3个是正常生效的(即返回的是CGlib增强过的代理类),只有1个始终返回了其自身的ServiceImpl类。
postProcessAfterInitialization处理代码如下:

    public Object postProcessAfterInitialization(Object springInitedBean, String beanName) throws BeansException {
        if (springInitedBean instanceof MochaBeanSelfAware) {           
            MochaBeanSelfAware proxyBean = (MochaBeanSelfAware)springInitedBean;
            proxyBean.setSelfSpringProxy(proxyBean);
            // 用于在环境中打印, setSelf的初始过程; 便于查看log, 是否注入? 注入的是什么类?          
            System.out.println("springInitedBean is: " + springInitedBean.getClass() +" ,beanName: "+ beanName);            
            return proxyBean;
        }
        return springInitedBean;
    }

得到的结果如下(可以看到wantServiceImpl并没有返回被增强后的类):

springInitedBean is: class com.xxx.service.TopNewsServiceImpl$$EnhancerByCGLIB$$d2e9d029 ,beanName: topNewsServiceImpl
springInitedBean is: class com.xxx.service.WantServiceImpl ,beanName: wantServiceImpl

进一步分析

  1. 首先怀疑可能是没有被配置文件中CGlib的处理未生效,但是经过排查后发现修改application.xml中的配置以及强制使用annotation去强制让wantservice用CGlib来处理,打印结果还是一致。而且是没有办法解释为什么在其他类中使用wantservice是正常的(因为其他类中正常说明了应该是使用了经过增强的wantservice$$EnhancerByCGLIB),相关配置xml如下:
<aop:aspectj-autoproxy proxy-target-class="true" />
  1. 因此进一步通过debug看了spring内部的处理流程,发现了有很意思的现象,在加载bean的过程中,其加载顺序是很有意思的。关键点在于由于wantservice在其它的service中被引用倒了,因此会直接在属性填充的过程中,先进行其它依赖项的加载过程。而在加载完成后,在beanFactory的SingletonObjects中已经有了wantservice$$EnhancerByCGLIB的实例,其后续的实例化过程会有一定的差别,从而使得wantService在调用BeanPostProcessor的postProcessAfterInitialization方法先返回的是wantServiceImpl的bean,但是最后返回时,会比较SingletonObjects和Bean对象,最后将SingletonObjects中的对象进行返回。这也就是为什么postProcessAfterInitialization获取到的bean和最后返回的bean不一致的原因。

关于返回bean不一致以及postProcessAfterInitialization方法的执行时机的解释

首先看一下这篇博客的解释Spring Bean的生命周期,其中关于生命周期的图特别清晰易懂,特此引用。
如果存在侵权问题,请通知删除,谢谢
生命周期如图:

生命周期.png

我们需要特别看一下这一步:


填充步骤

就是在这个方法中,会根据依赖去先加载其它相关的Service类,造成最后在缓存中已经存在了wantServiceImpl的增强对象了。
具体在spring代码中体现为populateBean()这一方法的调用:

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) {
    // Instantiate the bean.
    BeanWrapper instanceWrapper = null;
    if (mbd.isSingleton()) {
        instanceWrapper = (BeanWrapper) this.factoryBeanInstanceCache.remove(beanName);
    }
    if (instanceWrapper == null) {
        instanceWrapper = createBeanInstance(beanName, mbd, args);
    }
    final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);
    Class beanType = (instanceWrapper != null ? instanceWrapper.getWrappedClass() : null);

    // Allow post-processors to modify the merged bean definition.
    synchronized (mbd.postProcessingLock) {
        if (!mbd.postProcessed) {
            applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
            mbd.postProcessed = true;
        }
    }

    // Eagerly cache singletons to be able to resolve circular references
    // even when triggered by lifecycle interfaces like BeanFactoryAware.
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
            isSingletonCurrentlyInCreation(beanName));
    if (earlySingletonExposure) {
        if (logger.isDebugEnabled()) {
            logger.debug("Eagerly caching bean '" + beanName +
                    "' to allow for resolving potential circular references");
        }
        addSingletonFactory(beanName, new ObjectFactory() {
            public Object getObject() throws BeansException {
                return getEarlyBeanReference(beanName, mbd, bean);
            }
        });
    }

    // Initialize the bean instance.
    Object exposedObject = bean;
    try {
        populateBean(beanName, mbd, instanceWrapper);
        exposedObject = initializeBean(beanName, exposedObject, mbd);
    }
    catch (Throwable ex) {
        if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
            throw (BeanCreationException) ex;
        }
        else {
            throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
        }
    }

    if (earlySingletonExposure) {
        Object earlySingletonReference = getSingleton(beanName, false);
        if (earlySingletonReference != null) {
            if (exposedObject == bean) {
                exposedObject = earlySingletonReference;
            }
            else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
                String[] dependentBeans = getDependentBeans(beanName);
                Set actualDependentBeans = new LinkedHashSet(dependentBeans.length);
                for (int i = 0; i < dependentBeans.length; i++) {
                    String dependentBean = dependentBeans[i];
                    if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
                        actualDependentBeans.add(dependentBean);
                    }
                }
                if (!actualDependentBeans.isEmpty()) {
                    throw new BeanCurrentlyInCreationException(beanName,
                            "Bean with name '" + beanName + "' has been injected into other beans [" +
                            StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
                            "] in its raw version as part of a circular reference, but has eventually been " +
                            "wrapped. This means that said other beans do not use the final version of the " +
                            "bean. This is often the result of over-eager type matching - consider using " +
                            "'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
                }
            }
        }
    }

    // Register bean as disposable.
    registerDisposableBeanIfNecessary(beanName, bean, mbd);

    return exposedObject;
}

因此在填充之后,运行到以下这段代码时,发现虽然经过了实例化(实例化过程中会调用postProcessAfterInitialization方法),但是所产生的exposedObject还是和一开始所赋值的Bean是相同的,因此会将从getSingleton()中得到的对象最终传递给exposedObject并作为函数结果返回。也就是之前所说的为什么在方法中所得到的Bean和最后spring所用的Bean是不同的原因

Object earlySingletonReference = getSingleton(beanName, false); 
if (earlySingletonReference != null) { 
  if (exposedObject == bean) { 
    exposedObject = earlySingletonReference; 
  }
...

同时,根据时序图可以分析发现,BeanPost中的方法之所以没有在Bean一开始加载时就打印(反而时部分依赖类先打印了BeanPost中的方法)的原因,就在于该接口只有在实例化的前后才调用。在本例中就是为什么TopNews本来是后加载的类,却先打印了相关信息的原因

befor initial: class com.xxx.service.TopNewsServiceImpl
springInitedBean is: class com.xxx.service.TopNewsServiceImpl$$EnhancerByCGLIB$$d2e9d029 ,beanName: topNewsServiceImpl

befor initial: class com.xxx.service.WantServiceImpl
springInitedBean is: class com.xxx.service.WantServiceImpl ,beanName: wantServiceImpl

具体解决方案

那么根据以上的分析,方案就很简单了,我们在postProcessAfterInitialization方法中不应该直接使用spring从参数中传来的bean,而是直接从context里面去通过singletonObjects这个Map里拿到实际需要的实例对象即可。而要获取到当前spring的上下文,也只需简单的实现ApplicationContextAware接口,并在Application.xml中将具体类配置为Bean即可。
具体实现如下,首选是xml文件

<bean id="XXBeanPostProcessor" class="com.cache.XXBeanPostProcessor" />

然后是具体类的代码

public class MochaBeanPostProcessor implements BeanPostProcessor, ApplicationContextAware   {
    public Object postProcessAfterInitialization(Object springInitedBean, String beanName) throws BeansException {
        if (springInitedBean instanceof MochaBeanSelfAware) {
            MochaBeanSelfAware originBean = (MochaBeanSelfAware) springInitedBean;
            MochaBeanSelfAware proxyBean = (MochaBeanSelfAware)getTheSingletonObject(beanName);
            originBean.setSelfSpringProxy(proxyBean);
            // 用于在环境中打印, setSelf的初始过程; 便于查看log, 是否注入? 注入的是什么类?          
            System.out.println("springInitedBean is: " + springInitedBean.getClass() +" ,beanName: "+ beanName);
            System.out.println("getTheSingletonObject is: " + getTheSingletonObject(beanName).getClass().getName());
            if(beanName!=null && beanName.contains("want")){
                System.out.println("*****origin: "+originBean.toString());
                System.out.println("*****proxy: "+proxyBean.toString());
            }
            return originBean;
        }
        return springInitedBean;
    }

    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if(bean instanceof MochaBeanSelfAware){
            System.out.println("befor initial: "+bean.getClass());
        }
        return bean;
    }
    
    
    private Object getTheSingletonObject(String beanName){
        return currentCtx.getAutowireCapableBeanFactory().getBean(beanName);
    }

    
    private ApplicationContext currentCtx;
    
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        currentCtx = applicationContext;        
    }
}

最后是最终的运行结果,可以发现我们获取倒了正确的实例

befor initial: class com.xxx.service.TopNewsServiceImpl
springInitedBean is: class com.xxx.service.TopNewsServiceImpl$$EnhancerByCGLIB$$d2e9d029 ,beanName: topNewsServiceImpl
getTheSingletonObject is: com.xxx.service.TopNewsServiceImpl$$EnhancerByCGLIB$$d2e9d029
befor initial: class com.xxx.service.product.WantServiceImpl
springInitedBean is: class com.xxx.service.product.WantServiceImpl ,beanName: wantServiceImpl
getTheSingletonObject is: com.xxx.service.WantServiceImpl$$EnhancerByCGLIB$$925dead
*****origin: in want: count is 0
*****proxy: in want: count is 0

当然对于更加深入的spring加载过程还存在理解的不够准确的地方,也感谢大家批评指正,一起进步。
仅仅记录下遇到的这个问题,以供遇到类似问题的小伙伴能更快的解决问题~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容