Spring——AOP原理及流程详解

7 篇文章 3 订阅
订阅专栏

一、AOP结构介绍

我们先看个简单的AOP例子:

@Aspect
@Component
public class AopAspect {


    @Pointcut("execution(* com.example.spkie.AopTest.AopTest.test())")
    public void aopTest() {

    }

    @Before("aopTest()")
    public void doBefore(JoinPoint joinPoint){
        System.out.println("前置通知");
    }

    @Around("aopTest()")
    public Object aroundExec(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("环绕前置处理");
        Object proceed = joinPoint.proceed();
        System.out.println("环绕后置处理");
        return proceed;
    }

    @AfterReturning(value = "aopTest()")
    public void doAfterReturning(){
        System.out.println("doAfterReturning后置通知");
    }

    @After("aopTest()")
    public void doAfter(){
        System.out.println("doAfter最终通知");
    }

    @AfterThrowing(value = "aopTest()",throwing = "e")
    public void doThrow(Exception e){
        System.out.println("异常通知:"+e.getMessage());
    }

}

结果:

在这里插入图片描述

我们来细数一下有哪些要素?

  • @Aspect:切面类,告诉Spring我这个类是个切面,里面有特殊处理方法
  • @Pointcut:切点,告诉Spring我要针对什么
  • @Before、@Around、@AfterReturning、@After、@AfterThrowing:通知,告诉Spring针对后要做什么处理

要素就这些吧,@Aspect就不说了就是个标识,主要是切点和处理方法吧

@Pointcut

这个注解值的格式是:表达标签 (表达式格式),用白话说就是用了一种表达式来代表我要针对什么来进行特殊处理,表达标签有以下几种,表达式格式各不太一样,这里就不一一介绍了

  • execution:用于匹配方法执行的连接点
  • within:用于匹配指定类型内的方法执行
  • this:用于匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能包括引入接口也类型匹配
  • target:用于匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配
  • args:用于匹配当前执行的方法传入的参数为指定类型的执行方法
  • @within:用于匹配所以持有指定注解类型内的方法
  • @target:用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解
  • @args:用于匹配当前执行的方法传入的参数持有指定注解的执行
  • @annotation:用于匹配当前执行方法持有指定注解的方法
  • bean:Spring AOP扩展的,AspectJ没有对于指示符,用于匹配特定名称的Bean对象的执行方法

通知

我们上述看到了有五种通知注解,分别表示如下,表示有五种特殊处理方式:

  • @Before: 前置通知,在目标方法执行前执行
  • @Around: 环绕通知,可以在目标方法前、后进行处理,还可以修改目标方法返回值
  • @AfterReturning: 后置通知,在目标方法后执行(发生异常便不会执行)
  • @After: 最终通知,不管异常还是正常一定都会执行
  • @AfterThrowing:异常通知,在目标方法发生异常后执行

原理

一提起AOP可能第一反应就是动态代理,但是真的就只有动态代理这么简单吗?我们看一个动态代理的例子(以JDK动态代理为例):

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    Object invoke=null;
    try{
        System.out.println("前置通知:目标方法执行前执行");
        invoke = method.invoke(object, args);
        System.out.println("后置通知:目标方法执行后执行");
    }catch (Exception e){
        System.out.println("异常通知:异常才会执行");
    }finally {
        System.out.println("最终通知:一定会执行");
    }
    return invoke;
}

这乍一看好像就是这个道理啊,好像全满足了呀,真满足吗?环绕通知要怎么做?通知有多个,有多个处理方法怎么做?总不可能一直往这里面塞吧,还有环绕通知需要在invoke方法外面再套一层吧,有多个的话无限套娃?

那要怎么做?注意看这是不是都是串行执行的,串行执行的拦截处理方法是什么?拦截器链!!

流程如下图所示:

在这里插入图片描述

注意看所有通知都是多个:

  • 无环绕,无异常的情况下:所有前置通知→目标方法→所有后置通知→所有最终通知→返回
  • 无环绕,有异常的情况下:所有前置通知→目标方法→所有异常通知→所有最终通知→返回(这里注意前置、目标、后置任何一个异常都会到异常通知)
  • 有环绕的情况下:先执行环绕前置→再执行链条→然后环绕后置(如下图)

在这里插入图片描述

多个环绕会怎样?注意环绕通知本身就是链条的里面的,只不过在最前面执行,多个环绕就会像这样

在这里插入图片描述

好了重点来了,我们知道原理了动态代理+拦截器链,我们需要知道Spring怎么帮我们组装的?

  • 动态代理很简单就两种方式:JDK和Cglib
  • 拦截器链:是不是需要把上述切面里面的方法全提取出来封装好,然后最后组装成链条
  • 连接点:拦截器通过什么连接到一起?需要相同的连接点吧

接下来我们就去验证一下

连接点

在Spring里面连接点是Joinpoint这个接口:

在这里插入图片描述

如上图可见就两个实现类:

ReflectiveMethodInvocation:提供给JDK动态代理方式使用

CglibMethodInvocation:提供给Cglib动态代理方式使用

先不管有啥用,,记得先

拦截器

既然知道是拦截器链了,那每个通知方法应该都有对应的拦截器,我们去看看(只看invoke方法哈):

前置通知拦截器MethodBeforeAdviceInterceptor:

public class MethodBeforeAdviceInterceptor implements MethodInterceptor, BeforeAdvice, Serializable {
    private final MethodBeforeAdvice advice;

