zoukankan      html  css  js  c++  java
  • SpringAOP——源码

    一、找到代理对象初始化的地方

    上一篇的例子

     1 @Configuration
     2 @EnableAspectJAutoProxy
     3 public class Main {
     4 
     5     public static void main(String[] args){
     6         AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
     7         context.scan("com.app.aop");
     8         context.refresh();
     9         Xxable a = (Xxable)context.getBean("a");
    10         B b = (B) context.getBean("b");
    11         a.xx();
    12         b.xx();
    13         System.out.println(a.getClass().getName());
    14         System.out.println(b.getClass().getName());
    15     }
    16 }

    在第9行断点发现,IOC容器初始化后,IOC容器中存放的已经是代理对象proxy obejct

     根据动态代理分析:JDK动态代理是先创建targetobject然后将targetobject组合到proxyObject中,所以创建proxy object必是在target object初始化之后

    通过前面bean初始化知道在DI之后才bean基本初始化完成,也就是proxy object的产生是在DI之后,

    最后在bean初始化完成后,调用BeanPostProcessor时找到了proxy object产生的地方

    /* org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean */
    
        protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
            throws BeanCreationException {
            ...
            //这里实现DI
            populateBean(beanName, mbd, instanceWrapper);
            //DI完成后执行BeanPostProcessor
            exposedObject = initializeBean(beanName, exposedObject, mbd);
            ...
        }
    
    /* org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#initializeBean(java.lang.String, java.lang.Object, org.springframework.beans.factory.support.RootBeanDefinition) */
    
        protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) {
          ...
            //调用所有BeanPostProcessor前置方法BeanPostProcessor.postProcessBeforeInitialization
            wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
            ...
            //调用所有BeanPostProcessor前置方法BeanPostProcessor.postProcessAfterInitialization
            //AOP代理对象就是这里初始化的
            wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
            ...
        }

    具体的实现AnnotationAwareAspectJAutoProxyCreator.postProcessAfterInitialization

    二、AnnotationAwareAspectJAutoProxyCreator

    AnnoatiowareAspectJAutoProxyCreator.postprocessAfterInitialzation

    /* org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessAfterInitialization */
        public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
            if (bean != null) {
                //feactoryBean beanName = &beanName
                //Bean beanName = beanName
                Object cacheKey = getCacheKey(bean.getClass(), beanName);
                if (this.earlyProxyReferences.remove(cacheKey) != bean) {
                    //创建proxy object
                    return wrapIfNecessary(bean, beanName, cacheKey);
                }
            }
            return bean;
        }
    
        protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
            //跳过自定义代理的Bean例如有个Bean使用AspectJ实现AOP,不需要使用SpringAOP再来代理了
    (设置BeanPostProcessor.targetSourcedBeans.add)
    //避免自定义代理后,又默认代理一遍 if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) { return bean; } //skip不能被代理的Bean(设置BeanPostProcessor.before里面advisedBeans.put) //例如PointCut.class的实例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. //如果有advisor获取所有的advisors,然后筛选出class对应joinpoint的advisors Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null); //advisors不为空初始化proxy object if (specificInterceptors != DO_NOT_PROXY) { this.advisedBeans.put(cacheKey, Boolean.TRUE); //创建代理对象proxy object 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; }
    Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);获取所有的advisors
    /* org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator#getAdvicesAndAdvisorsForBean */
        protected Object[] getAdvicesAndAdvisorsForBean(
                Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {
            //统计advice
            List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);
            if (advisors.isEmpty()) {
                return DO_NOT_PROXY;
            }
            return advisors.toArray();
        }
    
        protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
            //查找所有Advisor,用Bean创建的Advisor+aspectBean中的advisor方法
            List<Advisor> candidateAdvisors = findCandidateAdvisors();
            //根据class中方法匹配对应的Advisor,过滤当前bean TargetClass与Advisor对应的pointCut相匹配的Advisor
            List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
            //加入一个默认的ExposeInvocationInterceptor.ADVISOR
            //后面责任链模式执行代理方法,这个advisor就位于责任链首位了,第一个需要被执行的advisor
            //作用就是记录target object的方法(ThreadLocal实现),便于调整五种advice的执行循序的实现
            extendAdvisors(eligibleAdvisors);
            if (!eligibleAdvisors.isEmpty()) {
                //一开始我以为是倒序后来发现错了,默认先比较order值
            //默认advisor。order < 0 放首位
                //然后调整afterThrowAdvice>afterRuturningAdvice>afterAdivce>RoundAdvice>BeforeAdvice
                eligibleAdvisors = sortAdvisors(eligibleAdvisors);
            }
            return eligibleAdvisors;
        }
    List<Advisor> candidateAdvisors = findCandidateAdvisors();:获取所有的advisors Bean创建的advisor+扫描aspect中的advisor方法
    /* org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator#findCandidateAdvisors */
        protected List<Advisor> findCandidateAdvisors() {
            // Add all the Spring advisors found according to superclass rules.
            //用Bean创建的Advice
            List<Advisor> advisors = super.findCandidateAdvisors();
            // Build Advisors for all AspectJ aspects in the bean factory.
            if (this.aspectJAdvisorsBuilder != null) {
                //加上aspect的Bean中的Advice方法
                advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
            }
            return advisors;
        }
    this.aspectJAdvisorsBuilder.buildAspectJAdvisors():获取aspect中声明的advisors
    /* org.springframework.aop.aspectj.annotation.BeanFactoryAspectJAdvisorsBuilder#buildAspectJAdvisors */
        public List<Advisor> buildAspectJAdvisors() {
            List<String> aspectNames = this.aspectBeanNames;
    
            if (aspectNames == null) {
                synchronized (this) {
                    aspectNames = this.aspectBeanNames;
                    if (aspectNames == null) {
                        List<Advisor> advisors = new ArrayList<>();
                        aspectNames = new ArrayList<>();
                        //获取beanfactory中所有的beanName,遍历allBeanNamesByType获得
                        String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
                                this.beanFactory, Object.class, true, false);
                        for (String beanName : beanNames) {
                            if (!isEligibleBean(beanName)) {
                                continue;
                            }
                            // We must be careful not to instantiate beans eagerly as in this case they
                            // would be cached by the Spring container but would not have been weaved.
                            //根据beanName过去bean的类型Type
                            Class<?> beanType = this.beanFactory.getType(beanName);
                            if (beanType == null) {
                                continue;
                            }
                            // 根据beanType筛选出切面aspect:
                            // class被@Aspect注解,且不是AspectJ编译器生成的类(通过魔数判断)
                            if (this.advisorFactory.isAspect(beanType)) {
                                //缓存aspectBean的beanName
                                aspectNames.add(beanName);
                                AspectMetadata amd = new AspectMetadata(beanType, beanName);
                                if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {
                                    MetadataAwareAspectInstanceFactory factory =
                                            new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);
                                    //从aspect中定义的advisor
                                    List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);
                                    if (this.beanFactory.isSingleton(beanName)) {
                                        //单例缓存advisor
                                        this.advisorsCache.put(beanName, classAdvisors);
                                    }
                                    else {
                                        //原型缓存factory
                                        this.aspectFactoryCache.put(beanName, factory);
                                    }
                                    advisors.addAll(classAdvisors);
                                }
                                else {
                                    // Per target or per this.
                                    if (this.beanFactory.isSingleton(beanName)) {
                                        throw new IllegalArgumentException("Bean with name '" + beanName +
                                                "' is a singleton, but aspect instantiation model is not singleton");
                                    }
                                    MetadataAwareAspectInstanceFactory factory =
                                            new PrototypeAspectInstanceFactory(this.beanFactory, beanName);
                                    this.aspectFactoryCache.put(beanName, factory);
                                    advisors.addAll(this.advisorFactory.getAdvisors(factory));
                                }
                            }
                        }
                        this.aspectBeanNames = aspectNames;
                        return advisors;
                    }
                }
            }
    
            if (aspectNames.isEmpty()) {
                return Collections.emptyList();
            }
            //有缓存直接去缓存取
            List<Advisor> advisors = new ArrayList<>();
            for (String aspectName : aspectNames) {
                List<Advisor> cachedAdvisors = this.advisorsCache.get(aspectName);
                if (cachedAdvisors != null) {
                    advisors.addAll(cachedAdvisors);
                }
                else {
                    MetadataAwareAspectInstanceFactory factory = this.aspectFactoryCache.get(aspectName);
                    advisors.addAll(this.advisorFactory.getAdvisors(factory));
                }
            }
            return advisors;
        }
    List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);获取aspecj的advisor并排序
    /* org.springframework.aop.aspectj.annotation.ReflectiveAspectJAdvisorFactory#getAdvisors */
        public List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory aspectInstanceFactory) {
            //获取aspectClass & AspectName并在此校验
            Class<?> aspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();
            String aspectName = aspectInstanceFactory.getAspectMetadata().getAspectName();
            validate(aspectClass);
    
            // We need to wrap the MetadataAwareAspectInstanceFactory with a decorator
            // so that it will only instantiate once.
            MetadataAwareAspectInstanceFactory lazySingletonAspectInstanceFactory =
                    new LazySingletonAspectInstanceFactoryDecorator(aspectInstanceFactory);
    
            List<Advisor> advisors = new ArrayList<>();
            //getAdvisorMethods获取aspect中advice方法并且排序,
            for (Method method : getAdvisorMethods(aspectClass)) {
                //注意advisors.size()这个参数,这里生成advisor实例类型是InstantiationModelAwarePointcutAdvisorImpl这个类实现了Ordered接口,
    //advisors.size()就是优先级,后面会根据这个变量排序一次
    Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, advisors.size(), aspectName); if (advisor != null) { advisors.add(advisor); } } // If it's a per target aspect, emit the dummy instantiating aspect. if (!advisors.isEmpty() && lazySingletonAspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) { Advisor instantiationAdvisor = new SyntheticInstantiationAdvisor(lazySingletonAspectInstanceFactory); advisors.add(0, instantiationAdvisor); } // Find introduction fields. //introduction:获取被@DeclareParents注解变量 for (Field field : aspectClass.getDeclaredFields()) { Advisor advisor = getDeclareParentsAdvisor(field); if (advisor != null) { advisors.add(advisor); } } return advisors; } private List<Method> getAdvisorMethods(Class<?> aspectClass) { final List<Method> methods = new ArrayList<>(); ReflectionUtils.doWithMethods(aspectClass, method -> { // Exclude pointcuts if (AnnotationUtils.getAnnotation(method, Pointcut.class) == null) { //获取aspect中的advice方法,没有被@Pointcut注解的方法,下面比较器中 //如果未被advice相关注解的方法,会设置为null,即排序的时候再筛选了一次 methods.add(method); } }, ReflectionUtils.USER_DECLARED_METHODS); // methods.sort(METHOD_COMPARATOR); return methods; }
    METHOD_COMPARATOR:排序比较器
    /* org.springframework.aop.aspectj.annotation.ReflectiveAspectJAdvisorFactory */
        static {
            Comparator<Method> adviceKindComparator = new ConvertingComparator<>(
                    //advice排序:around>before>after>afterReturning>afterThrowing
    //后面还会调整:afterThrowing>afterReturning>after>around>before
    //运行无异常时:around before>before>around after>after>after returning> //有异常时:around before>before>after>afterThrowing new InstanceComparator<>( Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class), (Converter<Method, Annotation>) method -> { AspectJAnnotation<?> annotation = AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(method); return (annotation != null ? annotation.getAnnotation() : null); }); Comparator<Method> methodNameComparator = new ConvertingComparator<>(Method::getName); METHOD_COMPARATOR = adviceKindComparator.thenComparing(methodNameComparator); }
    Object proxy = createProxy(
    bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));创建代理对象proxy object
    /* org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#createProxy */
        protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
                @Nullable Object[] specificInterceptors, TargetSource targetSource) {
    
            if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
                //修改beandefinition.setAttribute(org.springframework.aop.framework.autoproxy.AutoProxyUtils.originalTargetClass,targetClass)
                AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
            }
    
            ProxyFactory proxyFactory = new ProxyFactory();
            proxyFactory.copyFrom(this);
    
            //传说中的ProxyTargetClass属性
            //false 默认JDK动态代理、无接口类使用CgLib代理
            //true 强制全部使用CgLib代理
            if (!proxyFactory.isProxyTargetClass()) {
                if (shouldProxyTargetClass(beanClass, beanName)) {
                    //beandefinition.getAttribute(org.springframework.aop.framework.autoproxy.AutoProxyUtils.preserveTargetClass)
                    //=true,则使用Cglib代理
                    proxyFactory.setProxyTargetClass(true);
                }
                else {
                    //检测beanClass如果实现了接口,记录所有的接口到proxyFactory.interfaces容器中
                    //检测beanClass如果没有实现接口 proxyFactory.setProxyTargetClass(true);
                    evaluateProxyInterfaces(beanClass, proxyFactory);
                }
            }
         //将Advisor中实例转化为DefaultPointcutAdvisor实例
            Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
            //设置advisors到proxyFactory
            proxyFactory.addAdvisors(advisors);
            //设置targetSource到proxyFactory
            proxyFactory.setTargetSource(targetSource);
            //空方法
            customizeProxyFactory(proxyFactory);
    
            proxyFactory.setFrozen(this.freezeProxy);
            if (advisorsPreFiltered()) {
                proxyFactory.setPreFiltered(true);
            }
            //getProxyClassLoader  上下文线程类加载器,服务提供接口SPI了解一下
            return proxyFactory.getProxy(getProxyClassLoader());
        }
    return proxyFactory.getProxy(getProxyClassLoader());创建Proxy object ==>createAopProxy().getProxy(classLoader);
    //众所众知的默认代理设置
    /*org.springframework.aop.framework.DefaultAopProxyFactory#createAopProxy */
        public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
            if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
                //optimize = true
                //proxyTargetCass = true
                //proxyFactory.interfaces.length = 0
                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.");
                }
                if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
                    //目标类型是接口类型 || targetClass是Proxy的实现类
                    //返回JDK动态代理
                    return new JdkDynamicAopProxy(config);
                }
                //保底CgLib代理
                return new ObjenesisCglibAopProxy(config);
            }
            else {
                //默认JDK动态代理
                return new JdkDynamicAopProxy(config);
            }
        }
    
    /* org.springframework.aop.framework.JdkDynamicAopProxy#getProxy(java.lang.ClassLoader) */
        public Object getProxy(@Nullable ClassLoader classLoader) {
            if (logger.isTraceEnabled()) {
                logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());
            }
            //proxy object需要实现的接口
            Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
            findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
            //动态代理InvocationHandler的那个类this=JdkDynamicAopProxy实例
            return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
        }

    proxy.method == JdkDynamicAopProxy.invoke(method) == CglibAopProxy.DynamicAdvisedInterceptor.intercept(method):

    具体实现逻辑很相似,仅仅是代理对象创建方式不同,方法调用的逻辑基本一致。

    /* org.springframework.aop.framework.JdkDynamicAopProxy#invoke */
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
           ...
            // 生成一个advisor链,这里责任链模式Get the interception chain for this method
            // 验证当前方法是join point:pointAdvisor.getPointcut().getMethodMatcher().matchs(method)
            List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
            ...
            MethodInvocation invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
            // Proceed to the joinpoint through the interceptor chain.
            //通过责任链顺序执行方法
            retVal = invocation.proceed();
         ...
        }

    两种代理方法责任链模式实现是同一个方法。

    /* org.springframework.aop.framework.ReflectiveMethodInvocation#proceed */
    public Object proceed() throws Throwable {
            // We start with an index of -1 and increment early.
            if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
                return invokeJoinpoint();
            }
    
            Object interceptorOrInterceptionAdvice =
                    this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
            if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
                // Evaluate dynamic method matcher here: static part will already have
                // been evaluated and found to match.
                InterceptorAndDynamicMethodMatcher dm =
                        (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
                Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());
                if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {
                    return dm.interceptor.invoke(this);
                }
                else {
                    // Dynamic matching failed.
                    // Skip this interceptor and invoke the next in the chain.
                    return proceed();
                }
            }
            else {
                // It's an interceptor, so we just invoke it: The pointcut will have
                // been evaluated statically before this object was constructed.
                return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
            }
        }

     具体流程

    1、ExposeInvocationInterceptor----默认Advisor
        public Object invoke(MethodInvocation mi) throws Throwable {
            MethodInvocation oldInvocation = invocation.get();
            invocation.set(mi);
            try {
                return mi.proceed();
            }
            finally {
                invocation.set(oldInvocation);
            }
        }
        -----------
        mi.proceed();
    2、AspectJAfterThrowingAdvice----AfterThrowingAdvice
        public Object invoke(MethodInvocation mi) throws Throwable {
            try {
                return mi.proceed();
            }
            catch (Throwable ex) {
                if (shouldInvokeOnThrowing(ex)) {
                    invokeAdviceMethod(getJoinPointMatch(), null, ex);
                }
                throw ex;
            }
        }
        -----------
        try{
            mi.proceed();
        }catch(Throwable ex){
            AfterThrowingAdvice
        }
    3、AfterReturningAdviceInterceptor----AfterReturningAdvice
        public Object invoke(MethodInvocation mi) throws Throwable {
            Object retVal = mi.proceed();
            this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());
            return retVal;
        }
        -------------
        try{
            mi.proceed();
            AfterReturningAdvice
        }catch(Throwable ex){
            AfterThrowingAdvice
        }
    4、AspectJAfterAdvice----AfterAdvice
        public Object invoke(MethodInvocation mi) throws Throwable {
            try {
                return mi.proceed();
            }
            finally {
                invokeAdviceMethod(getJoinPointMatch(), null, null);
            }
        }
        ----------------
        try{
            mi.proceed();
            AfterReturningAdvice
        }catch(Throwable ex){
            AfterThrowingAdvice
        }finally{
            AfterAdvice
        }
    5、AspectJAroundAdvice----AroundAdvice
        public Object invoke(MethodInvocation mi) throws Throwable {
            if (!(mi instanceof ProxyMethodInvocation)) {
                throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi);
            }
            ProxyMethodInvocation pmi = (ProxyMethodInvocation) mi;
            ProceedingJoinPoint pjp = lazyGetProceedingJoinPoint(pmi);
            JoinPointMatch jpm = getJoinPointMatch(pmi);
            return invokeAdviceMethod(pjp, jpm, null, null);
        }
        ----------------
        try{
            around before advice
            mi.proceed();
            around after advice
            AfterReturningAdvice
        }catch(Throwable ex){
            AfterThrowingAdvice
        }finally{
            AfterAdvice
        }
    6、MethodBeforeAdviceInterceptor----BeforeAdvice
        public Object invoke(MethodInvocation mi) throws Throwable {
            this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
            return mi.proceed();
        }
        -----------------
        try{
            around after advice
            BeforeAdvice
            mi.proceed();//method.invoke(target,methodName);
            around after advice
            AfterReturningAdvice
        }catch(Throwable ex){
            AfterThrowingAdvice
        }finally{
            AfterAdvice
        }

    ..

  • 相关阅读:
    Asp.Net实现FORM认证的一些使用技巧(必看篇)
    python3 关联规则Apriori代码模版
    一家人(模拟)
    一家人(模拟)
    积木城堡(dp)
    积木城堡(dp)
    Tensorflow笔记——神经网络图像识别(四)搭建模块化的神经网络八股(正则化,指数衰减学习率,滑动平均等优化)...
    Tensorflow笔记——神经网络图像识别(四)搭建模块化的神经网络八股(正则化,指数衰减学习率,滑动平均等优化)
    保存训练好的机器学习模型
    保存训练好的机器学习模型
  • 原文地址:https://www.cnblogs.com/wqff-biubiu/p/12395843.html
Copyright © 2011-2022 走看看