AOP切面時(shí)BeanPostProcessor返回Bean未被CGlib代理

背景

為了實(shí)現(xiàn)基于注解的AOP緩存方案想幻,需要在Service層的方法上進(jìn)行AspectJ的切面注解來(lái)實(shí)現(xiàn)緩存配深。試驗(yàn)后發(fā)現(xiàn)在其他Service類中可以正常的被切面管理起來(lái),但是在內(nèi)部(相當(dāng)于this)時(shí),這種橫切沒有生效磨隘,因此引入了在BeanPostProcessor的postProcessAfterInitialization方法中將spring調(diào)用CGlib代理之后的內(nèi)賦值給了自身的一個(gè)引用记盒,從而使得調(diào)用內(nèi)部this時(shí)也可以使用到切面來(lái)管理掷豺。
但是有一個(gè)匪夷所思的問(wèn)題就出現(xiàn)了龄毡,使用了4個(gè)ServiceImpl來(lái)進(jìn)行操作,但是其中3個(gè)是正常生效的(即返回的是CGlib增強(qiáng)過(guò)的代理類)召庞,只有1個(gè)始終返回了其自身的ServiceImpl類岛心。
postProcessAfterInitialization處理代碼如下:

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

得到的結(jié)果如下(可以看到wantServiceImpl并沒有返回被增強(qiáng)后的類):

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

進(jìn)一步分析

  1. 首先懷疑可能是沒有被配置文件中CGlib的處理未生效,但是經(jīng)過(guò)排查后發(fā)現(xiàn)修改application.xml中的配置以及強(qiáng)制使用annotation去強(qiáng)制讓wantservice用CGlib來(lái)處理篮灼,打印結(jié)果還是一致忘古。而且是沒有辦法解釋為什么在其他類中使用wantservice是正常的(因?yàn)槠渌愔姓Uf(shuō)明了應(yīng)該是使用了經(jīng)過(guò)增強(qiáng)的wantservice$$EnhancerByCGLIB),相關(guān)配置xml如下:
<aop:aspectj-autoproxy proxy-target-class="true" />
  1. 因此進(jìn)一步通過(guò)debug看了spring內(nèi)部的處理流程穿稳,發(fā)現(xiàn)了有很意思的現(xiàn)象存皂,在加載bean的過(guò)程中,其加載順序是很有意思的逢艘。關(guān)鍵點(diǎn)在于由于wantservice在其它的service中被引用倒了旦袋,因此會(huì)直接在屬性填充的過(guò)程中,先進(jìn)行其它依賴項(xiàng)的加載過(guò)程它改。而在加載完成后疤孕,在beanFactory的SingletonObjects中已經(jīng)有了wantservice$$EnhancerByCGLIB的實(shí)例,其后續(xù)的實(shí)例化過(guò)程會(huì)有一定的差別央拖,從而使得wantService在調(diào)用BeanPostProcessor的postProcessAfterInitialization方法先返回的是wantServiceImpl的bean祭阀,但是最后返回時(shí),會(huì)比較SingletonObjects和Bean對(duì)象鲜戒,最后將SingletonObjects中的對(duì)象進(jìn)行返回专控。這也就是為什么postProcessAfterInitialization獲取到的bean和最后返回的bean不一致的原因。

關(guān)于返回bean不一致以及postProcessAfterInitialization方法的執(zhí)行時(shí)機(jī)的解釋

首先看一下這篇博客的解釋Spring Bean的生命周期遏餐,其中關(guān)于生命周期的圖特別清晰易懂伦腐,特此引用。
如果存在侵權(quán)問(wèn)題失都,請(qǐng)通知?jiǎng)h除柏蘑,謝謝
生命周期如圖:

生命周期.png

我們需要特別看一下這一步:


填充步驟

就是在這個(gè)方法中,會(huì)根據(jù)依賴去先加載其它相關(guān)的Service類粹庞,造成最后在緩存中已經(jīng)存在了wantServiceImpl的增強(qiáng)對(duì)象了咳焚。
具體在spring代碼中體現(xiàn)為populateBean()這一方法的調(diào)用:

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;
}

因此在填充之后,運(yùn)行到以下這段代碼時(shí)庞溜,發(fā)現(xiàn)雖然經(jīng)過(guò)了實(shí)例化(實(shí)例化過(guò)程中會(huì)調(diào)用postProcessAfterInitialization方法)革半,但是所產(chǎn)生的exposedObject還是和一開始所賦值的Bean是相同的,因此會(huì)將從getSingleton()中得到的對(duì)象最終傳遞給exposedObject并作為函數(shù)結(jié)果返回。也就是之前所說(shuō)的為什么在方法中所得到的Bean和最后spring所用的Bean是不同的原因

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