    public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice) {
        Assert.notNull(advice, "Advice must not be null");
        this.advice = advice;
    }

    @Nullable
    public Object invoke(MethodInvocation mi) throws Throwable {
        //前置处理  这个就是利用反射执行我们定义的前置方法
        this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
        // 调用链条
        return mi.proceed();
    }
}

后置通知拦截器AfterReturningAdviceInterceptor:

public class AfterReturningAdviceInterceptor implements MethodInterceptor, AfterAdvice, Serializable {
    private final AfterReturningAdvice advice;

    public AfterReturningAdviceInterceptor(AfterReturningAdvice advice) {
        Assert.notNull(advice, "Advice must not be null");
        this.advice = advice;
    }

    @Nullable
    public Object invoke(MethodInvocation mi) throws Throwable {
        //先执行链条
        Object retVal = mi.proceed();
        // 后利用反射执行我们定义的后置通知方法
        this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());
        return retVal;
    }
}

异常通知拦截器ThrowsAdviceInterceptor :

public class ThrowsAdviceInterceptor implements MethodInterceptor, AfterAdvice {
    // 省略............
    @Nullable
    public Object invoke(MethodInvocation mi) throws Throwable {
        try {
            // 这个就是链条
            return mi.proceed();
        } catch (Throwable var4) {
            // 链条报错了 就异常处理(还需要判断是不是需要处理的异常)
            // 异常通知可以指定需要处理的异常
            Method handlerMethod = this.getExceptionHandler(var4);
            if (handlerMethod != null) {
                this.invokeHandlerMethod(mi, var4, handlerMethod);
            }

            throw var4;
        }
    }
    // 省略...............
}

最终通知AspectJAfterAdvice :

public class AspectJAfterAdvice extends AbstractAspectJAdvice implements MethodInterceptor, AfterAdvice, Serializable {
    public AspectJAfterAdvice(Method aspectJBeforeAdviceMethod, AspectJExpressionPointcut pointcut, AspectInstanceFactory aif) {
        super(aspectJBeforeAdviceMethod, pointcut, aif);
    }

    @Nullable
    public Object invoke(MethodInvocation mi) throws Throwable {
        Object var2;
        try {
            // 先执行链条
            var2 = mi.proceed();
        } finally {
            //最终执行
            this.invokeAdviceMethod(this.getJoinPointMatch(), (Object)null, (Throwable)null);
        }

        return var2;
    }

}

环绕通知AspectJAroundAdvice :

public class AspectJAroundAdvice extends AbstractAspectJAdvice implements MethodInterceptor, Serializable {
    public AspectJAroundAdvice(Method aspectJAroundAdviceMethod, AspectJExpressionPointcut pointcut, AspectInstanceFactory aif) {
        super(aspectJAroundAdviceMethod, pointcut, aif);
    }

    @Nullable
    public Object invoke(MethodInvocation mi) throws Throwable {
        if (!(mi instanceof ProxyMethodInvocation)) {
            throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi);
        } else {
            ProxyMethodInvocation pmi = (ProxyMethodInvocation)mi;
            ProceedingJoinPoint pjp = this.lazyGetProceedingJoinPoint(pmi);
            JoinPointMatch jpm = this.getJoinPointMatch(pmi);
            // 这个就是去执行我们 自己写的环绕通知方法  
            // 所以环绕通知方法一定会有个参数嘛 joinPoint.proceed()就是执行链条
            return this.invokeAdviceMethod(pjp, jpm, (Object)null, (Throwable)null);
        }
    }

    protected ProceedingJoinPoint lazyGetProceedingJoinPoint(ProxyMethodInvocation rmi) {
        return new MethodInvocationProceedingJoinPoint(rmi);
    }
}

以上就是关于通知链条里面所有最后会执行的方法,可以看到共同点就是invoke方法的传参MethodInvocation ,这不就是我们之前说的连接点嘛,当然还有很多内置的其他拦截器,但这都跟我们AOP拦截器没关系

以上基础概念相信大家都懂了,接下来我们看看Spring是怎么代理一个Bean的,是怎么为这个Bean组装这些拦截器的

二、Bean介入点

这AOP代理到底是在Bean生成流程中哪个地方介入进来为我们生成代理对象的咧?

