SpringBoot2 | 第二十五篇(三):SpringAOP的实现原理

​ 从上一篇的笔记中,我们可以了解到 SpringAOP 的使用详情。这篇笔记将对 SpringAOP 实现原理的理解做一些记录。

1、织入与织入的时期

1.1、何为织入?

​ 织入是 把切面应用到目标对象并创建新的代理对象的过程。在目标对象的生命周期里有 3 个时期 可以进行织入,这三个时期分别为 编译期,类加载期、运行期

1.2、织入的时期

  1. 编译期——切面在目标类编译时期被织入,这种方式需要特殊的编译器。AspectJ 的织入编译器就是以这种方式织入切面。
  2. 类加载期——切面在目标类加载到 JVM 时被织入 ,这种方式需要特殊的类加载器,他可以在目标类被引入应用之前增强该目标类的字节码。AspectJ5 的 加载时织入(load-time weaving,LTW) 就支持这种织入方式
  3. 运行期——切面在应用运行期间的某个时刻被织入。一般情况下,在织入切面的时候,AOP 容器会为目标对象动态的创建代理对象。Spring AOP 就是以这种方式织入切面。(====)

2、原理概述:运行时织入

  • 运行时织入是怎么实现的? 答案是代理对象
  • 从静态代理到动态代理
  • 基于接口代理与基于继承代理

3、代理模式

3.1、静态代理

​ 在程序运行前,由程序员创建或特定工具自动生成源代码并对其编译生成 .class 文件。代理类和委托类的关系在 运行前 就确定了。

缺点:

​ 不是实现 AOP 的 don’t repeat yourself。如果需要被代理的类有很多个方法,那么静态代理需要重复好多次,那不得累死。所以 动态代理 出现啦 ~ ~ ~

3.2、动态代理 (SpringAOP 底层实现机制)

在实现阶段不用关心代理类,而在 运行阶段 才指定哪一个对象

1541989807821

  • jdk 动态代理(优先): 针对实现了接口的类产生的代理(基于接口)

    原理:实现目标对象实现的 所有 接口

  • cglib 代理:针对没有实现接口的类产生的代理,应用的是底层的 字节码增强技术,生成当前类的子类对象(基于继承)

    时序图:

    1542160012381

动态代理怎么体现动态?

jdk 代理为例子

这里有一个参数,把它设置为 true 后,运行后会把 jdk 代理生成的 class 文件保存下来

1
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

运行后可以看到项目的跟目录多了个文件夹

1542094527397

打开这个文件,可以看到,该代理类对被代理类的每个方法都进行了增强操作。

1542094656437

那这怎么体现出动态代理呢?

你可以尝试下,只要你往被代理类的接口添加多少个方法(当然你肯定要实现了这些方法)。并不需要我们手写其他代码就能动态增强被代理类的方法了。

加完方法后运行,你会发现 被代理类的 class 文件已经帮我们增强了~ ~ ~

3.3、jdk 代理和 cglib 代理对比

  • jdk 代理 能针对实现接口的类的 实现方法 进行动态代理
  • cglib 代理基于继承来实现代理,无法对 static、final 修饰的 进行代理
  • cglib 代理基于继承来实现代理,无法对 private、static 修饰的方法 进行代理

3.4、SpringAOP对两种实现的选择

  • 如果目标对象实现了接口,则默认采用 jdk 动态代理
  • 如果目标对象没有实现接口,则采用 cglib 动态代理
  • 如果目标对象实现了接口,且强制 cglib 动态代理,则使用 cglib 动态代理

那么如何强制使用 cglib 代理呢?

在配置类(启动类也属于配置类)中加上 @EnableAspectJAutoProxy(proxyTargetClass = true) 即可

1542159877810

环境/版本一览:

  • 开发工具:Intellij IDEA 2018.2.2
  • springboot: 2.1.0.RELEASE
  • jdk:1.8.0_171
  • maven:3.3.9
  • spring-boot-starter-aop:2.1.0.RELEASE

