AOP核心知识点
切面、切点、连接点、通知、织入
光看概念就一脸懵逼,根本记不住。
原理和思想
asm框架
通过某个技术在程序运行期间,根据需求动态的创建字节码文件,生成的字节码可以在内存中,也可以溢写到磁盘上,但最终要由一个class文件。由此asm框架进行动态生成。
匹配方法(正则表达式)
重新生成或者在原来的class文件基础之上,直接添加日志处理的相关逻辑即可。给这个核心功能起个名字就是AOP。
AOP是一种编程思想,当对AOP进行实践的时候,需要制定一些规范要求,根据规范要求,来实现AOP功能。
有了规范之后,那么需要安装某种规则进行一个匹配,比如通过某些规则找到需要实现AOP功能的方法。
通过类似于正则表达式的一个东西,来进行匹配方法。可以通过方法名字、方法参数、方法的返回值、类的包名(完全限定名)等等。
通过expression表达式,找到这个切入点
表达式语法
Spring aop官网语法如下
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern)
throws-pattern?)
翻译后表达的意思是
execution(<修饰符模式>?<返回类型模式><方法名模式>(<参数模式>)<异常模式>?)
其中除了返回类型模式、方法名模式和参数模式外,其它项都是可选的。
execution(* com.shuweicloud.bi.api.web..*Controller.list(..))
标识符 | 含义 |
execution() | 表达式的主体 |
第一个*符号 | 表示任意的返回类型 |
com.shuweicloud.bi.api.web | AOP所切的服务的包名,即,需要进行横切的业务类 |
包后面的.. | 表示当前包及子包 |
第二个*符号 | 表示类名,*即所有类 |
.list(..) | 表示匹配方法名list(),括号表示参数,两个点表示任何参数类型. |
通知
当通过上述的表达式匹配到方法(切入点)之后,那么需要方法的哪些位置进行增加呢?
AOP提供了5种通知方法
通知方法:
* 前置通知(@Before):logStart():在目标方法(div)运行之前运行
* 后置通知(@After):logEnd():在目标方法(div)运行结束之后运行
* 返回通知(@AfterReturning):logReturn():在目标方法(div)正常返回之后运行
* 异常通知(@AfterThrowing):logException:在目标方法(div)出现异常以后运行
* 环绕通知(@Around):动态代理,手动推进目标方法运行(joinPoint.procced())
注解版AOP操作步骤
- 导入Spring AOP需要的jar包。(spring-aspects)
- 定义一个业务逻辑类(MathCalculator);在业务逻辑运行的时候将日志进行打印(方法之前、方法运行结束、异常等)。
- 定义一个日志切面类(LogAspects):切面类里面的方法需要动态的感知MathCalculator.div()方法运行到哪里?
- 给切面类的目标方法标注何时运行。
- 将切面类和业务逻辑类(目标方法所在类)都加入到容器中;
- 必须告诉spring哪个类是切面类,给切面类加上一个注解 @Aspect
- 给配置类中加入注解@EnableAspectJAutoProxy开启基于注解的AOP模式
maven配置
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.1.7.RELEASE</version>
</dependency>
业务类:MathCalculator类
public class MathCalculator {
public int div(int i,int j) {
return i/j;
}
}
切面类
@Aspect
public class LogAspects {
//抽取公共的切入点表达式
//1、本类引用
//2、其他的切面引用
@Pointcut("execution(public int cn.com.git.annotation.aop.MathCalculator.*(..))")
public void pointCut() {
}
@Around("pointCut()")
public Object logArround(ProceedingJoinPoint pjp){
Signature signature = pjp.getSignature();
Object[] args = pjp.getArgs();
Object result= null;
System.out.println("执行around方法,获取到的参数是:"+Arrays.asList(args));
try {
result = pjp.proceed(args);
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("环绕通常异常:"+signature.getName());
}finally {
System.out.println("环绕返回通知,返回结果"+result);
}
return result;
}
//@Before在目标方法之前切入;切入点表达式(指定在哪个方法切入)
@Before("pointCut()")
public void logStart(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
System.out.println(joinPoint.getSignature().getName()+"before运行。。。。参数列表是:{"+Arrays.asList(args)+"}");
}
@After("pointCut()")
public void logEnd(JoinPoint joinPoint) {
System.out.println(joinPoint.getSignature().getName()+"after除法结束");
}
@AfterReturning(value="pointCut()",returning="result")
public void logReturn(JoinPoint joinPoint,Object result) {
System.out.println(joinPoint.getSignature().getName()+"afterReturning除法正常返回。。。运行结果:{"+result+"}");
}
//JoinPoint 一定要出现在参数表的第一位
@AfterThrowing(value = "pointCut()",throwing="exception")
public void logException(JoinPoint joinPoint,Exception exception) {
System.out.println(joinPoint.getSignature().getName()+"除法异常。。。异常信息:{"+exception+"}");
}
Config类
@EnableAspectJAutoProxy
@Configuration
public class MainConfigOfAOP {
//业务逻辑类加入到容器中
@Bean
public MathCalculator getMathCalculator() {
return new MathCalculator();
}
//切面类加入到容器中
@Bean
public LogAspects getLogAspects() {
return new LogAspects();
}
}
源码debug
通过AOP注解版,新建一个test类进行测试
public class IOCTest_AOP {
@Test
public void testAOP() {
AnnotationConfigApplicationContext applicationContext=new AnnotationConfigApplicationContext(MainConfigOfAOP.class);
MathCalculator mathCalculator = applicationContext.getBean(MathCalculator.class);
mathCalculator.div(2, 2);
applicationContext.close();
}
}
1、获取Spring context上下文Beanfactory对象
Spring上下文benfactory里加载了16个类,保护自定义类和容器自身所需要的类。
2、自定义类生成的代理类(DynamicAdvisedInterceptor)
通过名字可以看出,自定义类是生成了动态代理类。
当执行业务类的div()方法时候,其实执行的是代理类的方法。上图可以看到调用的CALLBACK_0对应的类是DynamicAdvisedInterceptor类的interceptor()方法。
3、责任链(chain)
在interceptor()方法中,获取到一个责任链
链中存放了5个通知类型。
获取到链之后,执行proceed()方法。
4、proceed()方法。
proceed()方法是在CglibMethodInvocation类的父类ReflectiveMethodInvocation中,方法如下
public Object proceed() throws Throwable {
// We start with an index of -1 and increment early.
//从索引为-1的拦截器开始调用,并按序递增,如果拦截器链中的拦截器迭代调用完毕,开始调用target函数,这个函数是通过反射机制完成的。
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
return invokeJoinpoint();
}
//获取下一个要执行的拦截器,沿着定义好的interceptorOrInterceptionAdvice链进行处理
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.
//这里对拦截器进行动态匹配的判断,这里是对pointcut触发进行匹配的地方,如果和定义的pointcut匹配,那么这个advice将会得到执行
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);
}
}
通过currentInterceptorIndex参数的++操作,获取chain保存的链式通知。执行invoke()方法。按F7进入invoke方法,可以看到具体的invoke都做了哪些操作。
第1个执行的类是ExposeInvocationInterceptor类的invoke方法
通过截图,可以看到invoke调用的是mi.proceed()方法。而此处的mi正是ReflectiveMethodInvocation。所以又回到了CglibMethodInvocation类的父类ReflectiveMethodInvocation中继续调用proceed()方法。
前面我们知道proceed()方法,会进行链的++操作,来获取链上的下一个对象。
第2个类:AspectJAfterThrowingAdvice
同样的,这个通知类也是执行mi.proceed();
第3个类、AfterReturningAdviceInterceptor
第4个类、AspectJAfterAdvice
第5个类、MethodBeforeAdviceInterceptor
可以看出,执行Before的时候,是先AOP注解标注的before的方法的。执行完之后,又回到了mi.proceed()。继续进行链式执行。
执行@Before方法
当currentInterceptorIndex++ 到5的时候,
问题、
1、递归执行不好理解,需要加深这个概念的理解
2、@Around环绕执行的逻辑
3、执行的这些类和定义的那些chain之间的关系
ExposeInvocationInterceptor-->AspectJAfterThrowingAdvice-->AfterReturningAdviceInterceptor-->AspectJAroundAdvice-->MethodBeforeAdviceInterceptor-->LogAspects-->(@Around通知执行proceed
()前半部分)-->LogAspects(@Before)-->@Around方法的后半部分-->@after方法-->@AfterReturning方法。
链的顺序是一样的吗?
不一样。chian上的顺序是通过拓扑排序,顺序可能会不一样,那么放到链上的目的就是能方便获取找到下一个需要执行的任务。