从AOP配置加载点一看便知,开启AOP的配置注解是 @EnableAspectJAutoProxy(现在已经默认开启了,不需要加注解也行,配置类是AopAutoConfiguration

EnableAspectJAutoProxy

@EnableAspectJAutoProxy注解内部导入了一个类AspectJAutoProxyRegistrar

在这里插入图片描述

AspectJAutoProxyRegistrar

这个类实现了ImportBeanDefinitionRegistrar接口,这个接口之前说过了,可以注册BeanDefination,所以我们要看看注册的这个是什么?干了什么?

在这里插入图片描述

沿着那个方法一路往下,发现注册了AnnotationAwareAspectJAutoProxyCreator

在这里插入图片描述

AnnotationAwareAspectJAutoProxyCreator

这个类可谓是最重要的类了,从下方的类图上看,它实现了很多接口,还有我们非常熟悉的后置处理器,在这里面主要实现了4个方法:

  • setBeanFactory:实例化后,初始化前调用
  • getEarlyBeanReference:和三级缓存有关,存在循环依赖里面会调用
  • postProcessBeforeInstantiation:实例化前执行
  • postProcessAfterInitialization:初始化后执行

别看有4个方法,其实下面三个方法内部都会调用一样的方法,只是需要注意在Bean生成流程中的介入点
在这里插入图片描述

我们先看一下共同方法是哪个,这个类的顶级父类是AbstractAutoProxyCreator,去看看

AbstractAutoProxyCreator

实例前执行

postProcessBeforeInstantiation()

实例前执行,主要是判断代理目标对象是否已经存在了,存在了就走getAdvicesAndAdvisorsForBean方法,然后调用createProxy()方法创建代理对象

Object cacheKey = this.getCacheKey(beanClass, beanName);
        if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {
            if (this.advisedBeans.containsKey(cacheKey)) {
                return null;
            }

            if (this.isInfrastructureClass(beanClass) || this.shouldSkip(beanClass, beanName)) {
                this.advisedBeans.put(cacheKey, Boolean.FALSE);
                return null;
            }
        }
        // 判断代理目标对象是否已经存在了 存在了就进入代理流程
        TargetSource targetSource = this.getCustomTargetSource(beanClass, beanName);
        if (targetSource != null) {
            if (StringUtils.hasLength(beanName)) {
                this.targetSourcedBeans.add(beanName);
            }

            Object[] specificInterceptors = this.getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
            // 创建动态代理对象
            Object proxy = this.createProxy(beanClass, beanName, specificInterceptors, targetSource);
            this.proxyTypes.put(cacheKey, proxy.getClass());
            return proxy;
        } else {
            return null;
        }

初始化后执行

postProcessAfterInitialization

初始化后执行,会调用wrapIfNecessary()方法

//该bean初始化完毕之后,回调该方法判断该bean是否需要被代理
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
    if (bean != null) {
        Object cacheKey = this.getCacheKey(bean.getClass(), beanName);
        //如果该bean未执行过AOP,则进行封装;如果执行过,则不再进行封装
        if (this.earlyProxyReferences.remove(cacheKey) != bean) {
            return this.wrapIfNecessary(bean, beanName, cacheKey);
        }
    }

    return bean;
}

wrapIfNecessary()方法也会调用getAdvicesAndAdvisorsForBean方法来获取对应的通知处理,如果没获取到通知处理方法说明不需要代理,获取到了就要创建代理对象了createProxy()

注意: 这里的通知处理就是切面里面的通知方法,getAdvicesAndAdvisorsForBean就是获取所有的切面类里面的切点及通知方法与Bean来匹配,匹配上了说明这个Bean要被代理,同时会封装匹配的切点对应的所有通知方法返回

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
    if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
        return bean;
    } else if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
        return bean;
    } else if (!this.isInfrastructureClass(bean.getClass()) && !this.shouldSkip(bean.getClass(), beanName)) {
        // 获取该bean的所有的通知处理
        Object[] specificInterceptors = this.getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, (TargetSource)null);
        // 获取的通知处理不为空 说明要代理
        if (specificInterceptors != DO_NOT_PROXY) {
            this.advisedBeans.put(cacheKey, Boolean.TRUE);
             // 创建代理
            Object proxy = this.createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
            this.proxyTypes.put(cacheKey, proxy.getClass());
            return proxy;
        } else {
            // 为空就不需要创建代理了  直接返回Bean
            this.advisedBeans.put(cacheKey, Boolean.FALSE);
            return bean;
        }
    } else {
        this.advisedBeans.put(cacheKey, Boolean.FALSE);
        return bean;
    }
}

循环依赖会调用

getEarlyBeanReference

三级缓存,存在循环依赖则会调用,这里put进去代表已经生成代理了,所以后续初始化后调用的时候会get判断一次,这个也会调用wrapIfNecessary() 方法

public Object getEarlyBeanReference(Object bean, String beanName) {
    Object cacheKey = this.getCacheKey(bean.getClass(), beanName);
    this.earlyProxyReferences.put(cacheKey, bean);
    return this.wrapIfNecessary(bean, beanName, cacheKey);
}

总结

所以会在Bean实例化前、循环依赖、初始化后介入处理,当然只会处理一次,最终都会调用getAdvicesAndAdvisorsForBean方法来对Bean进行切点匹配,匹配上了就调用createProxy方法生成代理对象然后返回

三、处理切面

AbstractAdvisorAutoProxyCreator.getAdvicesAndAdvisorsForBean()

会先获取所有的切面其下的通知方法,然后根据切点表达式去和这个Bean对象匹配,将匹配成功的通知方法返回,这就说明该Bean需要被代理,匹配成功的通知方法排序后就是需要执行的方法调用链

@Nullable
protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {
    // 获取所有切面其下的切面通知方法
    List<Advisor> advisors = this.findEligibleAdvisors(beanClass, beanName);
    // 为空返回空数组 不为空转成数组返回
    return advisors.isEmpty() ? DO_NOT_PROXY : advisors.toArray();
}

// 获取所有切面及其下的切面通知方法
protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
    // 获取所有切面及其下的切面通知方法
    List<Advisor> candidateAdvisors = this.findCandidateAdvisors();
    // 从中根据切点筛选出符合Bean的通知方法
    List<Advisor> eligibleAdvisors = this.findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
    this.extendAdvisors(eligibleAdvisors);
    if (!eligibleAdvisors.isEmpty()) {
        eligibleAdvisors = this.sortAdvisors(eligibleAdvisors);
    }

    return eligibleAdvisors;
}

获取所有切面其下通知方法

获取切面

AnnotationAwareAspectJAutoProxyCreator.findCandidateAdvisors

有个父类的方法是获取一些实现了Advisor接口的Bean,我们重点关注被@Aspect注解标识的Bean的处理