静态代理

ISubject.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.fatal.static_proxy;

/**
* @author: Fatal
* @date: 2018/11/12 0012 11:10
*/
public interface ISubject {

/** 增强 */
void request();

/** 不增强 */
void hello();
}
RealSubject.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.fatal.static_proxy;

/**
* 被代理类
* @author: Fatal
* @date: 2018/11/12 0012 11:11
*/
public class RealSubject implements ISubject {

@Override
public void request() {
System.out.println("被代理类RealSubject被访问了(需要增强)");
}

@Override
public void hello() {
System.out.println("被代理类RealSubject被访问了(不需要增强)");
}

}
Proxy.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.fatal.static_proxy;

/**
* 代理类
* @author: Fatal
* @date: 2018/11/12 0012 11:12
*/
public class Proxy implements ISubject {

/** 被代理对象 */
private RealSubject realSubject;

public Proxy(RealSubject realSubject) {
this.realSubject = realSubject;
}

@Override
public void request() {
System.out.println("====== before ======");
try {
realSubject.request();
} catch (Exception e) {
// 注意:代理类不会改变被代理类的方法。如果代理类有异常抛出,那么被代理类也要抛出。
throw e;
} finally {
System.out.println("====== after ======");
}
}

@Override
public void hello() {
realSubject.hello();
}

}
Client.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.fatal.static_proxy;

/**
* 测试客户端
* @author: Fatal
* @date: 2018/11/12 0012 11:17
*/
public class Client {

public static void main(String[] args) {
ISubject subject = new Proxy(new RealSubject());
subject.request();
System.out.println();
subject.hello();
}

}
显示

启动 Client.main() ,控制台如下

1542015151782

jdk 代理

ISubject.java
1
2
3
4
5
6
7
8
9
10
11
package com.fatal.dynamic_proxy;

/**
* @author: Fatal
* @date: 2018/11/12 0012 14:37
*/
public interface ISubject {

void action();

}
RealSubject.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.fatal.dynamic_proxy;

/**
* 被代理对象
* @author: Fatal
* @date: 2018/11/12 0012 14:38
*/
public class RealSubject implements ISubject {

@Override
public void action() {
System.out.println("我是被代理类,记得要执行我哦!!么么哒 ~ ~");
}

}
MyInvocationHandler.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
package com.fatal.jdk_dynamic_proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
* 自定义调用处理程序
* @author: Fatal
* @date: 2018/11/12 0012 14:39
*/
public class MyInvocationHandler implements InvocationHandler {

/** 声明被代理对象 */
private Object obj;

/**
* 初始化被代理对象
* 动态创建代理类并返回
* @return 代理类
*/
public Object binding(Object obj) {
this.obj = obj;
/**
* ClassLoader loader: 被代理类的类加载器
* Class<?>[] interfaces: 被代理类实现的所有方法
* InvocationHandler h: 调用程序处理器
*/
return Proxy.newProxyInstance(this.obj.getClass().getClassLoader()
,this.obj.getClass().getInterfaces(),this);
}

/**
* 书写增强逻辑
* @param proxy
* @param method 方法对象
* @param args 实际方法参数
* @return 方法返回值
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 对参数`args`进行处理
System.out.println("======== 已对参数进行处理 ========");

Object invoke = null;
try {
System.out.println("before in jdk proxy");
/**
* Object invoke(Object obj, Object... args)
* Object obj:被代理对象
* Object... args:实际方法参数
*/
invoke = method.invoke(obj, args);
} catch (InvocationTargetException e) {
// 注意:代理类不会改变被代理类的方法。如果代理类有异常抛出,那么被代理类也要抛出。
throw e;
} finally {
System.out.println("after in jdk proxy");
}
// 对返回结果`invoke`进行处理
System.out.println("======== 已对返回结果进行处理 ========");
return invoke;
}
}
Client.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.fatal.dynamic_proxy;

