Spring源码阅读--aop实现原理分析

悠悠 2023-10-13 08:12 118阅读 0赞

aop实现原理简介

这里分析的是,在spring中是如何基于动态代理的思想实现aop的。为了方便了解接下来的源码分析,这里简单化了一个流程图分析aop的基本实现思想。

在这里插入图片描述

so,基于上面的流程,一步步分析spring源码中的aop实现方式。

采用一个简单的aop例子,利用基于注解配置方式的切面配置,分析一个简单的Before AOP例子。在spring boot下运行以下简单例子。

AOP的advisor和advice配置。

  1. @Component
  2. @Aspect
  3. public class AopConfig {
  4. @Pointcut("execution(* com.garine.debug.testcase.model.AopObject..*(..))")
  5. public void mypoint(){
  6. //切面定义
  7. }
  8. @Before("mypoint()")
  9. public void doAround() throws Throwable {
  10. System.out.println("before logic");
  11. }
  12. }

AopObject,被代理拦截对象。

  1. @Component
  2. public class AopObject {
  3. public void aoped(){
  4. System.out.println("logic");
  5. }
  6. }

代理实现的处理器(BeanPostProcessor)

首先是第一步内容,对我们在AopConfig中的AOP配置内容进行解析并且保存到BeanFactory中,这个过程就是解析保存切面信息。

代理实现的源头–AnnotationAwareAspectJAutoProxyCreator

经过一遍的代码跟踪,我了解到注解方式的AOP配置,都离不开一个类–AnnotationAwareAspectJAutoProxyCreator,这个类继承了BeanPostProcessor接口,我们都知道BeanPostProcessor的实现类有多个执行处理节点,其中一个执行节点就是在Bean实例化之后。也就是在这个时机AnnotationAwareAspectJAutoProxyCreator拦截bean的初始化过程,根据提前解析得到的切面信息,对bean的方法进行尝试适配,如果有匹配则需要进行代理创建。

这里先分析的就是AnnotationAwareAspectJAutoProxyCreator,在bean实例化第一次查询所有切面信息时,就会解析保存Aop的信息到实例中,跟踪以下代码。

  • AbstractApplicationContext#refresh (上下文初始化主干方法)

    • AbstractApplicationContext#registerBeanPostProcessors (执行实例化并保存所有实现BeanPostProcessor接口的类

按照上面的逻辑,registerBeanPostProcessors 会比一般的bean实例化逻辑要早执行,因此我们接下来只需要分析AnnotationAwareAspectJAutoProxyCreator的初始化过程。

AnnotationAwareAspectJAutoProxyCreator的继承结构

在这里插入图片描述

通过上图可以知道,AnnotationAwareAspectJAutoProxyCreator是继承了BeanfactoryAware接口,所以在实例化时,会执行setFactory方法。而所有切面信息解析的执行者BeanFactoryAspectJAdvisorsBuilderAdapter初始化的时机也是在setFactory方法。

跟踪代码如下。

  • AbstractAdvisorAutoProxyCreator#setBeanFactory

    • AnnotationAwareAspectJAutoProxyCreator#initBeanFactory

在这个方法里面会新建一个BeanFactoryAspectJAdvisorsBuilderAdapter,这个对象会根据Beanfactory内的aop配置信息,进行解析保存。但是需要注意,此时虽然新建了BeanFactoryAspectJAdvisorsBuilderAdapter对象.但是此时还不会马上解析aop配置,需要在第一次个普通bean实例化时才执行解析aop配置。解析的方法就是

BeanFactoryAspectJAdvisorsBuilder#buildAspectJAdvisors,会在初次执行AnnotationAwareAspectJAutoProxyCreator调用postProcessBeforeInitialization时开始执行。

  1. protected void initBeanFactory(ConfigurableListableBeanFactory beanFactory) {
  2. super.initBeanFactory(beanFactory);
  3. //aspectJAdvisorsBuilder#buildAspectJAdvisors就是解析配置入口
  4. this.aspectJAdvisorsBuilder =
  5. new BeanFactoryAspectJAdvisorsBuilderAdapter(beanFactory, this.aspectJAdvisorFactory);
  6. }

代理对象(Proxy)的创建

解析并缓存切面

上面提到继承结构图中,AnnotationAwareAspectJAutoProxyCreator是实现了InstantiationAwareBeanPostProcessor接口的,InstantiationAwareBeanPostProcessor接口定义的postProcessBeforeInitialization方法是一个可以对已经注入依赖属性的bean对象实例进行编辑操作的接口,会在

  • AbstractAutowireCapableBeanFactory#doCreateBean

    • AbstractAutowireCapableBeanFactory#initializeBean(String, Object, RootBeanDefinition)

      • AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsBeforeInstantiation

方法中执行InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation,初次初始化缓存切面信息的话就是在这个方法里面。。具体的调用链如上所示。这里的postProcessBeforeInstantiation方法实际上是AnnotationAwareAspectJAutoProxyCreator的实例进行调用,AnnotationAwareAspectJAutoProxyCreator实现InstantiationAwareBeanPostProcessor接口。

下面进入InstantiationAwareBeanPostProcessor#postProcessBeforeInitialization方法分析代码。

  • AbstractAutoProxyCreator#postProcessBeforeInstantiation

    • AspectJAwareAdvisorAutoProxyCreator#shouldSkip (关键代码)

进入如下代码AbstractAutoProxyCreator,这个实例也就是之前一开始初始化的AnnotationAwareAspectJAutoProxyCreator实例,进入实例的shouldSkip 方法,

  1. @Override
  2. protected boolean shouldSkip(Class<?> beanClass, String beanName) {
  3. // TODO: Consider optimization by caching the list of the aspect names
  4. //预先解析缓存切面信息
  5. List<Advisor> candidateAdvisors = findCandidateAdvisors();
  6. for (Advisor advisor : candidateAdvisors) {
  7. if (advisor instanceof AspectJPointcutAdvisor) {
  8. if (((AbstractAspectJAdvice) advisor.getAdvice()).getAspectName().equals(beanName)) {
  9. return true;
  10. }
  11. }
  12. }
  13. return super.shouldSkip(beanClass, beanName);
  14. }
  • AnnotationAwareAspectJAutoProxyCreator#findCandidateAdvisors

方法findCandidateAdvisors代码如下,这里是预先解析缓存所有切面advisor信息,注意这一步操作是在AbstractAutoProxyCreator#postProcessBeforeInitialization处理,也就是开头提到的切面解析操作,解析完成就进行缓存。

  1. @Override
  2. protected List<Advisor> findCandidateAdvisors() {
  3. // Add all the Spring advisors found according to superclass rules.
  4. List<Advisor> advisors = super.findCandidateAdvisors();
  5. // Build Advisors for all AspectJ aspects in the bean factory.
  6. //这里就是前面提到的BeanFactoryAspectJAdvisorsBuilder解析所有切面信息的调用点
  7. advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
  8. return advisors;
  9. }

然后继续在这里先提前看一下是如何解析aop配置的。跟踪BeanFactoryAspectJAdvisorsBuilder#buildAspectJAdvisors

  1. public List<Advisor> buildAspectJAdvisors() {
  2. List<String> aspectNames = null;
  3. synchronized (this) {
  4. aspectNames = this.aspectBeanNames;
  5. if (aspectNames == null) {
  6. List<Advisor> advisors = new LinkedList<Advisor>();
  7. aspectNames = new LinkedList<String>();
  8. //查询出Beanfactory中所有已经注册的BeanName
  9. String[] beanNames =
  10. BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, Object.class, true, false);
  11. for (String beanName : beanNames) {
  12. if (!isEligibleBean(beanName)) {
  13. continue;
  14. }
  15. // We must be careful not to instantiate beans eagerly as in this
  16. // case they would be cached by the Spring container but would not
  17. // have been weaved
  18. Class<?> beanType = this.beanFactory.getType(beanName);
  19. if (beanType == null) {
  20. continue;
  21. }
  22. //判断Bean是否是切面Bean,isAspect方法判断[标注1]
  23. if (this.advisorFactory.isAspect(beanType)) {
  24. aspectNames.add(beanName);
  25. AspectMetadata amd = new AspectMetadata(beanType, beanName);
  26. if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {
  27. MetadataAwareAspectInstanceFactory factory =
  28. new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);
  29. //解析aop class的配置,包返回Advisor对象[标注2]
  30. List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);
  31. if (this.beanFactory.isSingleton(beanName)) {
  32. this.advisorsCache.put(beanName, classAdvisors);
  33. }
  34. else {
  35. this.aspectFactoryCache.put(beanName, factory);
  36. }
  37. advisors.addAll(classAdvisors);
  38. }
  39. else {
  40. // Per target or per this.
  41. if (this.beanFactory.isSingleton(beanName)) {
  42. throw new IllegalArgumentException("Bean with name '" + beanName +
  43. "' is a singleton, but aspect instantiation model is not singleton");
  44. }
  45. MetadataAwareAspectInstanceFactory factory =
  46. new PrototypeAspectInstanceFactory(this.beanFactory, beanName);
  47. this.aspectFactoryCache.put(beanName, factory);
  48. advisors.addAll(this.advisorFactory.getAdvisors(factory));
  49. }
  50. }
  51. }
  52. this.aspectBeanNames = aspectNames;
  53. return advisors;
  54. }
  55. }
  56. if (aspectNames.isEmpty()) {
  57. return Collections.emptyList();
  58. }
  59. List<Advisor> advisors = new LinkedList<Advisor>();
  60. for (String aspectName : aspectNames) {
  61. List<Advisor> cachedAdvisors = this.advisorsCache.get(aspectName);
  62. if (cachedAdvisors != null) {
  63. advisors.addAll(cachedAdvisors);
  64. }
  65. else {
  66. MetadataAwareAspectInstanceFactory factory = this.aspectFactoryCache.get(aspectName);
  67. advisors.addAll(this.advisorFactory.getAdvisors(factory));
  68. }
  69. }
  70. return advisors;
  71. }