protected List<Advisor> findCandidateAdvisors() {
    // 获取所有实现了Advisor接口的Bean 有些内置的比如事务
    List<Advisor> advisors = super.findCandidateAdvisors();
    if (this.aspectJAdvisorsBuilder != null) {
        // 获取被注解@Aspect标识的Bean 以及其下的切点和通知方法
        advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
    }

    return advisors;
}

BeanFactoryAspectJAdvisorsBuilder.buildAspectJAdvisors

会遍历所有的Bean找到其中被注解 @Aspect 标识的,然后去处理其下的切点和通知方法

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();
                List<String> aspectNames = new ArrayList();
                String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, Object.class, true, false);
                String[] var18 = beanNames;
                int var19 = beanNames.length;
                 // 遍历所有的Bean
                for(int var7 = 0; var7 < var19; ++var7) {
                    String beanName = var18[var7];
                    if (this.isEligibleBean(beanName)) {
                        Class<?> beanType = this.beanFactory.getType(beanName, false);
                        // 判断是否被@Aspect注解标识  标示的就需要去处理其下的切点和通知方法
                        if (beanType != null && this.advisorFactory.isAspect(beanType)) {
                            aspectNames.add(beanName);
                            AspectMetadata amd = new AspectMetadata(beanType, beanName);
                            if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {
                                MetadataAwareAspectInstanceFactory factory = new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);
                                // 去获取其下的切点和通知方法
                                List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);
                                if (this.beanFactory.isSingleton(beanName)) {
                                    this.advisorsCache.put(beanName, classAdvisors);
                                } else {
                                    this.aspectFactoryCache.put(beanName, factory);
                                }

                                advisors.addAll(classAdvisors);
                            } 
                            // 省略..............
                        }
                    }
                }

                this.aspectBeanNames = aspectNames;
                return advisors;
            }
        }
    }

    // 省略..............
}

获取切面下的通知方法

ReflectiveAspectJAdvisorFactory.getAdvisors

遍历切面下的所有方法,去找方法上是否有相应的注解,如果有则需要封装处理

public List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory aspectInstanceFactory) {
        Class<?> aspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();
        String aspectName = aspectInstanceFactory.getAspectMetadata().getAspectName();
        this.validate(aspectClass);
        MetadataAwareAspectInstanceFactory lazySingletonAspectInstanceFactory = new LazySingletonAspectInstanceFactoryDecorator(aspectInstanceFactory);
        List<Advisor> advisors = new ArrayList();
        // 获取切面下的所有方法
        Iterator var6 = this.getAdvisorMethods(aspectClass).iterator();
        // 遍历所有方法
        while(var6.hasNext()) {
            Method method = (Method)var6.next();
            // 判断该方法是否被相关注解标识  标识的方法处理后封装返回
            Advisor advisor = this.getAdvisor(method, lazySingletonAspectInstanceFactory, 0, aspectName);
            if (advisor != null) {
                advisors.add(advisor);
            }
        }
        // 省略......

        return advisors;
    }

ReflectiveAspectJAdvisorFactory.getAdvisor

遍历我需要的注解,在方法上找注解是否存在,存在的就需要封装处理

public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrderInAspect, String aspectName) {
    this.validate(aspectInstanceFactory.getAspectMetadata().getAspectClass());
    // 获取方法上的注解 实际就是遍历需要的注解 一个个找
    AspectJExpressionPointcut expressionPointcut = this.getPointcut(candidateAdviceMethod, aspectInstanceFactory.getAspectMetadata().getAspectClass());
    // 没有对应的注解就返回null  有对应的注解就需要处理封装后返回
    return expressionPointcut == null ? null : new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod, this, aspectInstanceFactory, declarationOrderInAspect, aspectName);
}

private AspectJExpressionPointcut getPointcut(Method candidateAdviceMethod, Class<?> candidateAspectClass) {
    // 看下面方法
    AspectJAnnotation<?> aspectJAnnotation = AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
    if (aspectJAnnotation == null) {
        return null;
    } else {
        // 找到了就设置一下切点上的表达式
        AspectJExpressionPointcut ajexp = new AspectJExpressionPointcut(candidateAspectClass, new String[0], new Class[0]);
        ajexp.setExpression(aspectJAnnotation.getPointcutExpression());
        if (this.beanFactory != null) {
            ajexp.setBeanFactory(this.beanFactory);
        }

        return ajexp;
    }
}
// ASPECTJ_ANNOTATION_CLASSES = new Class[]{Pointcut.class, Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class};
protected static AbstractAspectJAdvisorFactory.AspectJAnnotation<?> findAspectJAnnotationOnMethod(Method method) {
    // 遍历需要的注解,一个一个找
    Class[] var1 = ASPECTJ_ANNOTATION_CLASSES;
    int var2 = var1.length;
    for(int var3 = 0; var3 < var2; ++var3) {
        Class<?> clazz = var1[var3];
        AbstractAspectJAdvisorFactory.AspectJAnnotation<?> foundAnnotation = findAnnotation(method, clazz);
        if (foundAnnotation != null) {
            return foundAnnotation;
        }
    }
    return null;
}

通知方法的封装

InstantiationModelAwarePointcutAdvisorImpl

这个在构造里面就会对通知方法进行处理封装

