Spring aop解析(4)2018-08-14

上一节我们分析了Advisor的生成过程以及在Advisor中生成Advise的过程。接着上一节继续我们看看挑出适用于目标对象的Advisor:

private void addAdvisorsFromAspectInstanceFactory(MetadataAwareAspectInstanceFactory instanceFactory) {
        //获取Advisors集合
        List<Advisor> advisors = this.aspectFactory.getAdvisors(instanceFactory);
        //从中挑出适用于目标对象的Advisor
        advisors = AopUtils.findAdvisorsThatCanApply(advisors, getTargetClass());
        AspectJProxyUtils.makeAdvisorChainAspectJCapableIfNecessary(advisors);
        //对获取到的Advisor进行排序
        AnnotationAwareOrderComparator.sort(advisors);
        //将获取到Advisor添加到advisors集合中
        addAdvisors(advisors);
    }
2.3.1:addAdvisorsFromAspectInstanceFactory方法:

a、挑出适用于目标对象的Advisor: AopUtils.findAdvisorsThatCanApply(advisors, getTargetClass());

public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) {
        //如果传入的Advisor集合为空的话,直接返回这个空集合
        if (candidateAdvisors.isEmpty()) {
            return candidateAdvisors;
        }
        //创建一个合适的Advisor的集合 eligible
        List<Advisor> eligibleAdvisors = new LinkedList<Advisor>();
        //循环所有的Advisor
        for (Advisor candidate : candidateAdvisors) {
            //如果Advisor是IntroductionAdvisor  引介增强 可以为目标类 通过AOP的方式添加一些接口实现
            if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) {
                eligibleAdvisors.add(candidate);
            }
        }
        //是否有引介增强
        boolean hasIntroductions = !eligibleAdvisors.isEmpty();
        for (Advisor candidate : candidateAdvisors) {
            //如果是IntroductionAdvisor类型的话 则直接跳过
            if (candidate instanceof IntroductionAdvisor) {
                // already processed
                continue;
            }
            //判断此Advisor是否适用于target
            if (canApply(candidate, clazz, hasIntroductions)) {
                eligibleAdvisors.add(candidate);
            }
        }
        return eligibleAdvisors;
    }

public static boolean canApply(Advisor advisor, Class<?> targetClass, boolean hasIntroductions) {
        //如果是IntroductionAdvisor的话,则调用IntroductionAdvisor类型的实例进行类的过滤
        //这里是直接调用的ClassFilter的matches方法
        if (advisor instanceof IntroductionAdvisor) {
            return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass);
        }
        //通常我们的Advisor都是PointcutAdvisor类型
        else if (advisor instanceof PointcutAdvisor) {
            PointcutAdvisor pca = (PointcutAdvisor) advisor;
            //这里从Advisor中获取Pointcut的实现类 这里是AspectJExpressionPointcut
            return canApply(pca.getPointcut(), targetClass, hasIntroductions);
        }
        else {
            // It doesn't have a pointcut so we assume it applies.
            return true;
        }
    }

这里匹配的规则我们就不进行深入了。

2.4:接着我们来看看代理对象的创建过程:
public class TestAop {
    public static void main(String[] args) {
        //手工创建一个实例(Target)
        ProxyService aspectJService = new ProxyServiceImpl();
        //使用AspectJ语法 自动创建代理对象
        AspectJProxyFactory aspectJProxyFactory = new AspectJProxyFactory(aspectJService);
        //添加切面和通知类
        aspectJProxyFactory.addAspect(AopAdviceConfig.class);
        //创建代理对象
        ProxyService proxyService = aspectJProxyFactory.getProxy();
        //进行方法调用
        proxyService.testProxy();
    }
}

a、创建代理aspectJProxyFactory.getProxy():

 //通过调用createAopProxy()生成的对象调用getProxy()方法生成代理对象
 public <T> T getProxy() {
        return (T) createAopProxy().getProxy();
}

protected final synchronized AopProxy createAopProxy() {
    if (!this.active) {
        //这里会监听调用AdvisedSupportListener实现类的activated方法
        activate();
    }
    //获取AopProxyFactory
    //调用createAopProxy的时候传入了this对象
    return getAopProxyFactory().createAopProxy(this);
}
//在SpringAOP中 AopProxyFactory只有一个实现类,这个实现类就是DefaultAopProxyFactory
public AopProxyFactory getAopProxyFactory() {
    return this.aopProxyFactory;
}   

private void activate() {
        this.active = true;
        for (AdvisedSupportListener listener : this.listeners) {
            listener.activated(this);
        }
    }

b、我们接着看:createAopProxy()方法:

