AOP原理分析(一)準(zhǔn)備階段和代理階段

Aop的執(zhí)行原理,我們應(yīng)該基本都了解:通過編寫切面類掐场,我們可以在指定的切入點(diǎn)處插入我們額外的代碼塊往扔,就好比代理模式中贩猎,我們可以在執(zhí)行目標(biāo)方法的前后干一些自己想干的事情。那么這是怎么樣實(shí)現(xiàn)的呢萍膛?
我們自己寫的類中的代碼是硬編碼寫死的吭服,要想改變一個已經(jīng)寫好的類,我們常見的操作就是動態(tài)代理了蝗罗,沒錯艇棕,AOP的底層就是將切入點(diǎn)所在的類創(chuàng)建成了代理對象。
我們知道Spring中的一個主要功能就是管理所有的bean對象串塑,在創(chuàng)建對象的時候沼琉,為我們提供了很多的擴(kuò)展點(diǎn),可以方便我們來干預(yù)對象的創(chuàng)建桩匪,那么Aop究竟是在哪一個擴(kuò)展點(diǎn)的地方幫我們創(chuàng)建了代理對象呢刺桃?本文就來講解下從Aop的前置準(zhǔn)備到創(chuàng)建代理對象的整個流程。

一吸祟、@EnableAspectJAutoProxy

熟悉Spring開發(fā)的模式的話瑟慈,我們都知道,開啟一個新的功能的話屋匕,我們基本需要在配置類上加上一個@EnableXxx的注解葛碧,而這類@EnableXxx的注解多半是向Spring容器中注入了影響bean創(chuàng)建生命周期的bean信息,開啟基于注解的Aop的@EnableAspectJAutoProxy注解同樣于此,他為我們導(dǎo)入了AnnotationAwareAspectJAutoProxyCreator類的定義信息过吻。我們來看下AnnotationAwareAspectJAutoProxyCreator的繼承實(shí)現(xiàn)結(jié)構(gòu)圖:

AnnotationAwareAspectJAutoProxyCreator結(jié)構(gòu)圖.png

在整個結(jié)構(gòu)圖中进泼,能干預(yù)bean的生命周期的是左上角的BeanPostProcessor接口,那么整個AOP功能的具體實(shí)現(xiàn)就是在對于InstantiationAwareBeanPostProcessor的方法的具體實(shí)現(xiàn)中纤虽,就是AbstractAutoProxyCreator類中postProcessBeforeInstantiation方法和postProcessAfterInitialization方法乳绕,我們下面就關(guān)注它的具體實(shí)現(xiàn):

二、Aop的前置準(zhǔn)備

1逼纸、AOP基礎(chǔ)準(zhǔn)備工作

首先洋措,我們來看AbstractAutoProxyCreator類中postProcessBeforeInstantiation方法的具體實(shí)現(xiàn),他實(shí)現(xiàn)的接口是InstantiationAwareBeanPostProcessor杰刽,而InstantiationAwareBeanPostProcessor的postProcessBeforeInstantiation的觸發(fā)時機(jī)是在Spring容器開始準(zhǔn)備創(chuàng)建bean之前菠发,Spring給我們提供了一個機(jī)會可以自己在這創(chuàng)建對象,而不走Spring的創(chuàng)建對象流程贺嫂,但是AOP會在此就創(chuàng)建對象嗎滓鸠?我們繼續(xù)往下看。
我們先看下前半部分代碼:

    Object cacheKey = getCacheKey(beanClass, beanName);

        if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {
            if (this.advisedBeans.containsKey(cacheKey)) {//advisedBeans已經(jīng)分析過的組件第喳,不是增強(qiáng)過的組件
                return null;
            }//所有增強(qiáng)了的組件會被緩存在advisedBeans中糜俗,如果是我們需要增強(qiáng)的bean,就放在緩存中
            //isInfrastructureClass(beanClass):判斷當(dāng)前類是否有@Aspect注解,即當(dāng)前類是否是切面;shouldSkip中解析了所有的切面類并封裝了切面類中的切面方法
            if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {// shouldSkip:如果是切面類悠抹,則跳過處
                this.advisedBeans.put(cacheKey, Boolean.FALSE);
                return null;
            }
        }