**[标注1]如何判断类是否是aop切面配置类? **

通过以下代码。

  1. @Override
  2. public boolean isAspect(Class<?> clazz) {
  3. return (hasAspectAnnotation(clazz) && !compiledByAjc(clazz));
  4. }
  5. private boolean hasAspectAnnotation(Class<?> clazz) {
  6. //包含@Aspect注解
  7. return (AnnotationUtils.findAnnotation(clazz, Aspect.class) != null);
  8. }

[标注2]如何解析为Advisor对象?

  • ReflectiveAspectJAdvisorFactory#getAdvisors 遍历所有没被@PointCut注解标注的方法,也就是遍历切面内容方法

    • ReflectiveAspectJAdvisorFactory#getAdvisor 处理所有没被@PointCut注解标注的方法,候选切面内容方法

代码如下。

  1. @Override
  2. public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aspectInstanceFactory,
  3. int declarationOrderInAspect, String aspectName) {
  4. validate(aspectInstanceFactory.getAspectMetadata().getAspectClass());
  5. //解析判断候选方法是否有@Before,@After,@Around等注解,如果有,就继续执行新建Advisor对象。
  6. AspectJExpressionPointcut expressionPointcut = getPointcut(
  7. candidateAdviceMethod, aspectInstanceFactory.getAspectMetadata().getAspectClass());
  8. if (expressionPointcut == null) {
  9. return null;
  10. }
  11. //创建advisor
  12. return new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod,
  13. this, aspectInstanceFactory, declarationOrderInAspect, aspectName);
  14. }

最终循环解析,@Before,@After,@Around等标注的方法都会新建一个Advisor对象。新建的Advisor对象都保存在BeanFactoryAspectJAdvisorsBuilder#advisorsCache中,当AnnotationAwareAspectJAutoProxyCreator拦截bean的创建过程时,从这里面适配是否有切面可用。

这里解析得到的Advisor,大概有以下信息。下面的信息中,并没有对@PointCut注解做处理,pointCut属性只得出一个”mypoint()”,此时还不知道Advisor实际对应的拦截表达式。

在这里插入图片描述

拦截表达式还是空的,会在AnnotationAwareAspectJAutoProxyCreator#postProcessAfterInstantiation第一次执行时解析拦截表达式。

在这里插入图片描述

适配切面

在AbstractAutoProxyCreator#postProcessAfterInitialization执行时,找到上面AbstractAutoProxyCreator#postProcessBeforeInitialization缓存的所有的切面信息,之后是如何进行切面适配,从而决定是否需要进行代理对象的创建呢?

在调用AbstractAutoProxyCreator#postProcessAfterInitialization方法时,进行切面适配,并且会根据适配创建代理对象。根据以下调用链。

  • AbstractAutoProxyCreator#postProcessAfterInitialization

    • AbstractAutoProxyCreator#wrapIfNecessary

      1. protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
      2. if (beanName != null && this.targetSourcedBeans.contains(beanName)) {
      3. return bean;
      4. }
      5. if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
      6. return bean;
      7. }
      8. if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
      9. this.advisedBeans.put(cacheKey, Boolean.FALSE);
      10. return bean;
      11. }
      12. // Create proxy if we have advice.
      13. //查找匹配切面
      14. Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
      15. if (specificInterceptors != DO_NOT_PROXY) {
      16. this.advisedBeans.put(cacheKey, Boolean.TRUE);
      17. //创建代理对象
      18. Object proxy = createProxy(
      19. bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
      20. this.proxyTypes.put(cacheKey, proxy.getClass());
      21. return proxy;
      22. }
      23. this.advisedBeans.put(cacheKey, Boolean.FALSE);
      24. return bean;
      25. }
      • AbstractAdvisorAutoProxyCreator#getAdvicesAndAdvisorsForBean

        • AbstractAdvisorAutoProxyCreator#findEligibleAdvisors

    protected List findEligibleAdvisors(Class<?> beanClass, String beanName) {

    1. //从缓存取出所有切面信息

    List candidateAdvisors = findCandidateAdvisors();

    1. //根据advisor信息中的表达式进行方法对class的匹配

    List eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
    extendAdvisors(eligibleAdvisors);
    if (!eligibleAdvisors.isEmpty()) {

    1. eligibleAdvisors = sortAdvisors(eligibleAdvisors);

    }
    return eligibleAdvisors;
    }