同時(shí)督惰,根據(jù)時(shí)序圖可以分析發(fā)現(xiàn)不傅,BeanPost中的方法之所以沒有在Bean一開始加載時(shí)就打印(反而時(shí)部分依賴類先打印了BeanPost中的方法)的原因赏胚,就在于該接口只有在實(shí)例化的前后才調(diào)用。在本例中就是為什么TopNews本來(lái)是后加載的類商虐,卻先打印了相關(guān)信息的原因

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

具體解決方案

那么根據(jù)以上的分析觉阅,方案就很簡(jiǎn)單了,我們?cè)趐ostProcessAfterInitialization方法中不應(yīng)該直接使用spring從參數(shù)中傳來(lái)的bean秘车,而是直接從context里面去通過(guò)singletonObjects這個(gè)Map里拿到實(shí)際需要的實(shí)例對(duì)象即可典勇。而要獲取到當(dāng)前spring的上下文,也只需簡(jiǎn)單的實(shí)現(xiàn)ApplicationContextAware接口叮趴,并在Application.xml中將具體類配置為Bean即可割笙。
具體實(shí)現(xiàn)如下,首選是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);
            // 用于在環(huán)境中打印, setSelf的初始過(guò)程; 便于查看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;        
    }
}

最后是最終的運(yùn)行結(jié)果眯亦,可以發(fā)現(xiàn)我們獲取倒了正確的實(shí)例

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

當(dāng)然對(duì)于更加深入的spring加載過(guò)程還存在理解的不夠準(zhǔn)確的地方伤溉,也感謝大家批評(píng)指正,一起進(jìn)步妻率。
僅僅記錄下遇到的這個(gè)問(wèn)題乱顾,以供遇到類似問(wèn)題的小伙伴能更快的解決問(wèn)題~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市宫静,隨后出現(xiàn)的幾起案子走净,更是在濱河造成了極大的恐慌,老刑警劉巖孤里,帶你破解...
    沈念sama閱讀 211,639評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件伏伯,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡捌袜,警方通過(guò)查閱死者的電腦和手機(jī)说搅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)琢蛤,“玉大人蜓堕,你說(shuō)我怎么就攤上這事〔┢洌” “怎么了套才?”我有些...
    開封第一講書人閱讀 157,221評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)慕淡。 經(jīng)常有香客問(wèn)我背伴,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,474評(píng)論 1 283
  • 正文 為了忘掉前任傻寂,我火速辦了婚禮息尺,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘疾掰。我一直安慰自己搂誉,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,570評(píng)論 6 386
  • 文/花漫 我一把揭開白布静檬。 她就那樣靜靜地躺著炭懊,像睡著了一般。 火紅的嫁衣襯著肌膚如雪拂檩。 梳的紋絲不亂的頭發(fā)上侮腹,一...
    開封第一講書人閱讀 49,816評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音稻励,去河邊找鬼父阻。 笑死,一個(gè)胖子當(dāng)著我的面吹牛望抽,可吹牛的內(nèi)容都是我干的加矛。 我是一名探鬼主播,決...
    沈念sama閱讀 38,957評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼糠聪,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼荒椭!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起舰蟆,我...
    開封第一講書人閱讀 37,718評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤趣惠,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后身害,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體味悄,經(jīng)...
    沈念sama閱讀 44,176評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,511評(píng)論 2 327
  • 正文 我和宋清朗相戀三年塌鸯,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了侍瑟。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,646評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡丙猬,死狀恐怖涨颜,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情茧球,我是刑警寧澤庭瑰,帶...
    沈念sama閱讀 34,322評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站抢埋,受9級(jí)特大地震影響弹灭,放射性物質(zhì)發(fā)生泄漏督暂。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,934評(píng)論 3 313
  • 文/蒙蒙 一穷吮、第九天 我趴在偏房一處隱蔽的房頂上張望逻翁。 院中可真熱鬧,春花似錦捡鱼、人聲如沸八回。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)辽社。三九已至,卻和暖如春翘鸭,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背戳葵。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工就乓, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人拱烁。 一個(gè)月前我還...
    沈念sama閱讀 46,358評(píng)論 2 360
  • 正文 我出身青樓生蚁,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親戏自。 傳聞我的和親對(duì)象是個(gè)殘疾皇子邦投,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,514評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容