ublic AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
        //这段代码用来判断选择哪种创建代理对象的方式
        //config.isOptimize()   是否对代理类的生成使用策略优化 其作用是和isProxyTargetClass是一样的 默认为false
        //config.isProxyTargetClass() 是否使用Cglib的方式创建代理对象 默认为false
        //hasNoUserSuppliedProxyInterfaces目标类是否有接口存在 且只有一个接口的时候接口类型不是SpringProxy类型 
        if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
            //从AdvisedSupport中获取目标类 类对象
            Class<?> targetClass = config.getTargetClass();
            if (targetClass == null) {
                throw new AopConfigException("TargetSource cannot determine target class: " +
                        "Either an interface or a target is required for proxy creation.");
            }
            //判断目标类是否是接口 如果目标类是接口的话,则还是使用JDK的方式生成代理对象
            //如果目标类是Proxy类型 则还是使用JDK的方式生成代理对象
            if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
                return new JdkDynamicAopProxy(config);
            }
            return new ObjenesisCglibAopProxy(config);
        }
        else {
            //使用JDK的提供的代理方式生成代理对象
            return new JdkDynamicAopProxy(config);
        }
    }

c、我们接着往下看看createAopProxy().getProxy()方法:

public <T> T getProxy(ClassLoader classLoader) {
       return (T) createAopProxy().getProxy(classLoader);
   }

public Object getProxy(ClassLoader classLoader) {
       if (logger.isDebugEnabled()) {
           logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
       }
       //获取AdvisedSupport类型对象的所有接口
       Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
       //接口是否定义了 equals和hashcode方法 正常是没有的,定义了生成的代理以定义的为准
       findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
       //创建代理
       return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
   }

private void findDefinedEqualsAndHashCodeMethods(Class<?>[] proxiedInterfaces) {
       for (Class<?> proxiedInterface : proxiedInterfaces) {
           Method[] methods = proxiedInterface.getDeclaredMethods();
           for (Method method : methods) {
               if (AopUtils.isEqualsMethod(method)) {
                   this.equalsDefined = true;
               }
               if (AopUtils.isHashCodeMethod(method)) {
                   this.hashCodeDefined = true;
               }
               if (this.equalsDefined && this.hashCodeDefined) {
                   return;
               }
           }
       }
   }

e、我们知道创建代理需要额外功能类继承InvocationHandler实现invoke方法添加额外功能,那么我们来看看JdkDynamicAopProxy中的invoke的实现:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        MethodInvocation invocation;
        Object oldProxy = null;
        boolean setProxyContext = false;
        //目标对象
        TargetSource targetSource = this.advised.targetSource;
        Class<?> targetClass = null;
        Object target = null;

        try {
            //接口中没有定义 equals方法,并且调用的方法是equals方法(即Object中定义的equals方法) 
            if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
                //调用 JdkDynamicAopProxy 中写的equals方法
                return equals(args[0]);
            }else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
                //和上面的分析一样
                return hashCode();
            }else if (method.getDeclaringClass() == DecoratingProxy.class) {
                return AopProxyUtils.ultimateTargetClass(this.advised);
            }else if (!this.advised.opaque && method.getDeclaringClass().isInterface() && method.getDeclaringClass().isAssignableFrom(Advised.class)) {
              //如果 方法所在的类是接口 并且是Advised的子类,则直接调用下面的方法
                return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
            }

            Object retVal;
            //是否对外暴露代理对象
            if (this.advised.exposeProxy) {
                // Make invocation available if necessary.
                //把创建的代理对象放到线程上下文中返回之前老的代理
                oldProxy = AopContext.setCurrentProxy(proxy);
                setProxyContext = true;
            }
            //从TargetSource中获取目标对象
            target = targetSource.getTarget();
            if (target != null) {
                targetClass = target.getClass();
            }
            //从Advised中根据方法名和目标类获取 AOP拦截器执行链 重点要分析的内容
            List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
            //如果这个执行链为空的话
            if (chain.isEmpty()) {
                //直接进行方法调用
                Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
                retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
            }
            else {
                //如果AOP拦截器执行链不为空  说明有AOP通知存在
                invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
                //开始调用
                retVal = invocation.proceed();
            }
            //方法的返回值类型
            Class<?> returnType = method.getReturnType();
            if (retVal != null && retVal == target && returnType.isInstance(proxy) &&
                    !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
                //return this
                retVal = proxy;
            }
            //返回值类型错误
            else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
                throw new AopInvocationException(
                        "Null return value from advice does not match primitive return type for: " + method);
            }
            return retVal;
        }
        finally {
            //如果目标对象不为空  且目标对象是可变的 如prototype类型
            //通常我们的目标对象都是单例的  即targetSource.isStatic为true
            if (target != null && !targetSource.isStatic()) {
                //释放目标对象
                targetSource.releaseTarget(target);
            }
            if (setProxyContext) {
                //线程上下文复位
                AopContext.setCurrentProxy(oldProxy);
            }
        }
    }