此时如果是第一次执行适配方法findAdvisorsThatCanApply的话,candidateAdvisors中的拦截表达式还是空的,需要进行表达式获取,也就是@Pointcut的value。spring的操作的在第一次执行findAdvisorsThatCanApply时解析获取拦截表达式的值,获得拦截表达式值之后就跟当前class的方法进行匹配看是否需要进行代理。继续往下跟踪代码

  • AopUtils#canApply(org.springframework.aop.Advisor, java.lang.Class<?>, boolean)

    • AopUtils#canApply(org.springframework.aop.Pointcut, java.lang.Class<?>, boolean)

      • AspectJExpressionPointcut#getClassFilter

        • AspectJExpressionPointcut#checkReadyToMatch

    private void checkReadyToMatch() {

    if (getExpression() == null) {

    1. throw new IllegalStateException("Must set property 'expression' before attempting to match");

    }
    if (this.pointcutExpression == null) {

    1. this.pointcutClassLoader = (this.beanFactory instanceof ConfigurableBeanFactory ?
    2. ((ConfigurableBeanFactory) this.beanFactory).getBeanClassLoader() :
    3. ClassUtils.getDefaultClassLoader());
    4. //解析得到拦截表达式,例如根据@Before的value来关联查询出对应的表达式
    5. this.pointcutExpression = buildPointcutExpression(this.pointcutClassLoader);

    }
    }

最终解析完之后,advisor中的表达式信息结构如下图。包含在pointcut属性中,匹配时就根据pointcutExpression循环进行匹配class的方法。有兴趣的可以继续调试看看是如何实现匹配表达式的。

在这里插入图片描述

##  创建代理对象

如果在上面的匹配切面过程中,发现适配的切面,那就需要进行代理对象的创建了。

我们回到上面的AbstractAutoProxyCreator#wrapIfNecessary,主要看代码如下。

  1. // Create proxy if we have advice.
  2. //查找匹配切面
  3. Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
  4. if (specificInterceptors != DO_NOT_PROXY) {
  5. this.advisedBeans.put(cacheKey, Boolean.TRUE);
  6. //创建代理对象
  7. Object proxy = createProxy(
  8. bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
  9. this.proxyTypes.put(cacheKey, proxy.getClass());
  10. return proxy;
  11. }

所以,继续看

  • AbstractAutoProxyCreator#createProxy

的创建代理对象方法。设置ProxyFactory创建Proxy需要的一切信息。

  1. protected Object createProxy(
  2. Class<?> beanClass, String beanName, Object[] specificInterceptors, TargetSource targetSource) {
  3. if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
  4. AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
  5. }
  6. //新建代理对象工厂
  7. ProxyFactory proxyFactory = new ProxyFactory();
  8. proxyFactory.copyFrom(this);
  9. //设置工厂代理类
  10. if (!proxyFactory.isProxyTargetClass()) {
  11. if (shouldProxyTargetClass(beanClass, beanName)) {
  12. proxyFactory.setProxyTargetClass(true);
  13. }
  14. else {
  15. evaluateProxyInterfaces(beanClass, proxyFactory);
  16. }
  17. }
  18. //设置拦截切面
  19. Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
  20. for (Advisor advisor : advisors) {
  21. proxyFactory.addAdvisor(advisor);
  22. }
  23. //设置被代理对象
  24. proxyFactory.setTargetSource(targetSource);
  25. customizeProxyFactory(proxyFactory);
  26. proxyFactory.setFrozen(this.freezeProxy);
  27. if (advisorsPreFiltered()) {
  28. proxyFactory.setPreFiltered(true);
  29. }
  30. //创建代理对象
  31. return proxyFactory.getProxy(getProxyClassLoader());
  32. }

下面看ProxyFactory是如何创建代理对象,继续跟踪proxyFactory.getProxy(getProxyClassLoader());

  1. public Object getProxy(ClassLoader classLoader) {
  2. return createAopProxy().getProxy(classLoader);
  3. }