public InstantiationModelAwarePointcutAdvisorImpl(AspectJExpressionPointcut declaredPointcut, Method aspectJAdviceMethod, AspectJAdvisorFactory aspectJAdvisorFactory, MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) {
    this.declaredPointcut = declaredPointcut;
    this.declaringClass = aspectJAdviceMethod.getDeclaringClass();
    this.methodName = aspectJAdviceMethod.getName();
    this.parameterTypes = aspectJAdviceMethod.getParameterTypes();
    this.aspectJAdviceMethod = aspectJAdviceMethod;
    this.aspectJAdvisorFactory = aspectJAdvisorFactory;
    this.aspectInstanceFactory = aspectInstanceFactory;
    this.declarationOrder = declarationOrder;
    this.aspectName = aspectName;
    if (aspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) {
        Pointcut preInstantiationPointcut = Pointcuts.union(aspectInstanceFactory.getAspectMetadata().getPerClausePointcut(), this.declaredPointcut);
        this.pointcut = new InstantiationModelAwarePointcutAdvisorImpl.PerTargetInstantiationModelPointcut(this.declaredPointcut, preInstantiationPointcut, aspectInstanceFactory);
        this.lazy = true;
    } else {
        this.pointcut = this.declaredPointcut;
        this.lazy = false;
        // 封装通知方法
        this.instantiatedAdvice = this.instantiateAdvice(this.declaredPointcut);
    }

}

ReflectiveAspectJAdvisorFactory.getAdvice

所有的通知方法都会被封装成对应处理类

public Advice getAdvice(Method candidateAdviceMethod, AspectJExpressionPointcut expressionPointcut, MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) {
    Class<?> candidateAspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();
    this.validate(candidateAspectClass);
    AspectJAnnotation<?> aspectJAnnotation = AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
    if (aspectJAnnotation == null) {
        return null;
    } else if (!this.isAspect(candidateAspectClass)) {
        throw new AopConfigException("Advice must be declared inside an aspect type: Offending method '" + candidateAdviceMethod + "' in class [" + candidateAspectClass.getName() + "]");
    } else {
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Found AspectJ method: " + candidateAdviceMethod);
        }

        Object springAdvice;
       // 根据方法上的注解类型 封装对应的通知方法处理类
      switch(aspectJAnnotation.getAnnotationType()) {
        case AtPointcut:
            if (this.logger.isDebugEnabled()) {
                this.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())) {
                ((AbstractAspectJAdvice)springAdvice).setReturningName(afterReturningAnnotation.returning());
            }
            break;
        case AtAfterThrowing:
            springAdvice = new AspectJAfterThrowingAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
            AfterThrowing afterThrowingAnnotation = (AfterThrowing)aspectJAnnotation.getAnnotation();
            if (StringUtils.hasText(afterThrowingAnnotation.throwing())) {
                ((AbstractAspectJAdvice)springAdvice).setThrowingName(afterThrowingAnnotation.throwing());
            }
            break;
        default:
            throw new UnsupportedOperationException("Unsupported advice type on method: " + candidateAdviceMethod);
        }

        ((AbstractAspectJAdvice)springAdvice).setAspectName(aspectName);
        ((AbstractAspectJAdvice)springAdvice).setDeclarationOrder(declarationOrder);
        String[] argNames = this.parameterNameDiscoverer.getParameterNames(candidateAdviceMethod);
        if (argNames != null) {
            ((AbstractAspectJAdvice)springAdvice).setArgumentNamesFromStringArray(argNames);
        }

        ((AbstractAspectJAdvice)springAdvice).calculateArgumentBindings();
        return (Advice)springAdvice;
    }
}

通知方法与Bean匹配

AbstractAdvisorAutoProxyCreator.findAdvisorsThatCanApply

protected List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> beanClass, String beanName) {
    ProxyCreationContext.setCurrentProxiedBeanName(beanName);

    List var4;
    try {
        // 通知方法集合与Bean匹配
        var4 = AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);
    } finally {
        ProxyCreationContext.setCurrentProxiedBeanName((String)null);
    }

    return var4;
}

总结

所以这一步会找到所有的切面,遍历其下的所有切点和通知方法,然后根据切点中的表达式去与Bean对象匹配,获取所有匹配成功的通知方法,将这些通知方法排序后就是最后的方法执行链,同时也说明该Bean需要被代理,所以需要创建代理对象

四、创建代理对象

AbstractAutoProxyCreator.createProxy

这里实际就是在创建代理对象前填充一下必要信息,然后创建代理对象,默认是采用JDK动态代理,如果被代理的目标对象不是接口,则会采用Cglib动态代理

  • CglibAopProxy:Cglib动态代理逻辑类
  • JdkDynamicAopProxy:Jdk动态代理逻辑类(我们以这个为例)
protected Object createProxy(Class<?> beanClass, @Nullable String beanName, @Nullable Object[] specificInterceptors, TargetSource targetSource) {
        if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
            AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory)this.beanFactory, beanName, beanClass);
        }

        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.copyFrom(this);
        
        // 省略一大段...........
        
        // 匹配成功的某些通知方法会被包装成拦截器 上面说过了
        Advisor[] advisors = this.buildAdvisors(beanName, specificInterceptors);
        proxyFactory.addAdvisors(advisors);
        proxyFactory.setTargetSource(targetSource);
        this.customizeProxyFactory(proxyFactory);
        proxyFactory.setFrozen(this.freezeProxy);
        if (this.advisorsPreFiltered()) {
            proxyFactory.setPreFiltered(true);
        }

        ClassLoader classLoader = this.getProxyClassLoader();
        if (classLoader instanceof SmartClassLoader && classLoader != beanClass.getClassLoader()) {
            classLoader = ((SmartClassLoader)classLoader).getOriginalClassLoader();
        }
        // 上面设置搞定后 就要获取代理对象 JDK还是Cglib
        return proxyFactory.getProxy(classLoader);
    }