這段代碼中主要在管理advisedBeans這個集合珠月,這個集合中key為bean的名稱,value為boolean值锌钮,個人理解為這個集合的意義是桥温,key表示處理過的beanName,而value值表示這個bean應(yīng)不應(yīng)該被增強(qiáng)【不代理】梁丘。那什么樣的類是不需要增強(qiáng)的呢侵浸?這就需要關(guān)注if中的兩個條件了,一個是isInfrastructureClass(beanClass)氛谜,另一個是shouldSkip(beanClass, beanName)掏觉,滿足這兩個條件之一的都會加入advisedBeans集合,并且標(biāo)記為不增強(qiáng)值漫;一起來看看這兩個方法:

  1. isInfrastructureClass(beanClass):判斷是不是AOP的基礎(chǔ)設(shè)施類澳腹,如果是的話,就加入到advised集合中杨何,并標(biāo)記為不應(yīng)該增強(qiáng)酱塔,那么究竟什么類是AOP的基礎(chǔ)設(shè)施類呢?深入到代碼里我們發(fā)現(xiàn):分為兩大塊:一個是實(shí)現(xiàn)了Advice危虱,Pointcut羊娃,Advisor,AopInfrastructureBean這四個接口的類屬于AOP的基礎(chǔ)設(shè)施類埃跷;另一個是這個類上面標(biāo)注了@Aspect,即切面類
  2. shouldSkip(beanClass, beanName):這個方法是AOP準(zhǔn)備過程中的一個重要點(diǎn)蕊玷,因?yàn)檫@個方法中干了很多事情,為后面的AOP代理做好了鋪墊弥雹;代碼如下:
        protected boolean shouldSkip(Class<?> beanClass, String beanName) {
        // TODO: Consider optimization by caching the list of the aspect names
        List<Advisor> candidateAdvisors = findCandidateAdvisors();
        for (Advisor advisor : candidateAdvisors) {
            if (advisor instanceof AspectJPointcutAdvisor &&
                    ((AspectJPointcutAdvisor) advisor).getAspectName().equals(beanName)) {
                return true;
            }
        }
        return super.shouldSkip(beanClass, beanName);
    }

整個應(yīng)不應(yīng)該跳過的邏輯是:獲取所有的Advisor集合垃帅,然后遍歷Advisor集合,如果當(dāng)前bean的名稱和其中的一個AspectJPointcutAdvisor的aspectName相同剪勿,其實(shí)就是說明這個類是切面類贸诚,那么就會跳過。到這里窗宦,我相信有的小伙伴就會發(fā)出一個疑問了:前面不是處理過了嗎赦颇?為什么此處還是需要處理呢?其實(shí)這里我也有點(diǎn)疑問赴涵,但是它干了更多的事情,那就是創(chuàng)建并緩存了所有的Advisor對象订讼。
在findCandidateAdvisors方法的邏輯中首先是super執(zhí)行了AbstractAdvisorAutoProxyCreator中的邏輯:找到所有的Advisor的實(shí)現(xiàn)類的bean名稱髓窜,并進(jìn)行g(shù)etBean創(chuàng)建對象;其次時尋找@Aspect注解的類,然后為通知方法構(gòu)建Advisor,整個的構(gòu)建流程可以歸納如下:

  1. 拿到容器中所有的bean名稱
  2. 循環(huán)遍歷beanName,拿到beanType,判斷是不是切面類(@Aspect)
  3. 是切面類的話寄纵,就拿到類中除去標(biāo)有@Pointcut的方法鳖敷。然后遍歷方法:
  4. 把每個切面方法構(gòu)建為Advisor【InstantiationModelAwarePointcutAdvisorImpl】
  5. 處理@DeclareParents屬性注解,最終會構(gòu)建DeclareParentsAdvisor【屬于IntroductionAdvisor程拭,類級別的切入】定踱,其中對應(yīng)的Advice為DelegatePerTargetObjectIntroductionInterceptor【屬于IntroductionInterceptor,同樣也是MethodInterceptor】
  6. 緩存進(jìn)advisorsCache 【beanName--->List<Advisor>】

2恃鞋、Advice的構(gòu)建