createAopProxy()作用是根据class的种类判断采用的代理方式,看如下实现

  • DefaultAopProxyFactory#createAopProxy

    1. @Override
    2. public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
    3. if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
    4. Class<?> targetClass = config.getTargetClass();
    5. if (targetClass == null) {
    6. throw new AopConfigException("TargetSource cannot determine target class: " +
    7. "Either an interface or a target is required for proxy creation.");
    8. }
    9. if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
    10. //采用jdk动态代理必须基于接口
    11. return new JdkDynamicAopProxy(config);
    12. }
    13. //基于cglib实现代理不需要接口
    14. return new ObjenesisCglibAopProxy(config);
    15. }
    16. else {
    17. return new JdkDynamicAopProxy(config);
    18. }
    19. }

所以在当前调试的例子中,使用cglib代理。所以执行如下代理。

  1. @Override
  2. public Object getProxy(ClassLoader classLoader) {
  3. //。。。。。。
  4. // Configure CGLIB Enhancer...
  5. Enhancer enhancer = createEnhancer();
  6. if (classLoader != null) {
  7. enhancer.setClassLoader(classLoader);
  8. if (classLoader instanceof SmartClassLoader &&
  9. ((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {
  10. enhancer.setUseCache(false);
  11. }
  12. }
  13. enhancer.setSuperclass(proxySuperClass);
  14. enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
  15. enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
  16. enhancer.setStrategy(new ClassLoaderAwareUndeclaredThrowableStrategy(classLoader));
  17. //获取拦截回调函数
  18. Callback[] callbacks = getCallbacks(rootClass);
  19. Class<?>[] types = new Class<?>[callbacks.length];
  20. for (int x = 0; x < types.length; x++) {
  21. types[x] = callbacks[x].getClass();
  22. }
  23. // fixedInterceptorMap only populated at this point, after getCallbacks call above
  24. enhancer.setCallbackFilter(new ProxyCallbackFilter(
  25. this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));
  26. enhancer.setCallbackTypes(types);
  27. // Generate the proxy class and create a proxy instance.
  28. //返回一个cglib代理对象
  29. return createProxyClassAndInstance(enhancer, callbacks);
  30. }
  31. catch (CodeGenerationException ex) {
  32. //、、、、、、
  33. }
  34. }

getCallbacks(rootClass);在这个获取回调函数的方法中,普通的aop采用的回调函数是如下的方式。

  1. // Choose an "aop" interceptor (used for AOP calls).
  2. Callback aopInterceptor = new DynamicAdvisedInterceptor(this.advised);

cglib 的aop回调函数如下。

  1. public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
  2. Object oldProxy = null;
  3. boolean setProxyContext = false;
  4. Class<?> targetClass = null;
  5. Object target = null;
  6. try {
  7. //这里注入的advised就是之前创建的ProxyFactory对象
  8. if (this.advised.exposeProxy) {
  9. // Make invocation available if necessary.
  10. oldProxy = AopContext.setCurrentProxy(proxy);
  11. setProxyContext = true;
  12. }
  13. // May be null. Get as late as possible to minimize the time we
  14. // "own" the target, in case it comes from a pool...
  15. target = getTarget();
  16. if (target != null) {
  17. targetClass = target.getClass();
  18. }
  19. //根据切面信息创建切面内容调用链
  20. List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
  21. Object retVal;
  22. // Check whether we only have one InvokerInterceptor: that is,
  23. // no real advice, but just reflective invocation of the target.
  24. if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
  25. // We can skip creating a MethodInvocation: just invoke the target directly.
  26. // Note that the final invoker must be an InvokerInterceptor, so we know
  27. // it does nothing but a reflective operation on the target, and no hot
  28. // swapping or fancy proxying.
  29. Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
  30. retVal = methodProxy.invoke(target, argsToUse);
  31. }
  32. else {
  33. // We need to create a method invocation...
  34. //创建一个方法调用对象,具体调用实现没分析,Before逻辑大概是先调用切面,在反射调用目标方法
  35. retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
  36. }
  37. retVal = processReturnType(proxy, target, method, retVal);
  38. return retVal;
  39. }
  40. finally {
  41. if (target != null) {
  42. releaseTarget(target);
  43. }
  44. if (setProxyContext) {
  45. // Restore old proxy.
  46. AopContext.setCurrentProxy(oldProxy);
  47. }
  48. }
  49. }

发表评论

表情:
评论列表 (有 0 条评论,118人围观)

还没有评论,来说两句吧...

相关阅读