AOP流程及原理

目录

一、AOP结构介绍

  • @Pointcut

  • 通知

  • 原理

  • 连接点

  • 拦截器


二、Bean介入点

  • EnableAspectJAutoProxy

  • AspectJAutoProxyRegistrar

  • AnnotationAwareAspectJAutoProxyCreator

  • AbstractAutoProxyCreator

    • 实例前执行

    • 初始化后执行

    • 循环依赖会调用

  • 总结


三、处理切面

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

    • 获取切面

    • 获取切面下的通知方法

    • 通知方法的封装

  • 通知方法与Bean匹配

  • 总结


四、创建代理对象


五、代理执行方法


六、总结


一、AOP结构介绍

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

结果:

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

  • @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动态代理为例):

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


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


流程如下图所示:


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

  • 无环绕,无异常的情况下:所有前置通知 --> 目标方法 --> 所有后置通知 --> 所有最终通知 --> 返回

  • 无环绕,有异常的情况下:所有前置通知 --> 目标方法 --> 所有异常通知 --> 所有最终通知 --> 返回(这里注意前置、目标、后置任何一个异常都会到异常通知)

  • 有环绕的情况下:先执行环绕前置 --> 再执行链条 --> 然后环绕后置(如下图)

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

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

  • 动态代理很简单就两种方式:JDK 和 Cglib

  • 拦截器链:是不是需要把上述切面里面的方法全提取出来封装好,然后最后组装成链条

  • 连接点:拦截器通过什么连接到一起?需要相同的连接点吧,所谓连接点是指那些被拦截到的方法


连接点

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

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

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

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


二、拦截器

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


1、前置通知拦截器 MethodBeforeAdviceInterceptor:


2、后置通知拦截器AfterReturningAdviceInterceptor:


3、异常通知拦截器ThrowsAdviceInterceptor :


4、最终通知AspectJAfterAdvice :


5、环绕通知AspectJAroundAdvice :

以上就是关于通知链条里面所有最后会执行的方法,可以看到共同点就是invoke方法的传参MethodInvocation ,这不就是我们之前说的连接点嘛

当然还有很多内置的其他拦截器,但这都跟我们AOP拦截器没关系


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


三、Bean介入点


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


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


1、EnableAspectJAutoProxy

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


2、AspectJAutoProxyRegistrar

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

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


3、AnnotationAwareAspectJAutoProxyCreator

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

  • setBeanFactory:实例化后,初始化前调用

  • getEarlyBeanReference:和三级缓存有关,存在循环依赖里面会调用

  • postProcessBeforeInstantiation:实例化前执行

  • postProcessAfterInitialization:初始化后执行


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


4、AbstractAutoProxyCreator

在目标对象创建之前,执行 AbstractAutowireCapableBeanFactory#Object bean = resolveBeforeInstantiation(beanName, mbdToUse) --> applyBeanPostProcessorsBeforeInstantiation(targetType, beanName) ---> Object result = ibp.postProcessBeforeInstantiation(beanClass, beanName),主要是判断代理目标对象是否已经存在,存在了且需要代理就走getAdvicesAndAdvisorsForBean方法,然后调用createProxy()方法创建代理对象


5、目标对象创建完且初始化后执行

目标对象创建完且初始化后执行,AbstractAutowireCapableBeanFactory#initializeBean --> wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName)

--> result = beanProcessor.postProcessAfterInitialization(result, beanName),会调用wrapIfNecessary()方法

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


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


6、循环依赖会调用

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


7、总结

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


四、处理切面

AbstractAdvisorAutoProxyCreator.getAdvicesAndAdvisorsForBean()作用:

会先获取所有的切面其下的通知方法(@Before、@Around、@AfterReturning、@After等),然后根据切点表达式去和这个Bean对象匹配,

将匹配成功的通知方法返回,这就说明该Bean需要被代理,匹配成功的通知方法排序后就是需要执行的方法调用链


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

使用了模板方法模式,获取切面,AnnotationAwareAspectJAutoProxyCreator.findCandidateAdvisors 有个父类的方法是获取一些实现了Advisor接口的Bean,我们重点关注被@Aspect注解标识的Bean的处理

BeanFactoryAspectJAdvisorsBuilder.buildAspectJAdvisors


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


2、获取@Aspect切面下的通知方法


