Spring AOP 基本概念
一、基本概念
关于 AOP 的概念定义如果只是上百度查找一些文字描述,看完之后也是一脸懵逼,根据本无法理解。下面我们结合代码来解释这些羞涩难懂的概念。
1.1 切面(Aspect)
Aspect 是由 PointCut 和 Advice 组成。由于 PointCut 也可以定义多个,准确的说是多个 PointCut 和 多个Advice 组成。
如果从逻辑角度看切面,Spring中的配置事务管理就是一个很好的例子,当然像这样的例子还包括Spring中缓存的功能。
1.2 切点(Pointcut)
切点就是定义拦截(增强)哪些对象中的哪些方法的,简单点说就是拦截的条件定义。
1.3 连接点(JoinPoint)
程序执行过程中明确的点,如方法的调用或特定的异常被抛出。连接点由两个信息确定:
-
方法(表示程序执行点,即在哪个目标方法)
-
相对点(表示方位,即目标方法的什么位置,比如调用前,后等)
简单来说,连接点就是被拦截到的程序执行点,因为Spring只支持方法类型的连接点,所以在Spring中连接点就是被拦截到的方法。
1.4 通知(Advice)
通知是指拦截到连接点之后要执行的代码,包括了 “around”、“before” 和 “after” 等不同类型的通知。
Spring AOP框架以拦截器来实现通知模型,并维护一个以连接点为中心的拦截器链。
1.5 目标对象(Target)
目标对象指将要被增强的对象,即包含主业务逻辑的类对象。或者说是被一个或者多个切面所通知的对象。
例如:example.spring.aop.Calculator 就是目标对象,div 就是被拦截的方法。
public class Calculator {
public int div(int i, int j){
return i/j;
}
}
1.6 总结
- 通过切点(Pointcut)寻找需要增强的目标对象方法
- 目标方法被调用时,需要按通知(Advice)的顺序依次执行 logStart() -> div() -> logEnd() -> logReturn() 。 如果目标方法抛出异常,则执行 logException() 方法。
- 所有的通知(Advice)通过连接点(JoinPoint)获取目标方法的信息。
二、相关概念
2.1 织入(Weaving)
织入是将切面和业务逻辑对象连接起来, 并创建通知代理的过程。可以理解为就是 1.6 总结 的描述过程。织入可以在编译时,类加载时和运行时完成。
在编译时进行织入就是静态代理,而在运行时进行织入则是动态代理。
在Spring中 当bean实现接口时,使用 JDK 动态代理。当 bean 没实现接口,使用 CGLib 动态代理。
配置 CGLib 的两种方式:
- 通过配置强制走cglib,<aop:aspectj-autoproxy proxy-target-class=“true”/>
- 注解的方式,例如 SpringBoot 项目的启动类上添加 @EnableAspectJAutoProxy(proxyTargetClass=true)
Calculator 类被 CGLib 织入后的代码
public class Calculator$$EnhancerBySpringCGLIB$$b212acf extends Calculator implements SpringProxy, Advised, Factory {
..... 此处省略部分代码
static void CGLIB$STATICHOOK3() {
CGLIB$THREAD_CALLBACKS = new ThreadLocal();
CGLIB$emptyArgs = new Object[0];
Class arg = Class.forName("example.spring.aop.Calculator$$EnhancerBySpringCGLIB$$b212acf");
Class arg0;
CGLIB$div$0$Method = ReflectUtils.findMethods(new String[]{"div", "(II)I"},
(arg0 = Class.forName("example.spring.aop.Calculator")).getDeclaredMethods())[0];
CGLIB$div$0$Proxy = MethodProxy.create(arg0, arg, "(II)I", "div", "CGLIB$div$0");
Method[] arg9999 = ReflectUtils.findMethods(
new String[]{"equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I",
"clone", "()Ljava/lang/Object;"},
(arg0 = Class.forName("java.lang.Object")).getDeclaredMethods());
CGLIB$equals$1$Method = arg9999[0];
CGLIB$equals$1$Proxy = MethodProxy.create(arg0, arg, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$1");
CGLIB$toString$2$Method = arg9999[1];
CGLIB$toString$2$Proxy = MethodProxy.create(arg0, arg, "()Ljava/lang/String;", "toString", "CGLIB$toString$2");
CGLIB$hashCode$3$Method = arg9999[2];
CGLIB$hashCode$3$Proxy = MethodProxy.create(arg0, arg, "()I", "hashCode", "CGLIB$hashCode$3");
CGLIB$clone$4$Method = arg9999[3];
CGLIB$clone$4$Proxy = MethodProxy.create(arg0, arg, "()Ljava/lang/Object;", "clone", "CGLIB$clone$4");
}
final int CGLIB$div$0(int arg0, int arg1) {
return super.div(arg0, arg1);
}
public final int div(int arg0, int arg1) {
try {
MethodInterceptor arg9999 = this.CGLIB$CALLBACK_0;
if (this.CGLIB$CALLBACK_0 == null) {
CGLIB$BIND_CALLBACKS(this);
arg9999 = this.CGLIB$CALLBACK_0;
}
if (arg9999 != null) {
Object arg4 = arg9999.intercept(this, CGLIB$div$0$Method,
new Object[]{new Integer(arg0), new Integer(arg1)}, CGLIB$div$0$Proxy);
return arg4 == null ? 0 : ((Number) arg4).intValue();
} else {
return super.div(arg0, arg1);
}
} catch (Error | RuntimeException arg2) {
throw arg2;
} catch (Throwable arg3) {
throw new UndeclaredThrowableException(arg3);
}
}
..... 以下代码省略
}
2.2 增强器(Advisor)
InstantiationModelAwarePointcutAdvisorImpl 是 Advisor的一个实现类,从这个类的源码中可以看出。
Advisor 由切点和一个通知组成,由于它只有一个通知,所以也可以理解为切面的另外一种实现。