学习aop之前,先要了解设计模式中的动态代理模式
基础概念:
为多个有继承关系的类引入公共行为,可以在父类当中引入(当然,这也会破坏源代码)。 如果为毫不相干的多个类引入公共行为,这种就是aop的功能: 横向切入。
pointcut:切点。 指定增强行为的位置,也就是针对哪些方法。
joinpoint:连接点。 指定增强行为的时机,在方法执行的前、后,或者抛出异常后执行等。
advoice:通知。 指编写的需要执行的逻辑。
aspect:切面。 以上的综合
spring中的使用
a、xml配置模式:
引入依赖
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>5.1.12.RELEASE</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency>
定义切面增强逻辑
public class LogUtils { /** * 业务逻辑开始之前执行 * JoinPoint可以获取业务方法的参数,不需要可以不写 */ public void beforeMethod(JoinPoint joinPoint) { Object[] args = joinPoint.getArgs(); for (int i = 0; i < args.length; i++) { Object arg = args[i]; System.out.println(arg); } System.out.println("业务逻辑开始执行之前执行......."); } /** * 业务逻辑结束时执行(无论异常与否) */ public void afterMethod() { System.out.println("业务逻辑结束时执行,无论异常与否都执行......."); } /** * 异常时时执行 */ public void exceptionMethod() { System.out.println("异常时执行......."); } /** * 业务逻辑正常时执行 */ public void successMethod(Object retVal) { System.out.println("业务逻辑正常时执行......."); } /** * 环绕通知 * */ public Object arroundMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("环绕通知中的beforemethod...."); Object result = null; try{ // 控制原有业务逻辑是否执行 result = proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs()); }catch(Exception e) { System.out.println("环绕通知中的exceptionmethod...."); }finally { System.out.println("环绕通知中的after method...."); } return result; }
spring配置文件
<!--把通知bean交给spring来管理--> <bean id="logUtil" class="com.lagou.utils.LogUtil"></bean> <!--开始aop的配置--> <aop:config> <!--配置切⾯--> <aop:aspect id="logAdvice" ref="logUtil"> <!--配置前置通知--> <aop:before method="printLog" pointcut="execution(com.lagou.service.impl.TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Account))"> </aop:before> </aop:aspect> </aop:config>
对于上面使用的是前置通知,另外还有几种
返回后通知:<aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"></aop:after-returning>
抛出异常后通知: <aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>
方法结束后通知:<aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>
环绕通知:<aop:around method="aroundPrintLog" pointcut-ref="pt1"></aop:around>
有两点注意:
- 上面的切入点 用的是: pointcut-ref 这种需要把pointcut单独提出来配置,然后引用就行了
- <aop:pointcut id="pt1" expression="execution(* com.lagou.edu.service.impl.TransferServiceImpl.*(..))"/>
- LogUtil环绕通知和其他的通知类型有差别:
- 增强逻辑时,环绕通知的方法参数是ProceedingJoinPoint,其他通知的参数是JoinPoint(JoinPoint是获取原业务方法参数等信息,如果不关注可以使用无参方法,也就是不写JoinPoint参数)
- 其他通知一定会执行目标业务方法,而环绕通知可以控制是否执行。 要执行必须手动调用:proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
- ProceedingJoinPoint是JoinPoint的子类,增加了两个方法:
Object proceed() throws Throwable 继续执行业务方法
Object proceed(Object[] var1) throws Throwable 继续执行业务方法,可以传入新参数,也可使用原来的参数(比如上面的:proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs()))
JoinPoint的相关api:
Signature getSignature();
获取目标方法返回值 包名.类名.方法名(参数):void test.Test.test(String)
Object[] getArgs();
获取目标方法的参数值
getTarget().getClass();
获取被代理的对象的class
Object getThis();
获取代理对象
getSignature().getName()
获取当前的方法名 test
getSignature().getDeclaringType().getSimpleName()
获取简单类名 Test
getSignature().getDeclaringTypeName()
包名.类名 test.Test
getTarget().getClass();
获取被代理的对象的class: Test.Class
Object getThis();
获取代理对象
Modifier.toString(joinPoint.getSignature().getModifiers()))
获取方法的声明类型:如public
(MethodSignature)joinPoint.getSignature().getMethod().getParameterTypes();
获取方法的参数类型,返回Class<?> []
(CodeSignature)joinPoint.getSignature().getMethod().getParameterNames();
获取方法的参数名字,返回String []
Object getThis();
获取代理对象
b、xml + 注解模式
- <aop:aspectj-autoproxy/>
将上面xml中的其他aop配置去掉,在LogUtil类中添加相应的注解即可
@Pointcut("execution(* com.lagou.edu.service.impl.TransferServiceImpl.*(..))") public void pt1(){ } /** * 业务逻辑开始之前执行 */ @Before("pt1()") public void beforeMethod(JoinPoint joinPoint) { Object[] args = joinPoint.getArgs(); for (int i = 0; i < args.length; i++) { Object arg = args[i]; System.out.println(arg); } System.out.println("业务逻辑开始执行之前执行......."); } /** * 业务逻辑结束时执行(无论异常与否) */ @After("pt1()") public void afterMethod() { System.out.println("业务逻辑结束时执行,无论异常与否都执行......."); } /** * 异常时时执行 */ @AfterThrowing("pt1()") public void exceptionMethod() { System.out.println("异常时执行......."); } /** * 业务逻辑正常时执行 */ @AfterReturning(value = "pt1()",returning = "retVal") public void successMethod(Object retVal) { System.out.println("业务逻辑正常时执行......."); } /** * 环绕通知 * */ /*@Around("pt1()")*/ public Object arroundMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("环绕通知中的beforemethod...."); Object result = null; try{ // 控制原有业务逻辑是否执行 // result = proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs()); }catch(Exception e) { System.out.println("环绕通知中的exceptionmethod...."); }finally { System.out.println("环绕通知中的after method...."); } return result; }
c、纯注解模式
不需要在xml中开启aop支持,只需要在启动类中增加注解@EnableAspectJAutoProxy。
对于springAOP,如果target实现了接口,默认采用JDK动态代理, 否则采用cglib。
当然,对于不是final修饰的类都可以使用cglib,所以及时实现了接口,也可强制指定使用cglib。 两种配置方式如下:
- xml中配置:<aop:config proxy-target-class="true"> 或者 <aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
- 注解配置:
@EnableAspectJAutoProxy(proxyTargetClass = true
)
SpringAOP源码解析
如果是注解开启aop:
从开启aop功能的注解@EnableAspectJAutoProxy点进去,发现它引入了AspectJAutoProxyRegistrar类,
点进去看到调用了方法
跟进此方法最终看到注册了 AnnotationAwareAspectJAutoProxyCreator类
到这里先暂停,假如是xml配置<aop:aspectj-autoproxy />开启aop支持,其实最终也能跟踪到此类,来看一下
如果spring配置中使用了自定义标签,那么一定会注册对应的解析器。 在AopNamespaceHandler的初始化方法中,我们可以看到,使用的是解析器是
AspectJAutoProxyBeanDefinitionParser
继续跟代码:
继续跟,发现和上面一样,也是调用AopConfigUtils的注册方法,那么最终,肯定也是注册了AnnotationAwareAspectJAutoProxyCreator类
所以,不管从注解入手,还是xml入手,最终我们发现都是注册了 AspectJAwareAdvisorAutoProxyCreator。此类叫: 自动代理创建器,AOP的实现,基本就是靠这个类去完成。
注册后,回到上一层,先看看准备工作:处理是否强制使用cglib代理
proxy-target-class属性也就是我们说的,是否强制使用cglib代理! 如果是,继续跟进,我们发现又调用了AopConfigUtils的方法。 上面已经调用它的另一个方法注册了AnnotationAwareAspectJAutoProxyCreator类,所以这里取出来的BeanDefinition就是该类,
将强制使用cglib的属性设置进去。
说了这么多,正式来看看AnnotationAwareAspectJAutoProxyCreator如何工作的?
可以看到,其实现了BeanPostProcessor接口,那么会在初始化后调用 postProcessAfterInitialization方法,拦截每一个bean为其创建代理。 具体逻辑在曾爷爷类中
AbstractAutoProxyCreator 就重写了该方法,实现了具体逻辑。 先对拦截的bean,创建一个key,格式: beanClassName_beanName。 如果该bean适合被代理,则需要封装该bean生成代理返回。
继续跟踪: 已经处理过或者无须增强,又或者该bean是基础设施类,直接返回。 真正的创建代理在createProxy方法。
那么,在进入创建代理的createProxy方法前,需要先判断是否有切面增强,有才需要创建代理啊。 而获取所有增强的方法就是上面两行:
this.getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, (TargetSource)null);
进入子类方法:AbstractAdvisorAutoProxyCreator#getAdvicesAndAdvisorsForBean,其中
findCandidateAdvisors 获取所有的增强
findAdvisorsThatCanApply 从所有增强中找到适用于当前bean的增强
先看看获取所有增强,进入子类AnnotationAwareAspectJAutoProxyCreator#findCandidateAdvisors:
该方法主要有两个逻辑:
super.findCandidateAdvisors(); 调用父类获取配置文件中的所有增强,其实就是提取配置文件中指明为切面的类。
aspectJAdvisorsBuilder.buildAspectJAdvisors() 获取注解增强,其实就是遍历所有的beanName,获取bean后判断是否有AspectJ注解,如果有,进一步提取并放入增强的缓存。
其中最复杂的获取增强器,委托给getAdvisors方法实现
进一步跟踪getAdvisors方法:
循环调用,继续跟踪getAdvisor,,看看如何获取切点信息,并生成增强的
如上,
findAspectJAnnotationOnMethod 中先获取切点信息(这里的切点信息,如@Before("test()") ),
找到Method的增强类型后,就该生成对应的增强器了(如@Before就生成一个Before类型的增强器),回到getAdvisor继续跟一下代码看看:
如上: 增强器使用InstantiationModelAwarePointcutAdvisorImpl包装,进入构造函数:
如上,根据不同的增强类型,生成不同的增强器,比如我们看一下后置增强器 AspectJAfterAdvice
它实现了MethodInterceptor,所以它自身就是一个拦截器,在finally中就是增强方法,会在正常逻辑执行完成后执行。
那再看一下前置增强器 AspectJMethodBeforeAdvice, ,看看为什么增强逻辑会在正常逻辑之前
如图,前置增强没有直接实现MethodInterceptor,所以它本身不是一个拦截器。 而是借助另一个拦截器:MethodBeforeAdviceInterceptor。
当前置增强器创建好后会注入到该拦截器,然后调用。 如图,先调用增强器的方法,再执行业务逻辑。
当所有的getAdvisor循环调用完成后,一个切面类的所有方法增强就生成了; 当所有的beanName循环完成后,所有切面类的所有方法增强就生成了。 但是,并不是所有的增强都适合当前拦截的目标bean,所以还要筛选:
有了增强器,剩下的就是对目标bean生成代理了,继续回到上一层的createProxy方法:
如上,先给factory指定合适的代理方式,并把所有增强转换后设置给factory,最后在getProxy方法中生成代理。 继续跟踪:
createAopProxy(): 创建代理-这一步只是先确定使用哪种方式创建
如上:如果没有强制使用cglib,目标类也实现了接口,则使用JDK代理; 如果目标对象没有实现接口,则必须使用cglib
getProxy(classLoader): 获取代理-在确定后这一步真正开始创建
为目标创建代理后,该代理中也注入了之前获取的增强信息。 当目标方法调用时,会调用代理invoke方法,我们来看看invoke方法中,是如何实现增强的调用? 比如: 为什么前置增强器在目标方法之前,后置增强器在目标方法之后?
在invoke中的关键代码如上,先创建拦截器链,封装相应的增强,并用ReflectiveMethodInvocation封装后逐步调用。 (如果没有增强,在if中直接调用目标方法)
跟踪proceed():
如上,维护了一个拦截器计数器的维护,以便链可以有序进行。 但是这里并没有维护拦截器的顺序,而是依次调用。 至于增强的顺序是在目标方法的哪个位置执行,都是由各个增强器自行实现。
在上面创建增强器的时候,我们已经看过了。
到这里,springAOP的主要流程就算结束了, cglib创建代理暂时不看了。
总结起来就是: 一个实现了BeanPostProcessor的类,为所有的bean查找所有的增强,并生成代理, 在代理的invoke方法中调用所有的增强,而执行位置由增强器自己实现。