注意:这个笔记很少提到 Spring 源码,基本上都是基于源码对功能的模拟实现。
AOP 实现
ajc 编译器
- 使用编译器修改 class 源码实现增强,不依赖于 Spring
- 编译器增强能突破代理仅能通过方法重写增强的限制:可以对构造方法、静态方法等实现增强
注意
- 需要添加 aspectj-maven-plugin 插件依赖
- 要用 maven 的 compile 来编译再运行,因为idea 不会调用 ajc 编译器
agent 类加载
- 在运行时通过类加载的 agent 修改 class 源码实现增强,不依赖于 Spring
- 运行时需要在 VM options 里加入
-javaagent:本地maven仓库地址/org/aspectj/aspectjweaver/1.9.7/aspectjweaver-1.9.7.jar
proxy
jdk 动态代理使用
示例代码如下:
public class JdkProxyDemo {
interface Foo {
void foo();
}
static class Target implements Foo {
public void foo() {
System.out.println("target foo");
}
}
// jkd 代理:只能针对接口代理
// cglib 代理
public static void main(String[] param) {
//目标对象
Target target = new Target();
//类加载器:加载运行时代理类生成的字节码
ClassLoader loader = JdkProxyDemo.class.getClassLoader();
Foo proxy = (Foo) Proxy.newProxyInstance(loader, new Class[]{Foo.class}, (p, method, args) -> {
System.out.println("before....");
Object result = method.invoke(target, args);
System.out.println("after....");
return result;
});
proxy.foo();
}
}
总结:jdk 动态代理要求目标类必须实现接口,生成的代理类实现相同接口,因此代理与目标之间是平级兄弟关系
cglib 动态代理使用
示例代码如下:
public class CglibProxyDemo {
static class Target {
public void foo() {
System.out.println("target foo");
}
}
public static void main(String[] param) {
Target target = new Target();
Target proxy = (Target) Enhancer.create(Target.class, new MethodInterceptor() {
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("before...");
Object result = method.invoke(target, args); //方法反射调用目标
Object result1 = methodProxy.invoke(target, args);// 内部没有用反射,需要目标对象 (Spring)
Object result2 = methodProxy.invokeSuper(proxy, args);// 内部没有用反射,需要代理对象
System.out.println("after...");
return result;
}
});
proxy.foo();
}
}
总结:
- 代理是子类型,目标是父类型
- 目标类不能为 final,因为 final 类不能被继承
- 目标类方法不能为 final,因为 final 方法不能被重写
methodProxy
可以避免反射调用
jdk 动态代理原理
模拟 jdk 动态代理
流程如下:
- 定义代理类实现和目标类相同的接口
- 定义
InvocationHandler
接口 - 把
InvocationHandler
接口作为参数,创建代理类对象 - 执行代理类对象的方法,拿到 Method 对象后回调
InvocationHandler
接口 - 在
InvocationHandler
匿名内部类中通过 Method 对象调用目标对象方法,并做一些增强
模拟代码如下:
模拟 InvocationHandler
接口:
interface InvocationHandler {
Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
模拟创建代理对象过程:
public class A13 {
interface Foo {
void foo();
int bar();
}
//目标类
static class Target implements Foo {
public void foo() {
System.out.println("target foo");
}
@Override
public int bar() {
System.out.println("target bar");
return 100;
}
}
public static void main(String[] args) {
//模拟创建代理对象过程
Foo proxy = new $Proxy0(new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
// 1.功能增强
System.out.println("before...");
// 2.调用目标
return method.invoke(new Target(), args);
}
});
proxy.foo();
proxy.bar();
}
}
模拟代理类:
// 模拟代理类
public class $Proxy0 implements Foo {
private InvocationHandler h;
public $Proxy0(InvocationHandler h) {
this.h = h;
}
static Method foo;
static Method bar;
static {
try {
foo = Foo.class.getMethod("foo");
bar = Foo.class.getMethod("bar");
} catch (NoSuchMethodException e) {
throw new NoSuchMethodError(e.getMessage());
}
}
@Override
public void foo() {
try {
h.invoke(this, foo, new Object[0]);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
@Override
public int bar() {
try {
Object result = h.invoke(this, bar, new Object[0]);
return (int) result;
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
}
🍭扩展
ASM 是一个 Java 字节码操控框架,可以修改现有的字节码,或是在运行期间动态生成字节码(.class),没有源码(.java)
先做了解,后面学到 JVM 后再来深入。
cglib 动态代理原理
模拟 cglib 动态代理
和 jdk 动态代理原理类似,流程如下:
- 在代理类定义接口属性
methodInterceptor
- 在外部类设置
methodInterceptor
匿名内部类,即回调方法(增强) - 执行代理对象的方法,拿到 Method 对象后回调
methodInterceptor
接口 - 在
methodInterceptor
匿名内部类中通过 Method 对象 或 MethodProxy 对象 调用目标对象方法,并做一些增强
模拟代码在👇(MethodProxy 应用)
区别:
- 回调的接口换了,
InvocationHandler
改成了MethodInterceptor
- 调用目标接口时有所改进:
method.invoke
是反射调用,必须调用到足够次数才会进行优化methodProxy.invoke
是不反射调用,它会正常(间接)调用目标对象的方法(Spring 采用)methodProxy.invokeSuper
也是不反射调用,它会正常(间接)调用代理对象的方法,可以省略目标对象
MethodProxy
创建 MethodProxy
MethodProxy.create(Class c1, Class c2, String desc, String name1, String name2) //创建一个 MethodProxy 对象
- c1:目标类型
- c2:代理类型
- desc:参数和返回值,例如
()V
代表无参无返回值 - name1:带增强功能的方法名
- name2:带原始功能的方法名
MethodProxy 应用
模拟代码如下:
模拟创建代理对象过程:
//目标类
static class Target{
public void save() {
System.out.println("save()");
}
public void save(int i) {
System.out.println("save(int)");
}
public void save(long j) {
System.out.println("save(long)");
}
}
public static void main(String[] args) {
Proxy proxy = new Proxy();
Target target = new Target();
proxy.setMethodInterceptor(new MethodInterceptor() {
@Override
public Object intercept(Object p, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("before....");
// return method.invoke(target, args); //反射调用
// return methodProxy.invoke(target, args); //内部无反射,结合目标对象使用
return methodProxy.invokeSuper(p, args); //内部无反射,结合代理使用
}
});
proxy.save();
proxy.save(1);
proxy.save(2L);
}
模拟代理类:
public class Proxy extends Target{
private MethodInterceptor methodInterceptor;
public void setMethodInterceptor(MethodInterceptor methodInterceptor) {
this.methodInterceptor = methodInterceptor;
}
static Method save0;
static Method save1;
static Method save2;
static MethodProxy save0Proxy;
static MethodProxy save1Proxy;
static MethodProxy save2Proxy;
static {
try {
save0 = Target.class.getMethod("save");
save1 = Target.class.getMethod("save", int.class);
save2 = Target.class.getMethod("save", long.class);
save0Proxy = MethodProxy.create(Target.class, Proxy.class, "()V", "save", "savaSuper");
save1Proxy = MethodProxy.create(Target.class, Proxy.class, "(I)V", "save", "savaSuper");
save2Proxy = MethodProxy.create(Target.class, Proxy.class, "(J)V", "save", "savaSuper");
} catch (NoSuchMethodException e) {
throw new NoSuchMethodError(e.getMessage());
}
}
// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>带原始功能的方法
public void savaSuper(){
super.save();
}
public void savaSuper(int i){
super.save(i);
}
public void savaSuper(long j){
super.save(j);
}
// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>带增强功能的方法
@Override
public void save() {
try {
methodInterceptor.intercept(this, save0, new Object[0], save0Proxy);
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
@Override
public void save(int i) {
try {
methodInterceptor.intercept(this, save1, new Object[]{i}, save1Proxy);
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
@Override
public void save(long j) {
try {
methodInterceptor.intercept(this, save2, new Object[]{j}, save2Proxy);
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
}
MethodProxy 原理
methodProxy.invoke(target, args); //结合目标对象使用
methodProxy.invokeSuper(proxy, args); //结合代理对象使用
通过两个方法避免反射调用
methodProxy
产生的两个代理类和之前的代理类的区别:
- 之前的代理类是给目标类做增强
- 产生的两个代理类是为了避免两个方法的反射调用
- 产生的两个代理类的父类都是
FastClass
所以以下把产生的两个代理类称之为
FastClass
,避免混淆
产生的 FastClass
,一个配合目标对象使用,另一个配合代理对象使用
模拟两个 FastClass
(没有去继承 FastClass
抽象类是因为里面方法太多了,这里模拟两个核心方法):
调用 TargetFastClass
对象的流程如下:
MethodProxy.create()
时,底层会创建TargetFastClass
对象,并确定好每个增强方法对应的编号- 在
TargetFastClass
类中通过getIndex
得到每一个方法的编号 - 调用
methodProxy.invoke(target, args)
时,底层的是调用TargetFastClass
的invoke
方法
模拟 TargetFastClass
类:
public class TargetFastClass {
static Signature s0 = new Signature("save", "()V");
static Signature s1 = new Signature("save", "(I)V");
static Signature s2 = new Signature("save", "(J)V");
//获取目标方法的编号
/*
Target
save() 0
save(int) 1
save(long) 2
signature:包含方法名字,返回参数
*/
public int getIndex(Signature signature) {
if (s0.equals(signature)) {
return 0;
} else if (s1.equals(signature)) {
return 1;
} else if (s2.equals(signature)) {
return 2;
} else {
return 0;
}
}
// 根据方法编号,正常(不走反射)调用目标对象方法
public Object invoke(int index, Object target, Object[] args) {
if (index == 0) {
((Target) target).save();
return null;
} else if (index == 1) {
((Target) target).save((int) args[0]);
return null;
} else if (index == 2) {
((Target) target).save((long) args[0]);
return null;
} else {
throw new RuntimeException("无此方法");
}
}
}
模拟调用 TargetFastClass
对象流程:
@Test
public void test(){
TargetFastClass fastClass = new TargetFastClass();
int index = fastClass.getIndex(new Signature("save", "(I)V"));
System.out.println(index); //输出1
fastClass.invoke(index, new Target(), new Object[]{100});
}
调用 ProxyFastClass
对象的流程和 TargetFastClass
大体一致,代码如下:
模拟 ProxyFastClass
类:
public class ProxyFastClass {
static Signature s0 = new Signature("savaSuper", "()V");
static Signature s1 = new Signature("savaSuper", "(I)V");
static Signature s2 = new Signature("savaSuper", "(J)V");
//获取代理方法的编号
/*
Proxy
savaSuper() 0
savaSuper(int) 1
savaSuper(long) 2
signature:包含方法名字,返回参数
*/
public int getIndex(Signature signature) {
if (s0.equals(signature)) {
return 0;
} else if (s1.equals(signature)) {
return 1;
} else if (s2.equals(signature)) {
return 2;
} else {
return 0;
}
}
// 根据方法编号,正常(不走反射)调用目标对象方法
public Object invoke(int index, Object proxy, Object[] args) {
if (index == 0) {
((Proxy) proxy).savaSuper();
return null;
} else if (index == 1) {
((Proxy) proxy).savaSuper((int) args[0]);
return null;
} else if (index == 2) {
((Proxy) proxy).savaSuper((long) args[0]);
return null;
} else {
throw new RuntimeException("无此方法");
}
}
}
模拟调用 ProxyFastClass
对象流程:
@Test
public void test(){
ProxyFastClass fastClass = new ProxyFastClass();
int index = fastClass.getIndex(new Signature("saveSuper", "()V"));
System.out.println(index); //输出0
fastClass.invoke(index, new Proxy(), new Object[0]);
}
总结:
methodProxy.invoke(target, args); methodProxy.invokeSuper(proxy, args);
两个方法内部都是调用原始方法,如果是调用增强方法就死循环了,jdk 也是调用的原始方法,但是是通过反射。
所以
FastClass
的invoke
方法都是调用原始方法,只不过是用目标类调用和用代理类调用的区别
Spring 选择代理
模拟 Spring 底层 AOP
流程如下:
- 定义切点
- 定义通知
- 定义切面(封装切点和通知)
- 创建代理
代码如下:
//接口
interface I1 {
void foo();
void bar();
}
//目标类
static class Target1 implements I1 {
public void foo() {
System.out.println("target1 foo");
}
public void bar() {
System.out.println("target1 bar");
}
}
public static void main(String[] args) {
// 1. 定义切点
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(* foo())");
// 2. 定义通知
MethodInterceptor advice = invocation -> {
System.out.println("before...");
Object result = invocation.proceed();//调用目标
System.out.println("after...");
return result;
};
// 3. 定义切面(封装切点和通知)
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
// 4. 创建代理
Target1 target = new Target1();
// ProxyFactory 是 Spring 提供的代理工厂
ProxyFactory factory = new ProxyFactory();
factory.setTarget(target);
factory.addAdvisor(advisor);
factory.setInterfaces(target.getClass().getInterfaces());
I1 proxy = (I1) factory.getProxy();
System.out.println(proxy.getClass()); //jdk实现代理
proxy.foo();
proxy.bar();
}
注意:
定义通知的
MethodInterceptor
接口是 Spring 的,不同于之前 cglib 中的MethodInterceptor
选择代理规则
ProxyFactory
代理工厂用来创建代理,他的父类 ProxyConfig
中的 proxyTargetClass
属性决定选用哪个代理:
proxyTargetClass
= false,目标实现了接口,,用 jdk 实现proxyTargetClass
= false, 目标没有实现接口,用 cglib 实现proxyTargetClass
= true,总是使用 cglib 实现
默认 false
Spring 切点匹配
AspectJ 匹配方式
模拟 AspectJ 进行切点匹配:
AspectJExpressionPointcut pt1 = new AspectJExpressionPointcut();
pt1.setExpression("execution(* bar())"); //只能匹配方法
boolean foo = pt1.matches(T1.class.getMethod("foo"), T1.class); //检查是否匹配
setExpression
底层通过 matches
匹配:
boolean matches(Method method, Class<?> targetClass)
- method:目标方法
- targetClass:目标类
@Transactional
@Transactional
底层并不是通过 AspectJExpressionPointcut
的 setExpression
完成匹配,因为 AspectJExpressionPointcut
匹配的局限性,只能匹配方法,而@Transactional
有3种用法:
- 加在方法上
- 加在类上,代表类中所有方法都要进行事务增强
- 加在接口上,代表所有实现方法都要进行事务增强
模拟匹配 @Transactional
:
StaticMethodMatcherPointcut pt3 = new StaticMethodMatcherPointcut() {
@Override
//重写匹配规则
public boolean matches(Method method, Class<?> targetClass) {
//检查方法上是否加了 Transactional 注解
MergedAnnotations annotations = MergedAnnotations.from(method);
if (annotations.isPresent(Transactional.class)){
return true;
}
//查看类和实现接口是是否加了 Transactional 注解
annotations = MergedAnnotations
.from(targetClass, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY);
if (annotations.isPresent(Transactional.class)) {
return true;
}
return false;
}
};
boolean foo = pt3.matches(T1.class.getMethod("foo"), T1.class);//检查是否匹配
高低级切面
高级切面类 Aspect
示例代码:
@Aspect //高级切面类
static class Aspect1{
@Before("execution(* foo())")
public void before(){
System.out.println("aspect1 before...");
}
@After("execution(* foo())")
public void after(){
System.out.println("aspect1 after...");
}
}
//目标类
static class Target1 {
public void foo() {
System.out.println("target1 foo");
}
}
⛽知识加油站
切入点表达式可以直接写在通知方法上,也可以单独写在一个方法上,用 @Pointcut
注解,避免代码重复
低级切面类 Advisor
示例代码:
@Configuration
static class Config{
@Bean //低级切面
public Advisor advisor3(MethodInterceptor advice3){
//创建切点
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(* foo())");
//把切点和通知封装为切面
return new DefaultPointcutAdvisor(pointcut, advice3);
}
//创建通知
@Bean
public MethodInterceptor advice3(){
return invocation -> {
System.out.println("aspect3 before...");
Object result = invocation.proceed(); //调用目标方法
System.out.println("aspect3 after...");
return result;
};
}
}
切面执行顺序
默认情况下低级切面先执行。
自定义执行顺序:
- 在高级切面类上面添加
@Order
- 在低级切面类中调用
DefaultPointcutAdvisor
对象的setOrder
方法
@Order
局限性:
- 无法作用于低级切面类
- 无法作用于高级切面类的方法
Bean 后处理器
bean后处理器 AnnotationAwareAspectJAutoProxyCreator
的两个作用:
-
找到容器中的所有切面方法,并把高级切面转换为低级切面
-
创建代理对象
分别对应 AnnotationAwareAspectJAutoProxyCreator
中两个方法:
List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName)
Object wrapIfNecessary(Object bean, String beanName, Object cacheKey)
findEligibleAdvisors
List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName)
- beanClass:指定一个类,查找该类中的方法是否匹配容器中的切面类的切点,简单来说就是查找切面方法
- beanName:beanClass 类在容器中的名字,还没注册到容器的话可以随便起名
作用:把高级切面转换为低级切面,返回所有低级切面
高级切面的每个通知方法都会转换为一个低级切面
wrapIfNecessary
Object wrapIfNecessary(Object bean, String beanName, Object cacheKey)
只用关心第一个参数:
bean:目标对象
判断是否有必要为目标创建代理,内部会调用 findEligibleAdvisors
,只要返回集合不空, 则表示需要创建代理
代理创建时机
循环依赖:A 依赖 B,B 依赖 A(你中有我,我中有你)
创建原始类 bean 实例 -> (*) 依赖注入 -> 初始化 (*)
代理对象创建的时机在 “*”,二选一:
- 没有循环依赖时:初始化之后
- 有循环依赖时:创建原始类 bean 实例之后,并暂存于二级缓存
高级转低级切面
以 @Before
为例,@Before
会被转换为原始的 AspectJMethodBeforeAdvice
,流程如下:
- 获取
@Before
注解的方法 - 获取切点表达式
- 创建切点对象,设置其表达式
- 创建通知对象(每种通知都有自己的通知类)
- 创建切面对象,封装切点和通知
模拟代码如下:
//创建切面实例工厂
AspectInstanceFactory factory = new SingletonAspectInstanceFactory(new Aspect());
for (Method method: Aspect.class.getDeclaredMethods()){
// 1.获取 @Before 注解的方法
if (method.isAnnotationPresent(Before.class)){
// 2.获取切点表达式
String espression = method.getAnnotation(Before.class).value();
// 3.创建切点对象,设置其表达式
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression(espression);
// 4.创建通知
AspectJMethodBeforeAdvice advice = new AspectJMethodBeforeAdvice(method, pointcut, factory);
// 5.创建切面,封装切点和通知
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
list.add(advisor);
}
}
静态通知调用
统一转换为环绕通知
有多个通知的话,由外至内,再由内至外调用,所以需要把通知统一转换为环绕通知
环绕通知需要实现
MethodInterceptor
接口
5种通知对象:
- 前置通知:
AspectJMethodBeforeAdvice
- 后置通知:
AspectJAfterAdvice
- 返回后通知:
AspectJAfterReturningAdvice
- 抛出异常后通知:
AspectJAfterThrowingAdvice
- 环绕通知:
AspectJAroundAdvice
其中,后置通知、抛出异常后通知、环绕通知已经实现了 MethodInterceptor
接口,无需转换
通过 ProxyFactory
的 getInterceptorsAndDynamicInterceptionAdvice
进行转换:
List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, @Nullable Class<?> targetClass)
- method:目标方法对象
- targetClass:目标类型
- List<Object>:返回转换后的环绕通知集合
适配器模式
把一套接口转换为另一套接口,以便适合某种场景的使用,中间做转换的对象叫做适配器。
例如:
- 适配器
MethodBeforeAdviceAdapter
:将AspectJMethodBeforeAdvice
适配为MethodBeforeAdviceInterceptor
- 适配器
AfterReturningAdviceAdapter
:将AspectJAfterReturningAdvice
适配为AfterReturningAdviceInterceptor
调用链对象
MethodInvocation
:调用链对象,调用每一个环绕通知(即实现 MethodInterceptor
接口)和目标
MethodInvocation
的 proceed()
方法 :调用每一个环绕通知和目标
在某些通知内部需要用到调用链对象,所以在最外层需要将
MethodInvocation
放入ThreadLocal
(当前线程)
模拟调用链执行
执行流程如下:
由外至内,再由内至外
模拟 proceed()
方法完成递归调用:

proceed()
不是直接递归调用proceed()
,而是通过methodInterceptor.invoke(this)
间接调用proceed()
动态通知调用
静态通知调用:
@Before("execution(* foo(..))") // 静态通知调用,不带参数绑定,执行时不需要切点对象
public void before1() {
System.out.println("before1");
}
动态通知调用:
@Before("execution(* foo(..)) && args(x)") // 动态通知调用,需要参数绑定,执行时还需要切点对象
public void before2(int x) {
System.out.printf("before2(%d)", x);
}
args(x)
中的 x
表示目标方法的第一个参数
List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, @Nullable Class<?> targetClass)
返回的不仅有环绕通知集合,还有 InterceptorAndDynamicMethodMatcher
,即动态通知,内部有两个属性:
final MethodInterceptor interceptor;//环绕通知
final MethodMatcher methodMatcher; //切点对象
- 有参数绑定的通知调用时需要切点对象,对参数进行匹配及绑定
- 复杂程度高, 性能比没有参数绑定的通知调用低