从上面代码不难看出这段代码的重点:从Advised中根据方法名和目标类获取 AOP拦截器执行链(List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);)那么我们来看看获取的代码:

f、this.advised.getInterceptorsAndDynamicInterceptionAdvice:

public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, Class<?> targetClass) {
        //创建一个method的缓存对象在MethodCacheKey中实现了equals和hashcode方法同时还实现了compareTo方法
        MethodCacheKey cacheKey = new MethodCacheKey(method);
        List<Object> cached = this.methodCache.get(cacheKey);
        //先从缓存中获取 如果缓存中获取不到则再调用方法获取,获取之后放入到缓存中
        if (cached == null) {
            //调用的是advisorChainFactory的getInterceptorsAndDynamicInterceptionAdvice方法
            cached = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(
                    this, method, targetClass);
            this.methodCache.put(cacheKey, cached);
        }
        return cached;
    }


public List<Object> getInterceptorsAndDynamicInterceptionAdvice(
            Advised config, Method method, Class<?> targetClass) {
        //创建一个初始大小为之前获取到的 通知个数的 集合
        List<Object> interceptorList = new ArrayList<Object>(config.getAdvisors().length);
        //如果目标类为null的话,则从方法签名中获取目标类
        Class<?> actualClass = (targetClass != null ? targetClass : method.getDeclaringClass());
        //判断目标类是否存在引介增强通常为false
        boolean hasIntroductions = hasMatchingIntroductions(config, actualClass);
        //这里用了一个单例模式获取DefaultAdvisorAdapterRegistry实例
        AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();
        //循环目标方法匹配的通知
        for (Advisor advisor : config.getAdvisors()) {
            //如果是PointcutAdvisor类型的实例  我们大多数的Advisor都是PointcutAdvisor类型的
            if (advisor instanceof PointcutAdvisor) {
                PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;
                //如果提前进行过 切点的匹配了或者当前的Advisor适用于目标类
                if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) {
                    //将Advisor适配为MethodInterceptor
                    MethodInterceptor[] interceptors = registry.getInterceptors(advisor);
                    MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();
                    //检测Advisor是否适用于此目标方法
                    if (MethodMatchers.matches(mm, method, actualClass, hasIntroductions)) {
                        //MethodMatcher中的切点分为两种 一个是静态的 一种是动态的
                        //如果isRuntime返回true 则是动态的切入点每次方法的调用都要去进行匹配
                        //而静态切入点则回缓存之前的匹配结果值 
                        if (mm.isRuntime()) {
                            //动态切入点 则会创建一个InterceptorAndDynamicMethodMatcher对象
                            //这个对象包含MethodInterceptor和MethodMatcher 的实例
                            for (MethodInterceptor interceptor : interceptors) {
                                interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptor, mm));
                            }
                        }
                        else {
                            //添加到列表中
                            interceptorList.addAll(Arrays.asList(interceptors));
                        }
                    }
                }
            }
            //如果是引介增强
            else if (advisor instanceof IntroductionAdvisor) {
                IntroductionAdvisor ia = (IntroductionAdvisor) advisor;
                if (config.isPreFiltered() || ia.getClassFilter().matches(actualClass)) {
                    //将Advisor转换为Interceptor
                    Interceptor[] interceptors = registry.getInterceptors(advisor);
                    interceptorList.addAll(Arrays.asList(interceptors));
                }
            }
            //以上两种都不是
            else {
                //将Advisor转换为Interceptor
                Interceptor[] interceptors = registry.getInterceptors(advisor);
                interceptorList.addAll(Arrays.asList(interceptors));
            }
        }
        return interceptorList;
    }

从上面代码可以总结下流程:
1、循环目标方法的所有Advisor
2、判断Advisor的类型
  a、如果是PointcutAdvisor的类型,则判断此Advisor是否适用于此目标方法
 b、如果是IntroductionAdvisor引介增强类型,则判断此Advisor是否适用于此目标方法
 c、如果以上都不是,则直接转换为Interceptor类型。

不管是Advisor哪一类型最终都会registry.getInterceptors(advisor);进行转换,那么我们来看看这个方法:

g、registry.getInterceptors(advisor):
我们来先看看registry的获取代码:

 AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();