JdkDynamicAopProxy.getProxy

这一步很简单就是直接创建代理对象,处理类是this,说明该类本身就是处理类

public Object getProxy(@Nullable ClassLoader classLoader) {
    if (logger.isTraceEnabled()) {
        logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());
    }

    return Proxy.newProxyInstance(classLoader, this.proxiedInterfaces, this);
}

五、代理执行方法

我们以JDK动态代理为例,最终代理对象在执行方法的时候就会调用该方法:

JdkDynamicAopProxy.invoke

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object oldProxy = null;
        boolean setProxyContext = false;
        TargetSource targetSource = this.advised.targetSource;
        Object target = null;

        Class var8;
        try {
            
            //   省略...........
            
            if (method.getDeclaringClass() != DecoratingProxy.class) {
                Object retVal;
                //   省略...........

                target = targetSource.getTarget();
                Class<?> targetClass = target != null ? target.getClass() : null;
                // 根据具体要执行的方法 再去之前匹配成功的通知方法集合中找对应的增强方法
                // 前面匹配的通知方法集合并不一定是针对类下的所有方法 所以还需要匹配一次
                List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
                // 为空说明该方法并不需要增强 所以直接调用原本方法即可
                if (chain.isEmpty()) {
                    Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
                    retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
                } else {
                    // 不为空说明需要增强 所以会包装一个连接点 
                    // 然后执行 调用链条 
                    MethodInvocation invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
                    retVal = invocation.proceed();
                }

                Class<?> returnType = method.getReturnType();
                if (retVal != null && retVal == target && returnType != Object.class && returnType.isInstance(proxy) && !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
                    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);
                }

                Object var12 = retVal;
                return var12;
            }

            var8 = AopProxyUtils.ultimateTargetClass(this.advised);
        } finally {
            //   省略...........
        }
        return var8;
    }

六、总结

  • AOP代理对象的生成是在Bean实例化前、循环依赖、初始化后这三个位置判断生成的(以初始化后为主,其他两个阶段属于特殊阶段)
  • 通过获取所有的切面下的通知方法以切点表达式来与Bean匹配,来判断该Bean是否需要被代理,同时准备好了与该Bean相关的所有增强方法
  • AOP默认采用JDK动态代理的方式,如果被代理目标对象不是接口,则会采用Cglib的代理方法
  • AOP的底层原理虽然是动态代理,但是我觉得最重要的还是执行的方法调用链非常巧妙
  • 在逻辑实现上:每种通知在调用链上执行的方式及其执行顺序决定了其扮演的角色
  • 每个通知最后执行类在前面已经给出,可直接查看学习

最后附上个执行结构图

在这里插入图片描述

个人博客: 全是干货,相信不会让你失望