其中崖媚,在構(gòu)建InstantiationModelAwarePointcutAdvisorImpl中,會構(gòu)建當(dāng)前增強(qiáng)方法的Advice,也就是構(gòu)造方法中的instantiateAdvice方法恤浪,最終會調(diào)用ReflectiveAspectJAdvisorFactory的getAdvice方法來構(gòu)建相應(yīng)的Advice,關(guān)鍵代碼如下:

    switch (aspectJAnnotation.getAnnotationType()) {
            case AtPointcut:
                if (logger.isDebugEnabled()) {
                    logger.debug("Processing pointcut '" + candidateAdviceMethod.getName() + "'");
                }
                return null;
            case AtAround:
                springAdvice = new AspectJAroundAdvice(
                        candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
                break;
            case AtBefore:
                springAdvice = new AspectJMethodBeforeAdvice(
                        candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
                break;
            case AtAfter:
                springAdvice = new AspectJAfterAdvice(
                        candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
                break;
            case AtAfterReturning:
                springAdvice = new AspectJAfterReturningAdvice(
                        candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
                AfterReturning afterReturningAnnotation = (AfterReturning) aspectJAnnotation.getAnnotation();
                if (StringUtils.hasText(afterReturningAnnotation.returning())) {
                    springAdvice.setReturningName(afterReturningAnnotation.returning());
                }
                break;
            case AtAfterThrowing:
                springAdvice = new AspectJAfterThrowingAdvice(
                        candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
                AfterThrowing afterThrowingAnnotation = (AfterThrowing) aspectJAnnotation.getAnnotation();
                if (StringUtils.hasText(afterThrowingAnnotation.throwing())) {
                    springAdvice.setThrowingName(afterThrowingAnnotation.throwing());
                }
                break;
            default:
                throw new UnsupportedOperationException(
                        "Unsupported advice type on method: " + candidateAdviceMethod);
        }

        // Now to configure the advice...
        springAdvice.setAspectName(aspectName);
        springAdvice.setDeclarationOrder(declarationOrder);
        String[] argNames = this.parameterNameDiscoverer.getParameterNames(candidateAdviceMethod);
        if (argNames != null) {
            springAdvice.setArgumentNamesFromStringArray(argNames);
        }
        springAdvice.calculateArgumentBindings();

最終會把切面類beanName和其中的增強(qiáng)方法Advisor存放到advisorsCache緩存中畅哑,方便后面使用。

可以說準(zhǔn)備階段水由,Spring主要就干了兩件事吧:第一件事是標(biāo)記那些不需要代理的AOP基礎(chǔ)設(shè)施類荠呐;第二件事就是尋找并創(chuàng)建容器中所有的Advisor,這一步也分為兩小步:其一砂客,去尋找并創(chuàng)建所有直接實(shí)現(xiàn)了Advisor接口的泥张;其二,處理標(biāo)注了@Aspect注解的類鞠值,并將其以aspectName及List<Advisor>的形式緩存到advisorsCache中媚创。

3、Advisor & Advice

有必要介紹下AOP中的Advisor和Advice,這兩個東西到底有什么區(qū)別和關(guān)系呢齿诉?Advice就是我們要干預(yù)正常代碼而額外加的一部分代碼邏輯筝野,其實(shí)可以理解為攔截器,而Advisor可以說成是對Advice的一種封裝吧粤剧,因?yàn)槊總€Advisor包含一個Advice歇竟,然后Advisor還應(yīng)該包括Advice增強(qiáng)的增強(qiáng)表達(dá)式,即它應(yīng)該什么時候進(jìn)行增強(qiáng)抵恋,即增強(qiáng)條件吧焕议;在Spring中,提供了兩個Advisor的子接口弧关,分別是IntroductionAdvisor和PointcutAdvisor盅安,IntroductionAdvisor提供的getClassFilter()方法和PointcutAdvisor中提供的getPointcut()方法就是各自的增強(qiáng)條件(如下圖),從增強(qiáng)條件我們也可以看出:IntroductionAdvisor是基于類級別的增強(qiáng)世囊,而PointcutAdvisor是基于方法級別或者類級別的增強(qiáng)别瞭,顯然后者方法級別的增強(qiáng)是更加細(xì)粒度的,也是我們常用的@Aspect注解的Advisor株憾。如果這么說還是過于抽象的話蝙寨,那我們拿我們常用的切面類@Aspect來進(jìn)行來進(jìn)行類比:


Advisor.png
    @Component  //切面也是容器中的組件
@Aspect //說明這是切面
public class LogAspect {

    public LogAspect() {
        System.out.println("LogAspect....");
    }


    @DeclareParents(value = "com.spring.aop.HelloService", defaultImpl = DeclareParentsTestImpl.class)
    private DeclareParentsTest declareParentsTest;

    //前置通知
    @Before("execution(* com.spring.aop.HelloService.sayHello(..))")
    public void logStart(JoinPoint joinPoint) {
        String name = joinPoint.getSignature().getName();
        System.out.println("logStart()===>" + name + "...【args:" + Arrays.asList(joinPoint.getArgs()) + "】");
    }

    //返回通知
    @AfterReturning(value = "execution(* com.spring.aop.HelloService.sayHello(..))",returning = "result")
    public void logReturn(JoinPoint joinPoint,Object result) {
        String name = joinPoint.getSignature().getName();
        System.out.println("logReturn()==>" + name + "...【args:"+ Arrays.asList(joinPoint.getArgs())+"】【result:"+result+"】");
    }

    //后置通知
    @After("execution(* com.spring.aop.HelloService.sayHello(..))")
    public void logEnd(JoinPoint joinPoint) {
        String name = joinPoint.getSignature().getName();
        System.out.println("logEnd()===>" + name + "...【args:" + Arrays.asList(joinPoint.getArgs()) + "】");
    }

    //異常通知
    @AfterThrowing(value = "execution(* com.spring.aop.HelloService.sayHello(..))",throwing = "e")
    public void logError(JoinPoint joinPoint,Exception e) {
        String name = joinPoint.getSignature().getName();
        System.out.println("logError()==>" + name + "...【args:"+ Arrays.asList(joinPoint.getArgs())+"】【result:"+e+"】");
    }
}

像上面logStart,logReturn,logEnd,logError等就可以理解為一個Advice,而@Before晒衩,@AfterReturning,@After墙歪,@AfterThrowing中的表達(dá)式就會說一個Pointcut听系,然后方法加上注解中的表達(dá)式就構(gòu)成了一個Advisor(PointcutAdvisor),相信這么說,大家應(yīng)該都能明白了吧虹菲。

三靠胜、Aop生成代理

Spring提供了兩個地方來生成Aop代理對象,下面我們來看看在哪兩個地方可以生成Aop的代理對象:

1毕源、Aop生成代理對象的第一個地方

第一個地方是在AbstractAutoProxyCreator的postProcessBeforeInstantiation方法中浪漠,Aop的準(zhǔn)備工作做完后,就會查看有沒有自定義的TargetSource脑豹,如果有符合的TargetSource的話就會在此處直接創(chuàng)建bean的代理對象郑藏,不會繼續(xù)走Spring的創(chuàng)建bean的流程,但是瘩欺,這個地方有個麻煩的點(diǎn)必盖,就是我們需要干預(yù)到AnnotationAwareAspectJAutoProxyCreator的創(chuàng)建,需要修改其Bean的定義信息俱饿,將我們自定義的TargetSourceCreator賦值給AnnotationAwareAspectJAutoProxyCreator的customTargetSourceCreators屬性歌粥,我們可以如下操作:
自定義的TargetSource:

    public class MyTargetSource implements TargetSource {
    private final Object target;

    public MyTargetSource(Object target) {
        this.target = target;
    }

    @Override
    public Class<?> getTargetClass() {
        return HelloService.class;
    }

    @Override
    public boolean isStatic() {
        return true;
    }

    @Override
    public Object getTarget() throws Exception {
        return target;
    }

    @Override
    public void releaseTarget(Object target) throws Exception {

    }
}

封裝成TargetSourceCreator:

    public class MyTargetSourceCreator implements TargetSourceCreator {
    @Override
    public TargetSource getTargetSource(Class<?> beanClass, String beanName) {
        if (beanName.equals("helloService")) {
            try {
                return new MyTargetSource(ReflectionUtils.accessibleConstructor(HelloService.class).newInstance());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }
}

實(shí)現(xiàn)BeanFactoryPostProcessor修改AnnotationAwareAspectJAutoProxyCreator的bean定義信息:

    @Component
public class TargetSourceCreatorBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        BeanDefinition bd = beanFactory.getBeanDefinition(AopConfigUtils.AUTO_PROXY_CREATOR_BEAN_NAME); // AnnotationAwareAspectJAutoProxyCreator的beanName
        bd.getPropertyValues().add("customTargetSourceCreators",new TargetSourceCreator[]{new MyTargetSourceCreator()});
    }
}

如上述配置后,HelloService的對象創(chuàng)建就會在AOP準(zhǔn)備工作做好后拍埠,通過如下代碼失驶,獲取到自定義TargetSource后直接創(chuàng)建AOP代理對象,不走這個bean后面的生命周期了:

//創(chuàng)建個代理,如果為這個類指定了targetSource會在此就生成代理直接返回了枣购,不走這個bean后面的生命周期了
    TargetSource targetSource = getCustomTargetSource(beanClass, beanName); //
        if (targetSource != null) {
            if (StringUtils.hasLength(beanName)) {
                this.targetSourcedBeans.add(beanName);
            }
            Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
            Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);
            this.proxyTypes.put(cacheKey, proxy.getClass());
            return proxy;
        }

2嬉探、Aop生成代理對象的第二個地方

另一種情況是,當(dāng)沒有為對象指定自定義的TargetSource時棉圈,Spring會在bean的對象創(chuàng)建完成后的AbstractAutoProxyCreator的postProcessAfterInitialization方法中的wrapIfNecessary方法中創(chuàng)建Aop代理對象涩堤,代碼如下:

    Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
        if (specificInterceptors != DO_NOT_PROXY) {
            this.advisedBeans.put(cacheKey, Boolean.TRUE);
            Object proxy = createProxy(//創(chuàng)建代理對象,specificInterceptors所有的增強(qiáng)器
                    bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));

兩處創(chuàng)建Aop代理對象的邏輯都是一樣的分瘾,僅僅是時機(jī)不一樣罷了胎围,Spring默認(rèn)使用的是CGLIB來創(chuàng)建代理對象的,具體的創(chuàng)建過程在此就不說了德召,后續(xù)有時間再補(bǔ)上白魂,我們需要知道的是:當(dāng)我們執(zhí)行被AOP增強(qiáng)的類時,需要回調(diào)DynamicAdvisedInterceptor這個類中的intercept方法上岗,這也是后面我們講述AOP執(zhí)行流程的入口福荸。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市肴掷,隨后出現(xiàn)的幾起案子逞姿,更是在濱河造成了極大的恐慌辞嗡,老刑警劉巖捆等,帶你破解...
    沈念sama閱讀 218,525評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件滞造,死亡現(xiàn)場離奇詭異,居然都是意外死亡栋烤,警方通過查閱死者的電腦和手機(jī)谒养,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來明郭,“玉大人买窟,你說我怎么就攤上這事∈矶ǎ” “怎么了始绍?”我有些...
    開封第一講書人閱讀 164,862評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長话侄。 經(jīng)常有香客問我亏推,道長,這世上最難降的妖魔是什么年堆? 我笑而不...
    開封第一講書人閱讀 58,728評論 1 294
  • 正文 為了忘掉前任吞杭,我火速辦了婚禮,結(jié)果婚禮上变丧,老公的妹妹穿的比我還像新娘芽狗。我一直安慰自己,他們只是感情好桥爽,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評論 6 392
  • 文/花漫 我一把揭開白布蚓峦。 她就那樣靜靜地躺著媳维,像睡著了一般。 火紅的嫁衣襯著肌膚如雪顾复。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,590評論 1 305
  • 那天炎辨,我揣著相機(jī)與錄音捕透,去河邊找鬼。 笑死碴萧,一個胖子當(dāng)著我的面吹牛乙嘀,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播破喻,決...
    沈念sama閱讀 40,330評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼虎谢,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了曹质?” 一聲冷哼從身側(cè)響起婴噩,我...
    開封第一講書人閱讀 39,244評論 0 276
  • 序言:老撾萬榮一對情侶失蹤擎场,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后几莽,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體迅办,經(jīng)...
    沈念sama閱讀 45,693評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評論 3 336
  • 正文 我和宋清朗相戀三年章蚣,在試婚紗的時候發(fā)現(xiàn)自己被綠了站欺。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,001評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡纤垂,死狀恐怖矾策,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情峭沦,我是刑警寧澤贾虽,帶...
    沈念sama閱讀 35,723評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站吼鱼,受9級特大地震影響蓬豁,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蛉抓,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評論 3 330
  • 文/蒙蒙 一庆尘、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧巷送,春花似錦驶忌、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至飞蹂,卻和暖如春几苍,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背陈哑。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評論 1 270
  • 我被黑心中介騙來泰國打工妻坝, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人惊窖。 一個月前我還...
    沈念sama閱讀 48,191評論 3 370
  • 正文 我出身青樓刽宪,卻偏偏與公主長得像,于是被迫代替她去往敵國和親界酒。 傳聞我的和親對象是個殘疾皇子圣拄,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評論 2 355

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