ReflectiveAspectJAdvisorFactory.getAdvisors


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


ReflectiveAspectJAdvisorFactory.getAdvisor

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


3、通知方法的封装

InstantiationModelAwarePointcutAdvisorImpl

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

  public Advice getAdvice(Method candidateAdviceMethod, AspectJExpressionPointcut expressionPointcut,
  			MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) {

  		Class<?> candidateAspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();
  		validate(candidateAspectClass);

  		AspectJAnnotation<?> aspectJAnnotation =
  				AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
  		if (aspectJAnnotation == null) {
  			return null;
  		}

  		// If we get here, we know we have an AspectJ method.
  		// Check that it's an AspectJ-annotated class
  		if (!isAspect(candidateAspectClass)) {
  			throw new AopConfigException("Advice must be declared inside an aspect type: " +
  					"Offending method '" + candidateAdviceMethod + "' in class [" +
  					candidateAspectClass.getName() + "]");
  		}

  		if (logger.isDebugEnabled()) {
  			logger.debug("Found AspectJ method: " + candidateAdviceMethod);
  		}

  		AbstractAspectJAdvice springAdvice;

  		switch (aspectJAnnotation.getAnnotationType()) {
  			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())) {
  					springAdvice.setReturningName(afterReturningAnnotation.returning());
  				}
  				break;
  			case AtAfterThrowing:
  				springAdvice = new AspectJAfterThrowingAdvice(
  						candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
  				AfterThrowing afterThrowingAnnotation = (AfterThrowing) aspectJAnnotation.getAnnotation();
  				if (StringUtils.hasText(afterThrowingAnnotation.throwing())) {
  					springAdvice.setThrowingName(afterThrowingAnnotation.throwing());
  				}
  				break;
  			case AtAround:
  				springAdvice = new AspectJAroundAdvice(
  						candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);
  				break;
  			case AtPointcut:
  				if (logger.isDebugEnabled()) {
  					logger.debug("Processing pointcut '" + candidateAdviceMethod.getName() + "'");
  				}
  				return null;
  			default:
  				throw new UnsupportedOperationException(
  						"Unsupported advice type on method: " + candidateAdviceMethod);
  		}

  		// Now to configure the advice...
  		springAdvice.setAspectName(aspectName);
  		springAdvice.setDeclarationOrder(declarationOrder);
  		String[] argNames = this.parameterNameDiscoverer.getParameterNames(candidateAdviceMethod);
  		if (argNames != null) {
  			springAdvice.setArgumentNamesFromStringArray(argNames);
  		}
  		springAdvice.calculateArgumentBindings();
  		return springAdvice;
  }


4、通知方法与 Bean匹配

AbstractAdvisorAutoProxyCreator.findAdvisorsThatCanApply


5、总结

所以这一步会找到所有的切面,遍历其下的所有切点和通知方法,然后根据切点中的表达式去与Bean对象匹配,获取所有匹配成功的通知方法,

将这些通知方法排序后就是最后的方法执行链,同时也说明该Bean需要被代理,所以需要创建代理对象


五、创建代理对象


AbstractAutoProxyCreator.createProxy


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

  • CglibAopProxy:Cglib动态代理逻辑类

  • JdkDynamicAopProxy:Jdk动态代理逻辑类(我们以这个为例)

JdkDynamicAopProxy.getProxy


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


六、代理执行方法


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


JdkDynamicAopProxy.invoke


六、总结


1、AOP代理对象的生成机会是在Bean实例化前、初始化后这两个位置判断生成的(以初始化后为主,其他阶段属于特殊阶段)


2、通过获取所有的切面下的通知方法以切点表达式来与Bean匹配,来判断该Bean是否需要被代理,同时准备好了与该Bean相关的所有增强方法


3、AOP默认采用JDK动态代理的方式,如果被代理目标对象不是接口,则会采用Cglib的代理方法


4、AOP的底层原理虽然是动态代理,但是我觉得最重要的还是执行的方法调用链非常巧妙


5、在逻辑实现上:每种通知在调用链上执行的方式及其执行顺序决定了其扮演的角色


6、每个通知最后执行类在前面已经给出,可直接查看学习


最后附上个执行结构图

posted @ 2025-03-22 17:42  jock_javaEE  阅读(22)  评论(0)    收藏  举报