Spring AOP AspectJ注解 源码笔记

简单demo:

github
自定义切面MyAspect

@Aspect
@Component
@EnableAspectJAutoProxy
public class MyAspect {

    @Pointcut(value = "execution(* com.tianwen.spring.service..*.*d1(..))")
    public void pointcut() {}

    @Around(value = "pointcut()")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("before...");
        proceedingJoinPoint.proceed();
        System.out.println("after...");
    }

接口TestServiceI

public interface TestServiceI {

    void method1();

    void method2();
}

实现类TestServiceA

@Service
public class TestServiceA implements TestServiceI {

    @Override
    public void method1() {
        System.out.println("TestServiceA method1...");
    }

    @Override
    public void method2() {
        System.out.println("TestServiceA method2...");
    }
}

另一个没有实现接口的ServiceTestServiceB

@Service
public class TestServiceB {
    public void method1() {
        System.out.println("TestServiceB method1...");
    }
}

运行测试类Aop

public class Aop {

    @Test
    public void test1() {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("com.tianwen.spring");
        TestServiceI testServiceI = (TestServiceI) context.getBean("testServiceA");
        testServiceI.method1();
        System.out.println("---");
        testServiceI.method2();
        System.out.println("---");
        TestServiceB testServiceB = (TestServiceB) context.getBean("testServiceB");
        testServiceB.method1();
    }
}

运行结果

before...
TestServiceA method1...
after...
---
TestServiceA method2...
---
before...
TestServiceB method1...
after...

分析:

@EnableAspectJAutoProxy
此注解不一定要在MyAspect这个类上,标注在任何一个可以被容器扫描到的类上即可,例如标注在TestServiceB上。

  1. AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("com.tianwen.spring");
    public AnnotationConfigApplicationContext(String... basePackages) {
        this();
        scan(basePackages);
        refresh();
    }

    public AnnotationConfigApplicationContext() {
        this.reader = new AnnotatedBeanDefinitionReader(this);
        this.scanner = new ClassPathBeanDefinitionScanner(this);
    }

在无参构造器中,new AnnotatedBeanDefinitionReader()

    public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {
        Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
        Assert.notNull(environment, "Environment must not be null");
        this.registry = registry;
        this.conditionEvaluator = new ConditionEvaluator(registry, environment, null);
        AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
    }

AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry)有向容器注册ConfigurationClassPostProcessor类,这是一个BeanFactoryPostProcessor,在容器创建过程中会被调用。

        if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
            RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
            def.setSource(source);
            beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
        }
  1. AnnotationConfigApplicationContext构造器中调用的refresh()方法,实为AbstractApplicationContextrefresh()方法。
    public void refresh() throws BeansException, IllegalStateException {
        ...
        invokeBeanFactoryPostProcessors(beanFactory);
        ...
    }

关注refresh()中的invokeBeanFactoryPostProcessors(),其目的是为了执行所有的BeanFactoryPostProcessor

    protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
        PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());

        // Detect a LoadTimeWeaver and prepare for weaving, if found in the meantime
        // (e.g. through an @Bean method registered by ConfigurationClassPostProcessor)
        if (beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
            beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
            beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
        }
    }

委派类PostProcessorRegistrationDelegateinvokeBeanFactoryPostProcessors方法。
先根据类型获取到所有的BeanDefinitionRegistryPostProcessor,获取到了1中向容器注册的ConfigurationClassPostProcessor类,因为其实现了接口PriorityOrdered,优先执行。

    public static void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
        ...
        String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
        ...
        invokeBeanDefinitionRegistryPostProcessors(priorityOrderedPostProcessors, registry);
        ...

invokeBeanDefinitionRegistryPostProcessors方法中执行这些postProcessorpostProcessBeanDefinitionRegistry方法。

    private static void invokeBeanDefinitionRegistryPostProcessors(Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry) {

        for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
            postProcessor.postProcessBeanDefinitionRegistry(registry);
        }
    }
  1. ConfigurationClassPostProcessor用来处理所有扫描到的配置类,其postProcessBeanDefinitionRegistry
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
        int registryId = System.identityHashCode(registry);
        if (this.registriesPostProcessed.contains(registryId)) {
            throw new IllegalStateException(
                    "postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
        }
        if (this.factoriesPostProcessed.contains(registryId)) {
            throw new IllegalStateException(
                    "postProcessBeanFactory already called on this post-processor against " + registry);
        }
        this.registriesPostProcessed.add(registryId);

        processConfigBeanDefinitions(registry);
    }

关注processConfigBeanDefinitions方法。在处理扫描到的配置类时(配置类分为完全配置类和不完全配置类,都会在这个过程中处理)

    public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
...
            this.reader.loadBeanDefinitions(configClasses);
}

loadBeanDefinitionsForConfigurationClass方法

    public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
        TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
        for (ConfigurationClass configClass : configurationModel) {
            loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
        }
    }