详解Spring学习总结——Spring实现AOP的多种方式
08-31
主要介绍了详解Spring学习总结——Spring实现AOP的多种方式,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
spring aop实现原理
03-15
NULL 博文链接:https://zhang-yingjie-qq-com.iteye.com/blog/319927
AOP如何实现及实现原理
weixin_48986464的博客
04-06 1万+
AOP如何实现及实现原理 1.AOP简介 2.AOP实现原理 3.静态代理和动态代理的区别 4.代码实现举例 1. AOP简介: AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了
Spring高手之路17——动态代理的艺术与实践
最新发布
卓越无关环境,保持空杯心态——靡不有初,鲜克有终
04-11 1万+
本文深入分析了JDK和CGLIB两种动态代理技术在Spring框架中的应用。讨论了动态代理的基础概念,通过实例展示了如何实现和应用这两种方法,并比较了它们的性能差异及适用场景。进一步,探讨了在动态代理中实现熔断限流和日志监控的策略,以及如何利用动态代理优化Spring应用的设计和功能。
Spring AOP---深入剖析AOP注解实现原理
gongsenlin341的博客
05-26 1421
文章目录前言1.概述2.Spring如何集成AspectJ AOP3.AOP通知链如何生成4.何时进行AOP动态代理以及动态代理的方式5.通知链的调用过程6.后续 前言 阅读本文之前建议先掌握的前置知识: @Import注解的使用和实现原理 Bean的生命周期 1.概述 Spring AOP有常用的两种方式,一种是使用XML的方式,另一种是使用注解的方式。本文将详细的分析注解方式的实现原理。将会从如下几个点展开。 Spring如何集成AspectJ AOP AOP通知链如何形成 何时进行AOP动态代理
Spring AOP实现原理简介
热门推荐
wyl6019的博客
04-28 7万+
AOP联盟标准AOP联盟将AOP体系分为三层,从三层结构可以看出,AOP实现方式有很多种,包括反射、元数据处理、程序处理、拦截器处理等,通过本节学习,你就会看到Spring AOP的实现使用的是Java语言本身的特性,即Java Proxy代理类、拦截器技术实现。AOP简介概念切面(Aspect) :官方的抽象定义为“一个关注点的模块化,这个关注点可能会横切多个对象”。连接点(Joinpoint)...
SpringAOP实现原理
Java小技巧
11-09 497
什么是AOP AOP(Aspect-OrientedProgramming,面向方面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适
AOP原理
Just Do It!
03-09 634
AOP概述 软件的编程语言最终的目的就是用更自然更灵活的方式模拟世界,从原始机器语言到过程语言再到面向对象的语言,我们看到编程语言在一步步用更自然、更强大的方式描述软件。AOP是软件开发思想的一个飞跃,AOP的引入将有效弥补OOP的不足,OOP和AOP分别从纵向和横向对软件进行抽象,有效地消除重复性的代码,使代码以更优雅的更有效的方式进行逻辑表达。 AOP有三种植入切面的方法:其一是编译期织入
spring中的Aop大致流程
m0_49708063的博客
11-16 625
首先我们先来一个基础类 就是需要被Aop代理的类 再来一个切面 加上配置类 启动类 打上断点 debug执行 f7进入方法中查看 我们看一看chain中有什么属性 ExposeInvocationInterceptor的用处我们等下讲解 继续往下看 进入方法 来执行链式调用 就是我们所说的责任链模式 new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed
Spring aop实现原理
goxingman的博客
12-02 171
Spring aop实现原理 简介   前段时间写的java设计模式–代理模式,最近在看Spring Aop的时候,觉得于代理模式应该有密切的联系,于是决定了解下Spring Aop实现原理。 说起AOP就不得不说下OOP了,OOP中引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。但是,如果我们需要为部分对象引入公共部分的时候,OOP就会引入大量重复的代码。例如...
深入浅出Spring(三) AOP详解
weixin_30416497的博客
08-19 158
上次的博文深入浅出Spring(二) IoC详解中,我为大家简单介绍了一下Spring框架核心内容中的IoC,接下来我们继续讲解另一个核心AOP(Aspect Oriented Programming),即面向切面编程。 1、OOP回顾 在介绍AOP之前先来回顾一下大家都比较熟悉的OOP(Object Oriented Programming)。OOP主要是为...
跟开涛学Spring
09-07
1.24 【第六章】 AOP 之 6.5 AspectJ切入点语法详解 ——跟我学spring3 . . . . . . . . . . . . . . . . . . . . .260 1.25 【第六章】 AOP 之 6.6 通知参数 ——跟我学spring3 . . . . . . . . . . . . . . . . ....
spring2.5 api 离线版
09-10
Spring2.5-Reference_zh_CN.chm Spring2.5-中文参考手册.chm spring——AOP,IOC.doc Spring框架快速入门之简介.doc spring配置全书.doc Spring中的IOC与AOP详解.ppt
spring杂谈 作者zhang KaiTao
10-17
1.6 »SpringAOP AspectJ切入点语法详解(最全了,不需要再去其他地找了) 1.7 Spring开闭原则的表现-BeanPostProcessor扩展点-2 1.8 Spring3.1 对Bean Validation规范的新支持(方法级别验证) 1.9 Spring对事务...
跟我学spring3(1-7)
06-20
【第六章】 AOP 之 6.5 AspectJ切入点语法详解 ——跟我学spring3 【第六章】 AOP 之 6.6 通知参数 ——跟我学spring3 【第六章】 AOP 之 6.7 通知顺序 ——跟我学spring3 【第六章】 AOP 之 6.8 切面实例化模型 ...
springAop看这篇就够了-- Spring AOP是什么?你都拿它做什么?
大碍桃花开
01-13 907
为什么会有面向切面编程(AOP)?我们知道Java是一个面向对象(OOP)的语言,但它有一些弊端,比如当我们需要为多个不具有继承关系的对象引入一个公共行为,例如日志、权限验证、事务等功能时,只能在在每个对象里引用公共行为。这样做不便于维护,而且有大量重复代码。AOP的出现弥补了OOP的这点不足。 为了阐述清楚Spring AOP,我们从将以下方面进行讨论: 代理模式 静态代理原理及实践 动态代理原...
SpringAOP 原理详解
爱老婆爱生活~
04-13 1096
AOPSpring 框架中的一个核心概念。本文将介绍 Spring AOP的底层实现原理,并通过源代码解析来详细阐述其实现过程。
SpringAOP底层原理(动态代理)-》 AOP概念及术语 -》 AOP实现
ebb29bbe的博客
03-05 1534
AOP —— 面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。 是Spring框架中的一个重要内容。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
SpringAOP实现原理
qq_44918331的博客
03-11 1万+
一、什么是 AOPAOP(Aspect-Oriented Programming,面向方面编程),对 OOP(Object-Oriented Programming,面向对象编程) 【OOP与AOP】 概念 AOP(Aspect-Oriented Programming,面向方面编程) OOP(Object-Oriented Programming,面向对象编程) 方向 OOP 定义从上到下的关系 AOP 定义从左到右的关系 【两个部分】 核心关注点 业务处理的主要流程
springaop原理
05-31
SpringAOP基于动态代理实现,它通过在运行时动态地生成代理对象,将横切逻辑织入到目标对象的方法调用中。 在Spring AOP中,切面是以Advice和Pointcut为基础来实现的。Advice是横切逻辑的具体实现,而Pointcut则是用于定义切入点的表达式。当目标对象执行方法时,Spring将会根据Pointcut的表达式来判断是否要执行Advice,如果匹配成功,则将Advice织入到目标方法中。 Spring AOP支持多种类型的Advice,包括前置通知(Before)、后置通知(AfterReturning)、异常通知(AfterThrowing)和环绕通知(Around)。前置通知在目标方法执行前执行,后置通知在目标方法执行后执行,异常通知在目标方法抛出异常时执行,而环绕通知则可以在目标方法的前后执行自定义的逻辑。 总之,Spring AOP实现原理是通过动态代理技术来实现横切逻辑的织入,同时通过Advice和Pointcut的组合来定义和匹配切入点。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
写文章

热门文章

  • Spring——三级缓存解决循环依赖详解 15930
  • Spring——Bean注入几种方式(放入容器) 10055
  • CPU 100%排查及常见案例 9991
  • springboot整合Elasticsearch7.6实现简单查询及高亮分词查询 8152
  • Spring——AOP原理及流程详解 7064

分类专栏

  • elasticsearch 6篇
  • springboot 17篇
  • 结构与算法 4篇
  • netty 8篇
  • spring 7篇
  • mybatis 4篇
  • 运维 3篇
  • java 16篇
  • nacos 2篇
  • 分布式事务 5篇
  • mysql 5篇
  • RocketMq 1篇
  • Tomcat 1篇
  • redis 1篇

最新评论

  • Spring——三级缓存解决循环依赖详解

    Colins~: 主要还是动态代理,如果被代理的对象不完整(只是实例化,并没有属性初始化),那么这时候产生的代理对象就会存在风险(属性值为null),而且这个风险并不会随着原对象完整后(初始化完)而得到填充,因为代理对象已经产生了

  • Spring——三级缓存解决循环依赖详解

    一天睡二十四小时: 如果只有二级缓存,那么二级缓存本来就是存储的不完整的bean,一级缓存才是完整的bean,所以风险不是指这个吧?

  • Spring——三级缓存解决循环依赖详解

    一天睡二十四小时: `二级缓存能否解决循环依赖? 可以,但风险比三级缓存更大` 请教下, 这句话中`风险` 是指什么风险? 我理解的是如果用二级缓存,在出现ABC,BA,CA依赖的时候,A如果有动态代理,会多次创建。别的还有什么风险?

  • 分布式事务(二)———2PC/3PC(强一致性)解决方案

    这都行?: 有问题吧。第一阶段 操作成功 事务是不会提交的。到了第二阶段再判断是否 提交

  • Spring——AOP原理及流程详解

    m0_62760781: 这么好的文章,没人评论,狠狠顶一下

您愿意向朋友推荐“博客详情页”吗?

  • 强烈不推荐
  • 不推荐
  • 一般般
  • 推荐
  • 强烈推荐
提交

最新文章

  • Elasticsearch 7.6 - API高阶操作篇
  • Elasticsearch 7.6 - Springboot应用基础操作篇
  • Elasticsearch 7.6 - APi基础操作篇
2023年21篇
2022年34篇
2021年1篇
2020年24篇

目录

目录

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值

深圳SEO优化公司天津阿里店铺运营报价三亚网页制作报价怀化百度竞价哪家好龙岩模板网站建设哪家好银川网站推广系统推荐临沂网站建设公司乌海营销型网站建设潮州阿里店铺托管报价铜仁外贸网站建设报价乐山百搜标王报价四平英文网站建设报价绥化企业网站改版公司泰州百搜标王多少钱梅州百度网站优化排名价格平顶山网站优化按天计费四平网站seo优化公司株洲网站优化排名哪家好济南网站seo优化价格枣庄网站优化排名推荐日照网站建设设计价格黄山英文网站建设报价鹰潭百度关键词包年推广哪家好民治百度竞价包年推广林芝百度关键词包年推广延安高端网站设计推荐安庆百度seo哪家好毕节网页制作哪家好南充网站优化按天扣费多少钱拉萨网站优化按天扣费价格张掖网站推广系统推荐歼20紧急升空逼退外机英媒称团队夜以继日筹划王妃复出草木蔓发 春山在望成都发生巨响 当地回应60岁老人炒菠菜未焯水致肾病恶化男子涉嫌走私被判11年却一天牢没坐劳斯莱斯右转逼停直行车网传落水者说“没让你救”系谣言广东通报13岁男孩性侵女童不予立案贵州小伙回应在美国卖三蹦子火了淀粉肠小王子日销售额涨超10倍有个姐真把千机伞做出来了近3万元金手镯仅含足金十克呼北高速交通事故已致14人死亡杨洋拄拐现身医院国产伟哥去年销售近13亿男子给前妻转账 现任妻子起诉要回新基金只募集到26元还是员工自购男孩疑遭霸凌 家长讨说法被踢出群充个话费竟沦为间接洗钱工具新的一天从800个哈欠开始单亲妈妈陷入热恋 14岁儿子报警#春分立蛋大挑战#中国投资客涌入日本东京买房两大学生合买彩票中奖一人不认账新加坡主帅:唯一目标击败中国队月嫂回应掌掴婴儿是在赶虫子19岁小伙救下5人后溺亡 多方发声清明节放假3天调休1天张家界的山上“长”满了韩国人?开封王婆为何火了主播靠辱骂母亲走红被批捕封号代拍被何赛飞拿着魔杖追着打阿根廷将发行1万与2万面值的纸币库克现身上海为江西彩礼“减负”的“试婚人”因自嘲式简历走红的教授更新简介殡仪馆花卉高于市场价3倍还重复用网友称在豆瓣酱里吃出老鼠头315晚会后胖东来又人满为患了网友建议重庆地铁不准乘客携带菜筐特朗普谈“凯特王妃P图照”罗斯否认插足凯特王妃婚姻青海通报栏杆断裂小学生跌落住进ICU恒大被罚41.75亿到底怎么缴湖南一县政协主席疑涉刑案被控制茶百道就改标签日期致歉王树国3次鞠躬告别西交大师生张立群任西安交通大学校长杨倩无缘巴黎奥运

深圳SEO优化公司 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化