/**
* @author: Fatal
* @date: 2018/11/12 0012 15:20
*/
public class Client {

public static void main(String[] args) {
// 添加这个参数,会保存 jdk 代理生成的 class 文件
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

/** 创建被代理对象 */
RealSubject realSubject = new RealSubject();
/** 创建 InvocationHandler 对象 */
MyInvocationHandler handler = new MyInvocationHandler();

/** 将被代理对象绑定到 InvocationHandler,并获得一个代理对象 */
Object binding = handler.binding(realSubject);

ISubject proxy = (ISubject) binding;

/** 调用被增强的方法 */
proxy.action();

}

}
显示

启动 Client.main() ,控制台如下

1542102622514

cglib 代理

MyMethodInterceptor.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
package com.fatal.cglib_dynamic_proxy;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
* 自定义方法拦截器
* 功能与jdk动态代理的InvocationHandler差不多。
* 都是通过反射增强目标对象的方法
* @author: Fatal
* @date: 2018/11/13 0013 17:41
*/
public class MyMethodInterceptor implements MethodInterceptor {

/** 声明被代理对象 */
private Object obj;

/**
* 初始化被代理对象
* 动态创建代理类并返回
* @return 代理类
*/
public Object binding(Object obj) {
this.obj = obj;
/**
* Class type: 被代理类的字节码对象
* Callback callback: 自定义的MethodInterceptor实现
*/
return Enhancer.create(this.obj.getClass(), this);
}

/**
* @desc 所有生成的代理方法都调用此方法而不是原始方法。原始方法可以通过使用方法对象的普通反射调用,也可以通过使用methodproxy(更快)调用。
* @param obj 增强的对象
* @param method 方法对象
* @param args 参数
* @param methodProxy 用于调用父类(未拦截的)方法,可根据需要多次调用
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy)
throws Throwable {

// 对参数`args`进行处理
System.out.println("======== 已对参数进行处理 ========");

Object invoke = null;
try {
System.out.println("before in cglib proxy");
/**
* invokeSuper(Object obj, Object[] args)
* Object obj:被代理对象
* Object... args:实际方法参数
*/
// 使用 MethodProxy
invoke = methodProxy.invokeSuper(obj, args);
// 使用方法对象的普通反射调用
// invoke = method.invoke(this.obj, args);
} catch (InvocationTargetException e) {
// 注意:代理类不会改变被代理类的方法。如果代理类有异常抛出,那么被代理类也要抛出。
throw e;
} finally {
System.out.println("after in cglib proxy");
}
// 对返回结果`invoke`进行处理
System.out.println("======== 已对返回结果进行处理 ========");

return invoke;
}

}
Client.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.fatal.cglib_dynamic_proxy;

import com.fatal.jdk_dynamic_proxy.ISubject;
import com.fatal.jdk_dynamic_proxy.RealSubject;

/**
* @author: Fatal
* @date: 2018/11/12 0012 15:20
*/
public class Client {

public static void main(String[] args) {

/** 创建被代理对象 */
RealSubject realSubject = new RealSubject();
/** 创建 MethodInterceptor 对象 */
MyMethodInterceptor interceptor = new MyMethodInterceptor();

/** 将被代理对象绑定到 InvocationHandler,并获得一个代理对象 */
Object binding = interceptor.binding(realSubject);

ISubject proxy = (ISubject) binding;

/** 调用被增强的方法 */
proxy.action();

}

}
显示

1542103636147

参考资料

探秘Spring AOP

总结

SpringBoot的知识已经有前辈在我们之前探索了。比较喜欢的博主有:唐亚峰 | Battcn方志朋的专栏程序猿DD纯洁的微笑。对这门技术感兴趣的可以去他们的博客逛逛。谢谢他们的分享~~

以上文章是我用来学习的Demo,都是基于 SpringBoot2.x 版本。

源码地址: https://github.com/ynfatal/springboot2-learning/tree/master/chapter25_3

学习 apollo_0001 前辈的经验