loadBeanDefinitionsForConfigurationClass,在处理到MyAspect时,获取到类上标注的注解@EnableAspectJAutoProxy,该注解内有@Import(AspectJAutoProxyRegistrar.class)AspectJAutoProxyRegistrar是一个ImportBeanDefinitionRegistrar

    private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass,
            TrackedConditionEvaluator trackedConditionEvaluator) {
        ...
        loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
    }

loadBeanDefinitionsFromRegistrars调用对应的registerBeanDefinitions方法,即类AspectJAutoProxyRegistrarregisterBeanDefinitions方法。

    private void loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) {
        for (Map.Entry<ImportBeanDefinitionRegistrar, AnnotationMetadata> entry : registrars.entrySet()) {
            entry.getKey().registerBeanDefinitions(entry.getValue(), this.registry);
        }
    }

AspectJAutoProxyRegistrarregisterBeanDefinitions方法中向容器注册AnnotationAwareAspectJAutoProxyCreator,这是一个BeanPostProcessor(注意区分BeanFactoryPostProcessorBeanPostProcessor

    @Override
    public void registerBeanDefinitions(
            AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

        AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);

        AnnotationAttributes enableAspectJAutoProxy =
                AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
        if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
            AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
        }
        if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
            AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
        }
    }
  1. 回到refresh()方法中看finishBeanFactoryInitialization()
    protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
        ...
        beanFactory.preInstantiateSingletons();
    }

DefaultListableBeanFactorypreInstantiateSingletons()方法中,会对需要被创建的bean进行创建。

    public void preInstantiateSingletons() throws BeansException {
            ...
            if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
                    ...
                    getBean(beanName);
            }

这是一个容器创建bean的过程,
getBean->doGetBean->createBean
AbstractAutowireCapableBeanFactorycreateBean方法中,尝试返回代理类。

    protected Object createBean(String beanName, RootBeanDefinition mbd, Object[] args) throws BeanCreationException {
            ...
            // Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
            Object bean = resolveBeforeInstantiation(beanName, mbdToUse);

AbstractAutowireCapableBeanFactoryresolveBeforeInstantiation方法

    protected Object resolveBeforeInstantiation(String beanName, RootBeanDefinition mbd) {
                    ...
                    bean = applyBeanPostProcessorsBeforeInstantiation(targetType, beanName);

        ...
        return bean;
    }

applyBeanPostProcessorsBeforeInstantiation中执行所有的InstantiationAwareBeanPostProcessorpostProcessBeforeInstantiation方法

    protected Object applyBeanPostProcessorsBeforeInstantiation(Class<?> beanClass, String beanName) {
        for (BeanPostProcessor bp : getBeanPostProcessors()) {
            if (bp instanceof InstantiationAwareBeanPostProcessor) {
                InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
                Object result = ibp.postProcessBeforeInstantiation(beanClass, beanName);
                if (result != null) {
                    return result;
                }
            }
        }
        return null;
    }

因为InstantiationAwareBeanPostProcessorAnnotationAwareAspectJAutoProxyCreator的基类:
所以这里会执行上述向容器注册的AnnotationAwareAspectJAutoProxyCreatorpostProcessBeforeInstantiation方法,实际调用基类AbstractAutoProxyCreator的对应方法

    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
            ...
            if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
                this.advisedBeans.put(cacheKey, Boolean.FALSE);
                return null;
            }
            ...
    }

shouldSkip方法中,获取合适的通知器。

    protected boolean shouldSkip(Class<?> beanClass, String beanName) {
        List<Advisor> candidateAdvisors = findCandidateAdvisors();
        ...
    }

    protected List<Advisor> findCandidateAdvisors() {
        // Add all the Spring advisors found according to superclass rules.
        List<Advisor> advisors = super.findCandidateAdvisors();
        // Build Advisors for all AspectJ aspects in the bean factory.
        advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
        return advisors;
    }

BeanFactoryAspectJAdvisorsBuilderbuildAspectJAdvisors中,取得所有的beanName,使用isAspect方法判断其是否属于切面,取得通知器。

    public List<Advisor> buildAspectJAdvisors() {
                    ...
                    for (String beanName : beanNames) {
                        ...
                        if (this.advisorFactory.isAspect(beanType)) {
                                ...
                                List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);

AbstractAspectJAdvisorFactory中的isAspect方法

    @Override
    public boolean isAspect(Class<?> clazz) {
        return (hasAspectAnnotation(clazz) && !compiledByAjc(clazz));
    }

ReflectiveAspectJAdvisorFactory中的getAdvisors方法。取得切面上的切点与通知器。通知器类型为InstantiationModelAwarePointcutAdvisorImpl

    public List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory aspectInstanceFactory) {
            ...
            Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, advisors.size(), aspectName);
    }

    public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aspectInstanceFactory,
            int declarationOrderInAspect, String aspectName) {
        ...
        AspectJExpressionPointcut expressionPointcut = getPointcut(
                candidateAdviceMethod, aspectInstanceFactory.getAspectMetadata().getAspectClass());
        ...
        return new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod,
                this, aspectInstanceFactory, declarationOrderInAspect, aspectName);

    }