=========
public abstract class GlobalAdvisorAdapterRegistry {
    private static AdvisorAdapterRegistry instance = new DefaultAdvisorAdapterRegistry();

    
    public static AdvisorAdapterRegistry getInstance() {
        return instance;
    }
=========
public DefaultAdvisorAdapterRegistry() {
        //前置通知适配器
        registerAdvisorAdapter(new MethodBeforeAdviceAdapter());
        //后置返回通知适配器
        registerAdvisorAdapter(new AfterReturningAdviceAdapter());
        //后置异常通知适配器
        registerAdvisorAdapter(new ThrowsAdviceAdapter());
    }

接着我们来看看:registry.getInterceptors的代码:

public MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException {
        List<MethodInterceptor> interceptors = new ArrayList<MethodInterceptor>(3);
        //从Advisor中获取 Advice
        Advice advice = advisor.getAdvice();
        if (advice instanceof MethodInterceptor) {
            interceptors.add((MethodInterceptor) advice);
        }
        for (AdvisorAdapter adapter : this.adapters) {
            if (adapter.supportsAdvice(advice)) {
                //转换为对应的 MethodInterceptor类型
                //AfterReturningAdviceInterceptor MethodBeforeAdviceInterceptor  ThrowsAdviceInterceptor
                interceptors.add(adapter.getInterceptor(advisor));
            }
        }
        if (interceptors.isEmpty()) {
            throw new UnknownAdviceTypeException(advisor.getAdvice());
        }
        return interceptors.toArray(new MethodInterceptor[interceptors.size()]);
    }

l、拦截器连的调用过程:
invoke方法中的:

//如果AOP拦截器执行链不为空  说明有AOP通知存在
 invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
//开始调用
 retVal = invocation.proceed();

那么我们来看看这个方法:

public Object proceed() throws Throwable {
        // 如果执行到链条的末尾则直接调用连接点方法即直接调用目标方法
        if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
            //调用目标方法
            return invokeJoinpoint();
        }
        //获取集合中的 MethodInterceptor
        Object interceptorOrInterceptionAdvice =
                this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
        //如果是InterceptorAndDynamicMethodMatcher类型
        if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
            InterceptorAndDynamicMethodMatcher dm =
                    (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
            //这里每一次都去匹配是否适用于这个目标方法
            if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
                //如果匹配则直接调用 MethodInterceptor的invoke方法
                return dm.interceptor.invoke(this);
            }
            else {
                //如果不适用于此目标方法 则继续执行下一个链条 递归调用
                return proceed();
            }
        }
        else {
            //直接调用 MethodInterceptor的invoke方法 
            return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
        }
    }

看下invokeJoinpoint方法(直接掉用目标类的方法):

protected Object invokeJoinpoint() throws Throwable {
        //this.target 目标对象
        //this.method 目标方法
        this.arguments 目标方法参数信息
        return AopUtils.invokeJoinpointUsingReflection(this.target, this.method, this.arguments);
    }
    public static Object invokeJoinpointUsingReflection(Object target, Method method, Object[] args)
            throws Throwable {

        // Use reflection to invoke the method.
        try {
            //设置方法可见性
            ReflectionUtils.makeAccessible(method);
            //反射调用  最终是通过反射去调用目标方法
            return method.invoke(target, args);
        }
        catch (InvocationTargetException ex) {
            // Invoked method threw a checked exception.
            // We must rethrow it. The client won't see the interceptor.
            throw ex.getTargetException();
        }
        catch (IllegalArgumentException ex) {
            throw new AopInvocationException("AOP configuration seems to be invalid: tried calling method [" +
                    method + "] on target [" + target + "]", ex);
        }
        catch (IllegalAccessException ex) {
            throw new AopInvocationException("Could not access method [" + method + "]", ex);
        }
    }
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,444评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,421评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,363评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,460评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,502评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,511评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,280评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,736评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,014评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,190评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,848评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,531评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,159评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,411评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,067评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,078评论 2 352

推荐阅读更多精彩内容

  • AOP概述 AOP定义概念这里写图片描述这里我们只要关注三个重点的概念:1)基础(base):目标对象2)切面(a...
    铁甲依然在_978f阅读 309评论 0 0
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,644评论 18 139
  • 【今日话题】 迄今为止,除了父母之外,谁对你的影响最大?ta教会了你什么? 对我影响最大的那应该是我的老公吧,他...
    娜天同意阅读 264评论 2 1
  • (2017-10-29-周日 16:30:54) 在窗口中部,左端
    菜五阅读 98评论 0 0
  • “会” 和“懂”真的是相差甚远,就拿打羽毛球来说,很多人都会打,但是很多人不一定懂。真正懂的人不仅知道打羽毛...
    呼呼海爸阅读 882评论 0 0