此时,自定义的MyAspect上的@Aspect注解生效,被判断为一个通知器,加入到通知器List中。
再回到容器创建bean的过程中,在AbstractAutowireCapableBeanFactory createBean方法,doCreateBean方法,initializeBean方法,applyBeanPostProcessorsAfterInitialization方法。执行BeanPostProcessor的后置方法。

    protected Object createBean(String beanName, RootBeanDefinition mbd, Object[] args) throws BeanCreationException {
        ...
        Object beanInstance = doCreateBean(beanName, mbdToUse, args);
    }

    protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)
            throws BeanCreationException {
                ...
                exposedObject = initializeBean(beanName, exposedObject, mbd);
    }

    protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd) {
            ...
            wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
    }

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

所以这里会执行上述向容器注册的AnnotationAwareAspectJAutoProxyCreator的的postProcessAfterInitialization方法,实际调用基类AbstractAutoProxyCreator的对应方法

    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
                ...
                return wrapIfNecessary(bean, beanName, cacheKey);
    }

wrapIfNecessary方法中,getAdvicesAndAdvisorsForBeanfindEligibleAdvisors内取得合适的通知器。判断这些通知器是否适用当前bean。

    protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
        ...
        Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
    }

    protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, TargetSource targetSource) {
        ...
        List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);
    }

    protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
        ...
        List<Advisor> candidateAdvisors = findCandidateAdvisors();
        List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
    }

    protected List<Advisor> findAdvisorsThatCanApply(
            List<Advisor> candidateAdvisors, Class<?> beanClass, String beanName) {
            ...
            return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);
    }

最终在AopUtilsfindAdvisorsThatCanApplycanApply方法中,获取到切点(AspectJExpressionPointcut)的方法匹配器,对当前bean的方法逐个匹配,一旦匹配到,返回true。外层会返回对应的通知器,进而可得知需要为当前bean创建代理对象。是否需要为bean创建aop的代理对象,就是由此判断得知。

    public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {
        MethodMatcher methodMatcher = pc.getMethodMatcher();
            for (Method method : methods) {
                if ((introductionAwareMethodMatcher != null &&
                        introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions)) ||
                        methodMatcher.matches(method, targetClass)) {
                    return true;
                }
            }

        return false;
    }

createProxy中使用ProxyFactory创建代理对象。

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

而关于ProxyFactory如何创建代理对象,调用代理对象时,切面上的方法如何工作,与之前分析的ProxyFactoryBean流程大体一致。

总结:

  • 容器创建过程中,默认会向容器中注册ConfigurationClassPostProcessor类,一个BeanFactoryPostProcessor,以用来处理所有扫描到的配置类。
  • 容器创建过程中,在处理到被@EnableAspectJAutoProxy标注的类,解析该类注解时,对于@EnableAspectJAutoProxy上的@Import(AspectJAutoProxyRegistrar.class)的处理。会向容器中注册AnnotationAwareAspectJAutoProxyCreator,一个BeanPostProcessor。(注意区分BeanFactoryPostProcessorBeanPostProcessor
  • 容器完成创建后,会对不需要延迟注册的bean提前完成注册。
  • 在bean的创建过程中,在AbstractAutowireCapableBeanFactorycreateBeanresolveBeforeInstantiation方法中,执行beanPostProcessorpostProcessBeforeInstantiation方法时。上一步往容器中注册的AnnotationAwareAspectJAutoProxyCreator会工作,在执行其从基类AbstractAutoProxyCreator继承过来的postProcessBeforeInstantiation方法时,收集容器中所有的bean通知器。关于是否为通知器的判断方法在AbstractAspectJAdvisorFactoryisAspect方法中。
  • 在bean的创建过程中,在AbstractAutowireCapableBeanFactorydoCreateBeaninitializeBean方法中,执行所有beanPostProcessorpostProcessAfterInitialization方法。所以,AbstractAutoProxyCreatorpostProcessAfterInitialization方法会被执行,最终执行的是wrapIfNecessary方法。在该方法内,获取到配置的通知器,对每一个需要向容器注册的bean,用通知器上的切点的匹配规则对当前bean的方法逐个匹配,如果至少有一个方法匹配,说明需要创建代理对象,则使用ProxyFactory完成代理对象的创建(与之前分析的ProxyFactoryBean流程大体一致)。如果全部不匹配,返回普通bean。
  • 最终在方法调用时,如果是普通bean只是简单的方法调用,但如果是代理对象,方法调用过程中还伴随着织入的逻辑。至于这些逻辑如何工作。与之前分析的ProxyFactoryBean流程大体一致。
  • 目前看起来,上述方式的springAop配置,只是仅仅使用到了AspectJ的注解。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342