封面画师:T5-茨舞(微博)     封面ID:97451569

参考视频:黑马程序员Spring视频教程,全面深度讲解spring5底层原理

源码仓库:mofan212/advanced-spring (github.com)

9. AspectJ 编译器增强

创建一个 SpringBoot 项目,除了常见的依赖外,记得导入 AOP 相关的依赖:

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

一个 Service 类:

1
2
3
4
5
6
7
8
9
10
11
/**
* @author mofan
* @date 2023/1/9 21:17
*/
@Service
public class MyService {
private static final Logger log = LoggerFactory.getLogger(MyService.class);
public void foo() {
log.info("foo()");
}
}

一个切面类,注意这个切面类没有被 Spring 管理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* @author mofan
* @date 2023/1/9 21:18
*
* 注意此切面并未被 Spring 管理
*/
@Aspect
public class MyAspect {
private static final Logger log = LoggerFactory.getLogger(MyAspect.class);

@Before("execution(* indi.mofan.service.MyService.foo())")
public void before() {
log.info("before()");
}
}

一个用于测试的主启动类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@SpringBootApplication
public class A10Application {
private static final Logger log = LoggerFactory.getLogger(A10Application.class);

public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(A10Application.class, args);
MyService service = context.getBean(MyService.class);

log.info("service class: {}", service.getClass());
service.foo();

context.close();
}
}

运行主启动类后,控制台会显示:

indi.mofan.A10Application                : service class: class indi.mofan.service.MyService
indi.mofan.aop.MyAspect                  : before()
indi.mofan.service.MyService             : foo()

如果完全按照上述步骤进行,会发现 输出结果和给出的结果不一样。

在揭晓答案前,查看 service.getClass() 打印出的信息,它打印出的是原始类的 Class 信息,而非代理类的 Class 信息。

如果要问到 Spring AOP 的实现原理是什么,一下就能想到的是使用了代理,但这里并没有使用代理,依旧实现了增强。

这是因为在 pom.xml 中还引入了一个插件:

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
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.11</version>
<configuration>
<complianceLevel>1.8</complianceLevel>
<source>8</source>
<target>8</target>
<showWeaveInfo>true</showWeaveInfo>
<verbose>true</verbose>
<Xlint>ignore</Xlint>
<encoding>UTF-8</encoding>
</configuration>
<executions>
<execution>
<goals>
<!-- use this goal to weave all your main classes -->
<goal>compile</goal>
<!-- use this goal to weave all your test classes -->
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

之后不在使用 IDEA 自带的编译器进行编译,而是使用 Maven 编译,即:

使用Maven进行代码编译

编译之后查看生成的 target 文件夹下的 MyService.class 文件:

1
2
3
4
5
6
7
8
9
10
11
12
@Service
public class MyService {
private static final Logger log = LoggerFactory.getLogger(MyService.class);

public MyService() {
}

public void foo() {
MyAspect.aspectOf().before();
log.info("foo()");
}
}

可以看到在 foo() 方法中增加了一行代码:MyAspect.aspectOf().before();,也就是这行代码对 foo() 方法实现了增强。

这种方式属于编译时增强,和 Lombok 类似。

既然如此,那岂不是说使用这种方式时,没有 Spring 容器也能实现方法的增强?

确实如此。

1
2
3
4
5
6
7
8
9
public class A10Application {
private static final Logger log = LoggerFactory.getLogger(A10Application.class);

public static void main(String[] args) {
MyService service = new MyService();
log.info("service class: {}", service.getClass());
service.foo();
}
}
indi.mofan.A10Application - service class: class indi.mofan.service.MyService
indi.mofan.aop.MyAspect - before()
indi.mofan.service.MyService - foo()

除此之外,使用这种方式,就算 foo() 方法是静态方法,也能够成功增强。

1
2
3
4
5
6
public class MyService {
private static final Logger log = LoggerFactory.getLogger(MyService.class);
public static void foo() {
log.info("foo()");
}
}
1
2
3
4
5
public class A10Application {
public static void main(String[] args) {
MyService.foo();
}
}
indi.mofan.aop.MyAspect - before()
indi.mofan.service.MyService - foo()

遇到的一些问题

  • 使用 Lombok 的一些注解后无法编译通过。

答:本节只是做个小测试,实际开发时一般不会这么使用,因此移除 Lombok 的注解即可。比如本节采用的日志打印方式不再使用 @Slf4j,而是使用了诸如以下的方式:

1
private static final Logger log = LoggerFactory.getLogger(MyService.class);
  • 完全按照一样的步骤进行,但运行后的方法依旧没有增强。

答:这是由于 IDEA 在执行代码前又编译了一遍代码,覆盖了使用 Maven 编译生成字节码文件,导致增强失败。对 IDEA 进行设置,勾选 自动构建项目:

设置IDEA自动编译项目

  • 尽量使用 JDK8,因为 aspectj-maven-plugin 可能暂不支持高版本的 JDK。

10. Agent 类加载

重新创建一个 SpringBoot 项目,同样需要导入 AOP 相关的依赖。

一个 Service 类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* @author mofan
* @date 2023/1/9 22:22
*/
@Slf4j
@Service
public class MyService {
final public void foo() {
log.info("foo()");
bar();
}

public void bar(){
log.info("bar()");
}
}

一个切面类,注意这个切面类没有被 Spring 管理:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* @author mofan
* @date 2023/1/9 22:26
*/
@Slf4j
@Aspect
public class MyAspect {

@Before("execution(* indi.mofan.service.MyService.*())")
public void before() {
log.info("before()");
}
}

一个用于测试的主启动类:

1
2
3
4
5
6
7
8
9
10
11
12
@Slf4j
@SpringBootApplication
public class A11Application {

public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(A11Application.class, args);
MyService service = context.getBean(MyService.class);
log.info("service class: {}", service.getClass());
service.foo();
}

}

运行主启动类后,控制台会显示:

indi.mofan.A11Application                : service class: class indi.mofan.service.MyService
indi.mofan.aop.MyAspect                  : before()
indi.mofan.service.MyService             : foo()
indi.mofan.aop.MyAspect                  : before()
indi.mofan.service.MyService             : bar()

如果完全按照上述步骤进行,会发现输出结果和给出的结果不一样。

那是怎么达到增强的效果呢?

首先得在 resources 目录下新建 META-INF 文件夹,并在 META-INF 目录下新建 aop.xml 文件,其内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
<aspectj>
<aspects>
<!-- 切面类全限定类名 -->
<aspect name="indi.mofan.aop.MyAspect"/>
<weaver options="-verbose -showWeaveInfo">
<!-- 被增强方法所在类的全限定类名 -->
<include within="indi.mofan.service.MyService"/>
<!-- 切面类全限定类名 -->
<include within="indi.mofan.aop.MyAspect"/>
</weaver>
</aspects>
</aspectj>

在运行 main() 方法前添加 VM options:

1
-javaagent:D:\environment\Maven\3.6.3-repository\.m2\repository\org\aspectj\aspectjweaver\1.9.7\aspectjweaver-1.9.7.jar

其中的 D:\environment\Maven\3.6.3-repository\.m2 指本地 Maven 仓库地址,还需要确保本地仓库中存在 1.9.7 版本的 aspectjweaver,否则修改至对应版本。

这是控制台输出的信息就和前文的内容一样了。

从输出的内容可以看到 service.getClass() 打印出的信息也是原始类的 Class 信息,而非代理类的 Class 信息。因此不依赖 Spring 容器,直接 new 一个 MyService 实例并调用其 foo() 方法也能达到增强的目的。

如果查看 MyService 对应的 class 文件,会发现其内容并没有被修改,可以断定不是编译时增强,这里是在类加载时增强。

利用 Arthas 反编译类文件

可以借助阿里巴巴的 Arthas 来反编译加载的类文件,下载地址:下载 | arthas

在使用 Arthas 前,需要确保对应 Java 进程的存在,因此在上述代码中调用 service.foo(); 方法后并没有关闭 Spring 容器。

解压下载的压缩包,进入 arthas-boot.jar 文件的同级目录,使用终端工具执行 java -jar .\arthas-boot.jar

运行arthas-boot的jar包

运行之后会列举出存在的 Java 进程,找到需要连接的进程,之后输入目标进程对应的序号。当界面上成功显示 Arthas 的 Banner 时,证明连接成功:

选择A11Application的Java进程

输入 jad indi.mofan.service.MyService 表示需要反编译 MyService

反编译MyService文件

可以看到 foo()bar() 方法的第一行都被增加了一行代码,也就是这行代码对这两个方法实现了增强。

不仅如此,如果使用代理实现增强,被调用的 bar() 方法不会被成功增强,因为调用时默认使用了 this 关键词,表示调用的是原类中的方法,而不是代理类中的方法(经典面试题:@Transactional 注解失效的场景)。

11. 动态代理

11.1 JDK 动态代理

JDK 动态代理 只能 针对接口进行代理。

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
/**
* @author mofan
* @date 2023/1/10 21:35
*/
public class JdkProxyDemo {
interface Foo {
void foo();
}

static final class Target implements Foo {
@Override
public void foo() {
System.out.println("target foo");
}
}

public static void main(String[] args) {
// 目标对象
Target target = new Target();

// 用来加载在运行期间动态生成的字节码
ClassLoader classLoader = JdkProxyDemo.class.getClassLoader();
Foo proxy = (Foo) Proxy.newProxyInstance(classLoader, new Class[]{Foo.class}, (p, method, params) -> {
System.out.println("before...");
// 目标.方法(参数) --> 方法.invoke(目标, 参数)
Object result = method.invoke(target, params);
System.out.println("after...");
// 也返回目标方法执行的结果
return result;
});

proxy.foo();
}
}

运行 main() 方法后控制台打印出:

before...
target foo
after...

总结

  1. 代理对象和目标对象是兄弟关系,都实现了相同的接口,因此不能将代理对象强转成目标对象类型;
  2. 代理类与目标类之间没有继承关系,因此目标类可以被 final 修饰。

11.2 CGLib 动态代理

CGLib 动态代理与 JDK 动态代理不一样,无需目标类实现某个特定的接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class CglibProxyDemo {
static class Target {
public void foo() {
System.out.println("target foo");
}
}

public static void main(String[] args) {
Target target = new Target();

Target proxy = (Target) Enhancer.create(Target.class, (MethodInterceptor) (obj, method, params, methodProxy) -> {
System.out.println("before...");
// 用方法反射调用目标
Object result = method.invoke(target, params);
System.out.println("after...");
return result;
});

proxy.foo();
}
}

运行 main() 方法后控制台打印出:

before...
target foo
after...

调用目标方法的方式有三种,上文使用的是:

1
Object result = method.invoke(target, params);

使用这种方式,将利用反射对目标方法进行调用。

还可以使用 methodProxy 不利用反射对目标方法进行调用:

1
2
3
4
// 内部没使用反射,需要目标(spring 的选择)
Object result = methodProxy.invoke(target, args);
// 内部没使用反射,需要代理
Object result = methodProxy.invokeSuper(obj, args);

总结

  • 与 JDK 动态代理相比,CGLib 动态代理无需实现接口
  • 代理对象和目标对象是父子关系,也就是说代理类继承了目标类
  • 由于代理类继承了目标类,因此目标类不能被 final 修饰,否则将出现以下异常信息:
java.lang.IllegalArgumentException: Cannot subclass final class indi.mofan.a12.CglibProxyDemo$Target
  • 代理类继承目标类后,还会重写目标类中要求被增强的方法,因此被增强的方法不能被 final 修饰,否则将无法被增强,但不会抛出异常

12. JDK 动态代理原理

12.1 JDK 动态代理的模拟

先来模拟一下 JDK 动态代理的实现:

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
interface Foo {
void foo();
}

static class Target implements Foo {
@Override
public void foo() {
System.out.println("target foo");
}
}

public class $Proxy0 implements A13.Foo{
@Override
public void foo() {
// 1. 功能增强
System.out.println("before...");
// 2. 调用目标
new A13.Target().foo();
}
}

public static void main(String[] args) {
$Proxy0 proxy = new $Proxy0();
proxy.foo();
}

运行 main() 方法,控制台打印出:

before...
target foo

代码的实现很简单,但仔细想一下,如果是 JDK 中的实现:

  • “功能增强”的代码实现会直接硬编码吗?直接打印?
  • “调用目标”的代码就这样直接写上去?存不存在满足某些条件才调用目标的场景呢?

也就是说,“功能增强”和“调用目标”这两部分的代码都是不确定的。

针对这种“不确定”的实现,可以提供一个抽象类,等到用户具体使用时才实现抽象类,重写抽象方法。

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
interface Foo {
void foo();
}

static class Target implements Foo {
@Override
public void foo() {
System.out.println("target foo");
}
}

interface InvocationHandler {
void invoke();
}

public class $Proxy0 implements A13.Foo{

private final A13.InvocationHandler h;

public $Proxy0(A13.InvocationHandler h) {
this.h = h;
}

@Override
public void foo() {
h.invoke();
}
}

public static void main(String[] args) {
$Proxy0 proxy = new $Proxy0(new InvocationHandler() {
@Override
public void invoke() {
// 1. 功能增强
System.out.println("before...");
// 2. 调用目标
new A13.Target().foo();
}
});
proxy.foo();
}

运行 main() 方法,控制台依旧成功打印出:

before...
target foo

多个抽象方法的接口

这样的实现依旧有问题,如果接口中提供了两个抽象方法呢?比如:

1
2
3
4
5
interface Foo {
void foo();

void bar();
}

此时无论是目标类,还是代理类都要重写这个方法:

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
static class Target implements Foo {
@Override
public void foo() {
System.out.println("target foo");
}

@Override
public void bar() {
System.out.println("target bar");
}
}

public class $Proxy0 implements A13.Foo{

private final A13.InvocationHandler h;

public $Proxy0(A13.InvocationHandler h) {
this.h = h;
}

@Override
public void foo() {
h.invoke();
}

@Override
public void bar() {
h.invoke();
}
}

public static void main(String[] args) {
$Proxy0 proxy = new $Proxy0(new InvocationHandler() {
@Override
public void invoke() {
// 1. 功能增强
System.out.println("before...");
// 2. 调用目标
new A13.Target().foo();
}
});
proxy.foo();
// 调用另一个方法
proxy.bar();
}

此时再执行 main() 方法,控制台上打印出:

before...
target foo
before...
target foo

打印结果有点问题。当调用代理对象的 bar() 方法时,输出了 target foo,而不是 bar() 方法应该打印的 target bar

原因就出在实现 InvocationHandlerinvoke() 方法时,依旧只调用了目标类的 foo() 方法,而不是 bar() 方法。

也就是说,在调用代理对象中的某个方法时,增强的应该是目标对象中对应的方法,希望在调用目标方法时能够动态编码。

那么可以在 invoke() 方法中添加两个入参,分别表示需要调用的目标方法和目标方法的参数:

1
2
3
interface InvocationHandler {
void invoke(Method method, Object[] params) throws Throwable;
}

增加参数之后需要修改代理类,并将实现的抽象方法的 Method 对象与参数传递给 invoke() 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class $Proxy0 implements A13.Foo{

private final A13.InvocationHandler h;

public $Proxy0(A13.InvocationHandler h) {
this.h = h;
}

@Override
@SneakyThrows
public void foo() {
Method method = A13.Foo.class.getMethod("foo");
h.invoke(method, new Object[0]);
}

@Override
@SneakyThrows
public void bar() {
Method method = A13.Foo.class.getMethod("bar");
h.invoke(method, new Object[0]);
}
}

还需要修改下 main() 方法中 InvocationHandler 的实现,利用传递的 Method 对象和参数信息反射调用目标方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void main(String[] args) {
$Proxy0 proxy = new $Proxy0(new InvocationHandler() {
@Override
public void invoke(Method method, Object[] params) throws Throwable {
// 1. 功能增强
System.out.println("before...");
// 2. 调用目标
Object invoke = method.invoke(new Target(), params);
}
});
proxy.foo();
// 调用另一个方法
proxy.bar();
}

再执行 main() 方法,控制台上打印出:

before...
target foo
before...
target bar

有返回值的抽象方法

优化还在继续,如果抽象方法有返回值呢?比如:

1
2
3
4
5
interface Foo {
void foo();

int bar();
}

实现了这个接口的目标类和代理类重写的方法都需要有具体的返回值:

1
2
3
4
5
6
7
8
9
10
11
12
static class Target implements Foo {
@Override
public void foo() {
System.out.println("target foo");
}

@Override
public int bar() {
System.out.println("target bar");
return 100;
}
}

目标类很简单,直接返回,那代理类返回什么?

InvocationHandlerinvoke() 方法是对“功能增强”和“调用目标”的抽象,因此可以使 invoke() 方法也返回一个值,返回的值即为目标方法的返回值,这样就可以使得代理类中的方法有值可返。

1
2
3
interface InvocationHandler {
Object invoke(Method method, Object[] params) throws Throwable;
}
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
public class $Proxy0 implements A13.Foo {

private final A13.InvocationHandler h;

public $Proxy0(A13.InvocationHandler h) {
this.h = h;
}

@Override
public void foo() {
try {
Method foo = A13.Foo.class.getMethod("foo");
h.invoke(foo, new Object[0]);
} catch (RuntimeException | Error e) {
throw e;
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}

@Override
public int bar() {
try {
Method bar = A13.Foo.class.getMethod("bar");
return (int) h.invoke(bar, new Object[0]);
} catch (RuntimeException | Error e) {
throw e;
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
}

修改 main() 方法,打印 bar() 方法的返回值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void main(String[] args) {
$Proxy0 proxy = new $Proxy0(new InvocationHandler() {
@Override
public Object invoke(Method method, Object[] params) throws Throwable {
// 1. 功能增强
System.out.println("before...");
// 2. 调用目标
return method.invoke(new Target(), params);
}
});
proxy.foo();
// 调用另一个方法
System.out.println(proxy.bar());
}

运行结果如下:

before...
target foo
before...
target bar
100

在静态代码块里创建 Method 实例

每调用一次代理对象中的方法都会创建一个 Method 实例,这些实例是可以复用的,因此可以将这些实例的创建移动到静态代码块中:

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
public class $Proxy0 implements A13.Foo {

private final A13.InvocationHandler h;

public $Proxy0(A13.InvocationHandler h) {
this.h = h;
}

@Override
public void foo() {
try {
h.invoke(foo, new Object[0]);
} catch (RuntimeException | Error e) {
throw e;
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}

@Override
public int bar() {
try {
return (int) h.invoke(bar, new Object[0]);
} catch (RuntimeException | Error e) {
throw e;
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}

static Method foo;
static Method bar;

static {
try {
foo = A13.Foo.class.getMethod("foo");
bar = A13.Foo.class.getMethod("bar");
} catch (NoSuchMethodException e) {
throw new NoSuchMethodError(e.getMessage());
}
}
}

invoke() 方法增加代理对象作为参数

在 JDK 提供的 InvocationHandler 接口的 invoke() 方法还将代理对象作为方法的参数,以便用户根据实际情况使用。继续修改自定义的 InvocationHandler 接口:

1
2
3
interface InvocationHandler {
Object invoke(Object proxy, Method method, Object[] params) throws Throwable;
}

修改代理类中对 invoke() 方法的调用,第一个参数为当前类的实例,即 this

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
public class $Proxy0 implements A13.Foo {

// --snip--

@Override
public void foo() {
try {
h.invoke(this, foo, new Object[0]);
} catch (RuntimeException | Error e) {
throw e;
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}

@Override
public int bar() {
try {
return (int) h.invoke(this, bar, new Object[0]);
} catch (RuntimeException | Error e) {
throw e;
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}

// --snip--
}

main() 方法重写的 invoke() 方法也要增加 proxy 参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void main(String[] args) {
$Proxy0 proxy = new $Proxy0(new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
// 1. 功能增强
System.out.println("before...");
// 2. 调用目标
return method.invoke(new Target(), params);
}
});
proxy.foo();
// 调用另一个方法
System.out.println(proxy.bar());
}

运行 main() 方法,结果不发生变化。

向 JDK 靠齐

到此为止,自定义的 InvocationHandler 接口与 JDK 提供的 InvocationHandler 接口无异,注释自定义的 InvocationHandler,更换为 JDK 提供的 InvocationHandler 接口。

在 JDK 提供的 InvocationHandler 接口的注释中有一句:@see Proxy,在 Proxy 类的代码中有:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Proxy implements java.io.Serializable {

// --snip--

protected InvocationHandler h;

protected Proxy(InvocationHandler h) {
Objects.requireNonNull(h);
this.h = h;
}

// --snip--
}

Proxy 类中有一个 InvocationHandler 对象的成员变量。

因此还可以使代理类 $Proxy0 继承 Proxy 来进一步减少代码:

1
2
3
4
5
6
7
8
9
10
11
12
import java.lang.reflect.InvocationHandler;

public class $Proxy0 extends Proxy implements A13.Foo {

private static final long serialVersionUID = -6909541593982979501L;

public $Proxy0(InvocationHandler h) {
super(h);
}

// --snip--
}

12.2 代理类的源码

JDK 动态代理生成的代理类是以字节码的形式存在的,并不存在所谓的 .java 文件,但也不是说就没办法看到生成的代理类信息了。

以【11.1 JDK 动态代理】中的程序为例,查看 JDK 生成的代理类信息。

利用 Arthas 反编译代理类字节码文件

如果要使用 Arthas 的反编译功能需要满足两个条件:

  1. 知道被反编译文件的全限定类名
  2. 程序不能中断,需要存在 Java 进程

为了满足这个条件,可以在控制台打印出生成的代理类的全限定类名,然后利用阻塞 IO 使程序不中断:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@SneakyThrows
public static void main(String[] args) {
// 原始对象
Target target = new Target();

// 用来加载在运行期间动态生成的字节码
ClassLoader classLoader = JdkProxyDemo.class.getClassLoader();
Foo proxy = (Foo) Proxy.newProxyInstance(classLoader, new Class[]{Foo.class}, (p, method, params) -> {
System.out.println("before...");
// 目标.方法(参数) --> 方法.invoke(目标, 参数)
Object result = method.invoke(target, params);
System.out.println("after...");
// 也返回目标方法执行的结果
return result;
});

// 打印代理类的全限定类名
System.out.println(proxy.getClass());

proxy.foo();

// 只要不在控制台上输入并回车,程序就不会终端
System.in.read();
}

运行 main() 方法后,控制台打印出:

class indi.mofan.a12.$Proxy0
before...
target foo
after...

其中的 indi.mofan.a12.$Proxy0 就是生成的代理类的全限定类名,可以把它复制下来,之后按照【10. Agent 类加载】中使用 Arthas 的方式 indi.mofan.a12.$Proxy0 进行反编译即可。

将生成的代理类字节码文件保存在磁盘上

除了借助外部工具外,还可以直接将 JDK 生成的代理类字节码文件保存在磁盘上,其做法与【Lambda 与序列化】一文中将函数式接口动态生成的 Class 保存到磁盘上类似。

可以在 main() 方法开头加一句:

1
2
3
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
// 或者
System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");

不同版本的 JDK 添加的配置信息不同,至于具体是哪一个可以查看 JDK 中 ProxyGenerator 类中的 saveGeneratedFiles 成员变量,比如:

1
private static final boolean saveGeneratedFiles = (Boolean)AccessController.doPrivileged(new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles"));

显然,此处应该使用第一种方式来设置系统属性。最终的 main() 方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void main(String[] args) {
// 将动态代理生成的 class 保存到磁盘
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

// 原始对象
Target target = new Target();

// 用来加载在运行期间动态生成的字节码
ClassLoader classLoader = JdkProxyDemo.class.getClassLoader();
Foo proxy = (Foo) Proxy.newProxyInstance(classLoader, new Class[]{Foo.class}, (p, method, params) -> {
// --snip--
});

proxy.foo();
}

除此之外,还可以通过在运行前添加 VM options:

1
-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true

如果既在代码里设置了系统属性,又配置了 VM options,最终以代码中的配置为主。

运行 main() 方法,在当前项目目录下生成 indi.mofan.a12.$Proxy0.class 文件,查看其内容:

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
final class $Proxy0 extends Proxy implements Foo {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;

public $Proxy0(InvocationHandler var1) throws {
super(var1);
}

public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}

public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}

public final void foo() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}

public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}

static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("indi.mofan.a12.JdkProxyDemo$Foo").getMethod("foo");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}

其内容与自定义的 $Proxy0 几乎无异,只不过 JDK 生成的代理类信息还生成 equals()toString()hashCode() 三个方法对应的 Method 对象,并对它们也进行了相同的增强。

12.3 JDK 代理类字节码生成

JDK 在生成代理类时,没有经历源码阶段、编译阶段,而是直接到字节码阶段,使用了 ASM 来完成。

ASM 的学习成本较高,在此不做过多介绍,本节将采用一直“曲线求国”的方式,使用 IDEA 的 ASM Bytecode outline 插件将 Java 源码转换成使用 ASM 编写的代码。

ASM Bytecode outline 插件在高版本 Java 中可能无法很好地工作,建议在 Java8 环境下使用。

自行编写一个接口和代理类:

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
public interface Foo {
void foo();
}

public class $Proxy0 extends Proxy implements Foo {
private static final long serialVersionUID = 6059465134835974286L;

static Method foo;

static {
try {
foo = Foo.class.getMethod("foo");
} catch (NoSuchMethodException e) {
throw new NoSuchMethodError(e.getMessage());
}
}

public $Proxy0(InvocationHandler h) {
super(h);
}

@Override
public void foo() {
try {
this.h.invoke(this, foo, null);
} catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
}

将上述代码进行编译,编译成功后在 $Proxy0 文件中右击,选择 Show Bytecode outline 浏览当前类对应的字节码信息:

Show-Bytecode-outline

查看 ASMified,并拷贝其内容,复制到 $Proxy0Dump 中:

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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
import jdk.internal.org.objectweb.asm.*;

/**
* @author mofan
* @date 2023/1/16 22:04
*/
public class $Proxy0Dump implements Opcodes {

public static byte[] dump() throws Exception {

ClassWriter cw = new ClassWriter(0);
FieldVisitor fv;
MethodVisitor mv;
AnnotationVisitor av0;

// 生成类信息
cw.visit(52, ACC_PUBLIC + ACC_SUPER, "indi/mofan/$Proxy0", null, "java/lang/reflect/Proxy", new String[]{"indi/mofan/Foo"});

cw.visitSource("$Proxy0.java", null);

{
fv = cw.visitField(ACC_PRIVATE + ACC_FINAL + ACC_STATIC, "serialVersionUID", "J", null, new Long(6059465134835974286L));
fv.visitEnd();
}
{
fv = cw.visitField(ACC_STATIC, "foo", "Ljava/lang/reflect/Method;", null, null);
fv.visitEnd();
}
{
mv = cw.visitMethod(ACC_PROTECTED, "<init>", "(Ljava/lang/reflect/InvocationHandler;)V", null, null);
mv.visitParameter("h", 0);
mv.visitCode();
Label l0 = new Label();
mv.visitLabel(l0);
mv.visitLineNumber(26, l0);
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 1);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/reflect/Proxy", "<init>", "(Ljava/lang/reflect/InvocationHandler;)V", false);
Label l1 = new Label();
mv.visitLabel(l1);
mv.visitLineNumber(27, l1);
mv.visitInsn(RETURN);
Label l2 = new Label();
mv.visitLabel(l2);
mv.visitLocalVariable("this", "Lindi/mofan/$Proxy0;", null, l0, l2, 0);
mv.visitLocalVariable("h", "Ljava/lang/reflect/InvocationHandler;", null, l0, l2, 1);
mv.visitMaxs(2, 2);
mv.visitEnd();
}
{
mv = cw.visitMethod(ACC_PUBLIC, "foo", "()V", null, null);
mv.visitCode();
Label l0 = new Label();
Label l1 = new Label();
Label l2 = new Label();
mv.visitTryCatchBlock(l0, l1, l2, "java/lang/Throwable");
mv.visitLabel(l0);
mv.visitLineNumber(32, l0);
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, "indi/mofan/$Proxy0", "h", "Ljava/lang/reflect/InvocationHandler;");
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETSTATIC, "indi/mofan/$Proxy0", "foo", "Ljava/lang/reflect/Method;");
mv.visitInsn(ACONST_NULL);
mv.visitMethodInsn(INVOKEINTERFACE, "java/lang/reflect/InvocationHandler", "invoke", "(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;", true);
mv.visitInsn(POP);
mv.visitLabel(l1);
mv.visitLineNumber(35, l1);
Label l3 = new Label();
mv.visitJumpInsn(GOTO, l3);
mv.visitLabel(l2);
mv.visitLineNumber(33, l2);
mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[]{"java/lang/Throwable"});
mv.visitVarInsn(ASTORE, 1);
Label l4 = new Label();
mv.visitLabel(l4);
mv.visitLineNumber(34, l4);
mv.visitTypeInsn(NEW, "java/lang/reflect/UndeclaredThrowableException");
mv.visitInsn(DUP);
mv.visitVarInsn(ALOAD, 1);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/reflect/UndeclaredThrowableException", "<init>", "(Ljava/lang/Throwable;)V", false);
mv.visitInsn(ATHROW);
mv.visitLabel(l3);
mv.visitLineNumber(36, l3);
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
mv.visitInsn(RETURN);
Label l5 = new Label();
mv.visitLabel(l5);
mv.visitLocalVariable("throwable", "Ljava/lang/Throwable;", null, l4, l3, 1);
mv.visitLocalVariable("this", "Lindi/mofan/$Proxy0;", null, l0, l5, 0);
mv.visitMaxs(4, 2);
mv.visitEnd();
}
{
mv = cw.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
mv.visitCode();
Label l0 = new Label();
Label l1 = new Label();
Label l2 = new Label();
mv.visitTryCatchBlock(l0, l1, l2, "java/lang/NoSuchMethodException");
mv.visitLabel(l0);
mv.visitLineNumber(19, l0);
mv.visitLdcInsn(Type.getType("Lindi/mofan/Foo;"));
mv.visitLdcInsn("foo");
mv.visitInsn(ICONST_0);
mv.visitTypeInsn(ANEWARRAY, "java/lang/Class");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getMethod", "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;", false);
mv.visitFieldInsn(PUTSTATIC, "indi/mofan/$Proxy0", "foo", "Ljava/lang/reflect/Method;");
mv.visitLabel(l1);
mv.visitLineNumber(22, l1);
Label l3 = new Label();
mv.visitJumpInsn(GOTO, l3);
mv.visitLabel(l2);
mv.visitLineNumber(20, l2);
mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[]{"java/lang/NoSuchMethodException"});
mv.visitVarInsn(ASTORE, 0);
Label l4 = new Label();
mv.visitLabel(l4);
mv.visitLineNumber(21, l4);
mv.visitTypeInsn(NEW, "java/lang/NoSuchMethodError");
mv.visitInsn(DUP);
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/NoSuchMethodException", "getMessage", "()Ljava/lang/String;", false);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/NoSuchMethodError", "<init>", "(Ljava/lang/String;)V", false);
mv.visitInsn(ATHROW);
mv.visitLabel(l3);
mv.visitLineNumber(23, l3);
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
mv.visitInsn(RETURN);
mv.visitLocalVariable("e", "Ljava/lang/NoSuchMethodException;", null, l4, l3, 0);
mv.visitMaxs(3, 1);
mv.visitEnd();
}
cw.visitEnd();

return cw.toByteArray();
}
}

编写测试方法使用 $Proxy0Dump 生成 $Proxy0 的 class 文件:

1
2
3
4
5
6
7
8
9
public class TestProxy {
public static void main(String[] args) throws Exception {
byte[] dump = $Proxy0Dump.dump();

FileOutputStream os = new FileOutputStream("$Proxy0.class");
os.write(dump, 0, dump.length);
os.close();
}
}

运行 main() 方法后,在工作目录下生成 $Proxy0.class

工作目录查看方式:

查看当前工作目录

也就是说会在 D:\Code\IdeaCode\advanced-spring 目录下生成 $Proxy0.class 文件,IDEA 反编译后的内容与手动编写的 $Proxy0.java 文件的内容无异。

实际使用时并不需要使用 $Proxy0Dump 生成 $Proxy.class 文件,而是利用 ClassLoader 直接加载类信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static void main(String[] args) throws Exception {
byte[] dump = $Proxy0Dump.dump();

ClassLoader classLoader = new ClassLoader() {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
return super.defineClass(name, dump, 0, dump.length);
}
};

Class<?> proxyClass = classLoader.loadClass("indi.mofan.$Proxy0");
Constructor<?> constructor = proxyClass.getConstructor(InvocationHandler.class);
Foo fooProxy = (Foo) constructor.newInstance((InvocationHandler) (proxy, method, args1) -> {
System.out.println("before...");
System.out.println("模拟调用目标");
return null;
});

fooProxy.foo();
}

运行上述代码后,控制台打印出:

before...
模拟调用目标

12.4 JDK 反射优化

使用 JDK 的动态代理时,会使用反射调用方法:

1
Object result = method.invoke(target, params);

相比于正常调用方法,利用反射的性能要稍微低一些,JDK 有怎么反射进行优化吗?

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
/**
* @author mofan
* @date 2023/1/16 22:36
*/
public class TestMethodProxy {

public static void main(String[] args) throws Exception {
Method foo = TestMethodProxy.class.getMethod("foo", int.class);
for (int i = 1; i <= 17; i++) {
show(i, foo);
foo.invoke(null, i);
}
System.in.read();
}

// 方法反射调用时,底层使用了 MethodAccessor 的实现类
private static void show(int i, Method foo) throws Exception {
Method getMethodAccessor = Method.class.getDeclaredMethod("getMethodAccessor");
getMethodAccessor.setAccessible(true);
Object invoke = getMethodAccessor.invoke(foo);
if (invoke == null) {
System.out.println(i + ":" + null);
return;
}
// DelegatingMethodAccessorImpl 的全限定类名(不同版本的 JDK 存在差异)
Field delegate = Class.forName("sun.reflect.DelegatingMethodAccessorImpl").getDeclaredField("delegate");
delegate.setAccessible(true);
System.out.println(i + ": " + delegate.get(invoke));
}

public static void foo(int i) {
System.out.println(i + ": foo");
}
}

运行 main() 方法后,控制台打印出:

1: null
1: foo
2: sun.reflect.NativeMethodAccessorImpl@1be6f5c3
2: foo
3: sun.reflect.NativeMethodAccessorImpl@1be6f5c3
3: foo
4: sun.reflect.NativeMethodAccessorImpl@1be6f5c3
4: foo
5: sun.reflect.NativeMethodAccessorImpl@1be6f5c3
5: foo
6: sun.reflect.NativeMethodAccessorImpl@1be6f5c3
6: foo
7: sun.reflect.NativeMethodAccessorImpl@1be6f5c3
7: foo
8: sun.reflect.NativeMethodAccessorImpl@1be6f5c3
8: foo
9: sun.reflect.NativeMethodAccessorImpl@1be6f5c3
9: foo
10: sun.reflect.NativeMethodAccessorImpl@1be6f5c3
10: foo
11: sun.reflect.NativeMethodAccessorImpl@1be6f5c3
11: foo
12: sun.reflect.NativeMethodAccessorImpl@1be6f5c3
12: foo
13: sun.reflect.NativeMethodAccessorImpl@1be6f5c3
13: foo
14: sun.reflect.NativeMethodAccessorImpl@1be6f5c3
14: foo
15: sun.reflect.NativeMethodAccessorImpl@1be6f5c3
15: foo
16: sun.reflect.NativeMethodAccessorImpl@1be6f5c3
16: foo
17: sun.reflect.GeneratedMethodAccessor2@5b2133b1
17: foo

从上述信息可知,第一次调用时没有使用 MethodAccessor 对象,从第二次到第十六次,使用了 NativeMethodAccessorImpl 对象,而在第十七次使用了 GeneratedMethodAccessor2 对象。

NativeMethodAccessorImpl 基于 Java 本地 API 实现,性能较低,第十七次调用换成 GeneratedMethodAccessor2 后,性能得到一定的提升。

使用 Arthas 反编译查看 GeneratedMethodAccessor2 类中的信息,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class GeneratedMethodAccessor2 extends MethodAccessorImpl {
/*
* Loose catch block
*/
public Object invoke(Object object, Object[] objectArray) throws InvocationTargetException {
// --snip--
try {
// 正常调用方法
TestMethodProxy.foo((int)c);
return null;
}
catch (Throwable throwable) {
throw new InvocationTargetException(throwable);
}
catch (ClassCastException | NullPointerException runtimeException) {
throw new IllegalArgumentException(super.toString());
}
}
}

在反编译得到的代码中,不再是通过反射调用方法,而是直接正常调用方法,即:

1
TestMethodProxy.foo((int)c);

因此性能得到了提升,但这样的提升也是有一定代价的:为优化 一个 方法的反射调用,生成了一个 GeneratedMethodAccessor2 代理类。

13. CGLib 动态代理原理

13.1 CGLib 动态代理的模拟

同样先模拟下 CGLib 动态代理的模拟:

被代理的类,即目标类:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Target {
public void save() {
System.out.println("save()");
}

public void save(int i) {
System.out.println("save(int)");
}

public void save(long i) {
System.out.println("save(long)");
}
}

CGLib 动态代理生成的代理类:

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
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 {
try {
save0 = Target.class.getMethod("save");
save1 = Target.class.getMethod("save", int.class);
save2 = Target.class.getMethod("save", long.class);
} catch (NoSuchMethodException e) {
throw new NoSuchMethodError(e.getMessage());
}
}

@Override
public void save() {
try {
methodInterceptor.intercept(this, save0, new Object[0], null);
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}

@Override
public void save(int i) {
try {
methodInterceptor.intercept(this, save1, new Object[]{i}, null);
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}

@Override
public void save(long i) {
try {
methodInterceptor.intercept(this, save2, new Object[]{i}, null);
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void main(String[] args) {
Target target = new Target();

Proxy proxy = new Proxy();
proxy.setMethodInterceptor(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("before");
// 反射调用
return method.invoke(target, args);
}
});

proxy.save();
proxy.save(1);
proxy.save(2L);
}

运行 main() 方法后,控制台打印出:

before
save()
before
save(int)
before
save(long)

13.2 MethodProxy

在上述 Proxy 类中,重写了父类中的方法,并在重写的方法中调用了 intercept() 方法,重写的这些方法相当于是带增强功能的方法。

在 JDK 的动态代理中,使用反射对方法进行调用,而在 CGLib 动态代理中,可以使用 intercept() 方法中 MethodProxy 类型的参数实现不经过反射来调用方法。

接收的 MethodProxy 类型的参数可以像 Method 类型的参数一样,在静态代码块中被实例化。

可以通过静态方法 MethodProxy.create() 来创建 MethodProxy 对象:

1
2
3
4
5
6
7
public static MethodProxy create(Class c1, Class c2, String desc, String name1, String name2) {
MethodProxy proxy = new MethodProxy();
proxy.sig1 = new Signature(name1, desc);
proxy.sig2 = new Signature(name2, desc);
proxy.createInfo = new CreateInfo(c1, c2);
return proxy;
}
  • 参数 c1 指目标类(或者说原始类)的 Class 对象;

  • 参数 c2 指代理类的 Class 对象;

  • 参数 desc 指方法描述符(【Lambda 与序列化】一文中介绍了关于 Java 描述符的更多内容);

  • 参数 name1 指带 增强 功能的方法名称;

  • 参数 name2 指带 原始 功能的方法名称。

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
/**
* @author mofan
* @date 2023/1/18 21:31
*/
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", "saveSuper");
save1Proxy = MethodProxy.create(Target.class, Proxy.class, "(I)V", "save", "saveSuper");
save2Proxy = MethodProxy.create(Target.class, Proxy.class, "(J)V", "save", "saveSuper");
} catch (NoSuchMethodException e) {
throw new NoSuchMethodError(e.getMessage());
}
}

// >>>>>>>>>>>>>>>>>>>>>>>> 带原始功能的方法
public void saveSuper() {
super.save();
}

public void saveSuper(int i) {
super.save(i);
}

public void saveSuper(long i) {
super.save(i);
}


// >>>>>>>>>>>>>>>>>>>>>>>> 带增强功能的方法
@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 i) {
try {
methodInterceptor.intercept(this, save2, new Object[]{i}, save2Proxy);
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
}

main() 方法中不再使用 Method 类型的参数对方法进行调用,而是使用 MethodProxy 类型的参数:

1
2
// 内部没有反射调用,但需要结合目标对象使用
return methodProxy.invoke(target, args);

或者:

1
2
// 内部没有反射调用,但需要结合代理对象使用
return methodProxy.invokeSuper(o, args);

14. MethodProxy 原理

调用 methodProxy.invoke() 方法时,会额外使用一个代理类,该代理类配合目标对象使用。调用 methodProxy.invokeSuper() 方法时,也会额外使用一个代理类,该代理类配合代理对象使用。

当调用 MethodProxy 对象的 invoke() 方法或 invokeSuper() 方法时,就会生成这两个代理类,它们都继承至 FastClass

FastClass 是一个抽象类,其内部有多个抽象方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
public abstract class FastClass {
public abstract int getIndex(String var1, Class[] var2);

public abstract int getIndex(Class[] var1);

public abstract Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException;

public abstract Object newInstance(int var1, Object[] var2) throws InvocationTargetException;

public abstract int getIndex(Signature signature);

public abstract int getMaxIndex();
}

重点讲解 invoke() 方法与 getIndex(Signature signature) 方法。

模拟生成的与目标类相关的代理类

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
/**
* @author mofan
* @date 2023/1/19 17:11
*/
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");

/**
* <p>获取目标方法的编号</p>
* <p>
* Target 目标类中的方法:
* save() 0
* save(int) 1
* save(long) 2
* </p>
*
* @param signature 包含方法名称、参数返回值
* @return 方法编号
*/
public int getIndex(Signature signature) {
if (s0.equals(signature)) {
return 0;
}
if (s1.equals(signature)) {
return 1;
}
if (s2.equals(signature)) {
return 2;
}
return -1;
}

/**
* 根据 getIndex() 方法返回的方法编号正常调用目标对象方法
*
* @param index 方法编号
* @param target 目标对象
* @param args 调用目标对象方法需要的参数
* @return 方法返回结果
*/
public Object invoke(int index, Object target, Object[] args) {
if (index == 0) {
((Target) target).save();
return null;
}
if (index == 1) {
((Target) target).save((int) args[0]);
return null;
}
if (index == 2) {
((Target) target).save((long) args[0]);
return null;
}
throw new RuntimeException("无此方法");
}

public static void main(String[] args) {
TargetFastClass fastClass = new TargetFastClass();
int index = fastClass.getIndex(new Signature("save", "()V"));
fastClass.invoke(index, new Target(), new Object[0]);

index = fastClass.getIndex(new Signature("save", "(J)V"));
fastClass.invoke(index, new Target(), new Object[]{2L});
}
}

运行 main() 方法后,控制台打印出:

save()
save(long)

模拟生成的与代理类相关的代理类

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
/**
* @author mofan
* @date 2023/1/19 17:09
*/
public class ProxyFastClass {
static Signature s0 = new Signature("saveSuper", "()V");
static Signature s1 = new Signature("saveSuper", "(I)V");
static Signature s2 = new Signature("saveSuper", "(J)V");

/**
* <p>获取代理方法的编号</p>
* <p>
* Proxy 代理类中的方法:
* saveSuper() 0
* saveSuper(int) 1
* saveSuper(long) 2
* </p>
*
* @param signature 包含方法名称、参数返回值
* @return 方法编号
*/
public int getIndex(Signature signature) {
if (s0.equals(signature)) {
return 0;
}
if (s1.equals(signature)) {
return 1;
}
if (s2.equals(signature)) {
return 2;
}
return -1;
}

/**
* 根据 getIndex() 方法返回的方法编号正常调用代理对象中带原始功能的方法
*
* @param index 方法编号
* @param proxy 代理对象
* @param args 调用方法需要的参数
* @return 方法返回结果
*/
public Object invoke(int index, Object proxy, Object[] args) {
if (index == 0) {
((Proxy) proxy).saveSuper();
return null;
}
if (index == 1) {
((Proxy) proxy).saveSuper((int) args[0]);
return null;
}
if (index == 2) {
((Proxy) proxy).saveSuper((long) args[0]);
return null;
}
throw new RuntimeException("无此方法");
}

public static void main(String[] args) {
ProxyFastClass fastClass = new ProxyFastClass();
int index = fastClass.getIndex(new Signature("saveSuper", "()V"));
fastClass.invoke(index, new Proxy(), new Object[0]);
}
}

运行 main() 方法后,控制台打印出:

save()

总结

调用 MethodProxy.create() 方法创建 MethodProxy 对象时,要求传递带增强功能的方法名称、带原始功能的方法名称以及方法描述符。

根据两个方法名称和方法描述符可以在调用生成的两个代理类中的 getIndex() 方法时获取被增强方法的编号,之后:

  • 调用 methodProxy.invoke() 方法时,就相当于调用 TargetFastClass 中的 invoke() 方法,并在这个 invoke() 方法中正常调用目标对象方法(Spring 底层的选择)。
  • 调用 methodProxy.invokeSuper() 方法时,就相当于调用 ProxyFastClass 中的 invoke() 方法,并在这个 invoke() 方法中正常调用代理对象中带原始功能的方法。

与 JDK 中优化反射调用方法的对比

  • 在 JDK 中需要反射调用 16 次方法后才会生成优化反射调用的代理类,而在 CGLib 中,当调用 MethodProxy.create() 方法时就会生成由于优化反射调用的代理类;
  • 在 JDK 中一个方法的反射调用优化就要生成一个代理类,而在 CGLib 中,一个代理类生成两个 FastClass 代理类。

15. JDK 和 CGLib 的统一

15.1 advisor

切面有 aspectadvisor 两个概念,aspect 是多组通知(advice)和切点(pointcut)的组合,也是实际编码时使用的,advisor 则是更细粒度的切面,仅包含一个通知和切点,aspect 在生效之前会被拆解成多个 advisor

Spring 中对切点、通知、切面的抽象如下:

classDiagram

class Advisor
class PointcutAdvisor
class Pointcut
class Advice
class MethodInterceptor
class AspectJExpressionPointcut

Pointcut <|-- AspectJExpressionPointcut
Advice <|-- MethodInterceptor
Advisor <|-- PointcutAdvisor
PointcutAdvisor o-- Pointcut
PointcutAdvisor o-- Advice

<<interface>> Advisor
<<interface>> PointcutAdvisor
<<interface>> Advice
<<interface>> Pointcut
<<interface>> MethodInterceptor
  • 切点:即 Pointcut,其典型实现是 AspectJExpressionPointcut
  • 通知:即 Advice,其典型子类接口为 MethodInterceptor,表示环绕通知
  • 切面:即 Advisor,仅包含一个切点和通知

本节将重点介绍 advisor 切面。

15.2 切面与代理对象的创建

通过以下四步创建切面和代理:

  1. 备好切点
  2. 备好通知
  3. 备好切面
  4. 创建代理

在 Spring 中,切点通过接口 org.springframework.aop.Pointcut 来表示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public interface Pointcut {

/**
* 根据类型过滤
*/
ClassFilter getClassFilter();

/**
* 根据方法匹配
*/
MethodMatcher getMethodMatcher();


/**
* Canonical Pointcut instance that always matches.
*/
Pointcut TRUE = TruePointcut.INSTANCE;

}

Pointcut 接口有很多实现类,比如:

  • AnnotationMatchingPointcut:通过注解进行匹配
  • AspectJExpressionPointcut:通过 AspectJ 表达式进行匹配(本节的选择)

在 Spring 中,通知的表示也有很多接口,在此介绍最基本、最重要的接口 org.aopalliance.intercept.MethodInterceptor,这个接口实现的通知属于环绕通知。

在 Spring 中,切面的实现也有很多,在此选择 DefaultPointcutAdvisor,创建这种切面时,传递一个节点和通知。

最后创建代理对象时,无需显式实现 JDK 动态代理或 CGLib 动态代理,Spring 提供了名为 ProxyFactory 的工厂,其内部通过不同的情况选择不同的代理实现,更方便地创建代理对象。

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
interface I1 {
void foo();

void bar();
}

static class Target1 implements I1 {

@Override
public void foo() {
System.out.println("target1 foo");
}

@Override
public void bar() {
System.out.println("target1 bar");
}
}

static class Target2 {
public void foo() {
System.out.println("target2 foo");
}

public void bar() {
System.out.println("target2 bar");
}
}

public static void main(String[] args) {
/*
* 两个切面概念:
* aspect =
* 通知 1 (advice) + 切点 1(pointcut)
* 通知 2 (advice) + 切点 2(pointcut)
* 通知 3 (advice) + 切点 3(pointcut)
* ...
*
* advisor = 更细粒度的切面,包含一个通知和切点
* */

// 1. 备好切点(根据 AspectJ 表达式进行匹配)
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 factory = new ProxyFactory();
factory.setTarget(target);
factory.addAdvisor(advisor);

I1 proxy = (I1) factory.getProxy();
System.out.println(proxy.getClass());
proxy.foo();
proxy.bar();
}

运行 main() 方法,控制台打印出:

class indi.mofan.a15.A15$Target1$$EnhancerBySpringCGLIB$$381723d1
before...
target1 foo
after...
target1 bar

foo() 方法被增强,但 bar() 并没有,并且选择了 CGLib 动态代理作为代理的实现。

Spring 是根据什么信息来选择不同的动态代理实现呢?

ProxyFactory 的父类 ProxyConfig 中有个名为 proxyTargetClass 的布尔类型成员变量:

  • proxyTargetClass == false,并且目标对象所在类实现了接口时,将选择 JDK 动态代理;

  • proxyTargetClass == false,但目标对象所在类未实现接口时,将选择 CGLib 动态代理;

  • proxyTargetClass == true,总是选择 CGLib 动态代理。

上文中的 target 对象的所在类 Targer1 实现了 I1 接口,最终为什么依旧选择了 CGLib 动态代理作为代理类的创建方式呢?

这是因为并没有显式这是 target 对象的实现类,Spring 认为其并未实现接口。

设置 factory 对象的 interfaces 信息:

1
factory.setInterfaces(target.getClass().getInterfaces());

之后再运行 main(),控制台打印出:

class indi.mofan.a15.$Proxy0
before...
target1 foo
after...
target1 bar

此时选择的动态代理实现方式是 JDK 动态代理。

再设置 factory 对象的 proxyTargetClasstrue

1
factory.setProxyTargetClass(true);

运行 main() 方法后,控制台打印出以下内容,选择 CGLib 动态代理作为动态代理的实现方式:

class indi.mofan.a15.A15$Target1$$EnhancerBySpringCGLIB$$34c2d9b8
before...
target1 foo
after...
target1 bar

再将 proxyTargetClass 的值修改回 false,并修改目标对象的所在类为 Target2Target2 并未实现任何接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static void main(String[] args) {
// --snip--

// 4. 创建代理
Target2 target = new Target2();
ProxyFactory factory = new ProxyFactory();
factory.setTarget(target);
factory.addAdvisor(advisor);
factory.setInterfaces(target.getClass().getInterfaces());
factory.setProxyTargetClass(false);

Target2 proxy = (Target2) factory.getProxy();
System.out.println(proxy.getClass());
proxy.foo();
proxy.bar();
}

运行 main() 方法后,控制台打印出以下内容,依旧选择 CGLib 动态代理作为动态代理的实现方式:

class indi.mofan.a15.A15$Target2$$EnhancerBySpringCGLIB$$4bb2ac74
before...
target2 foo
after...
target2 bar

ProxyFactory 是用来创建代理的核心实现,使用 AopProxyFactory 选择具体的代理实现:

  • JdkDynamicAopProxy
  • ObjenesisCglibAopProxy
classDiagram

Advised <|-- ProxyFactory
ProxyFactory o-- Target
ProxyFactory o-- "多" Advisor

ProxyFactory --> AopProxyFactory : 使用
AopProxyFactory --> AopProxy
Advised <|-- 基于CGLIB的Proxy
基于CGLIB的Proxy <-- ObjenesisCglibAopProxy : 创建
AopProxy <|-- ObjenesisCglibAopProxy
AopProxy <|-- JdkDynamicAopProxy
基于JDK的Proxy <-- JdkDynamicAopProxy : 创建
Advised <|-- 基于JDK的Proxy

class AopProxy {
   +getProxy() Object
}

class ProxyFactory {
	proxyTargetClass : boolean
}

class ObjenesisCglibAopProxy {
	advised : ProxyFactory
}

class JdkDynamicAopProxy {
	advised : ProxyFactory
}

<<interface>> Advised
<<interface>> AopProxyFactory
<<interface>> AopProxy

AopProxyFactory 根据 proxyTargetClass 等设置选择 AopProxy 实现,AopProxy 通过 getProxy() 方法创建代理对象。

上述类图中的类与接口都实现了 Advised 接口,能够获得关联的切面集合与目标(实际上是从 ProxyFactory 中获取的)。

调用代理方法时,会借助 ProxyFactory 统一将通知转换为环绕通知 MethodInterceptor

16. 切点匹配

上一节中,选择 AspectJExpressionPointcut 作为切点的实现,判断编写的 AspectJ 表达式是否与某一方法匹配可以使用其 matches() 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static void main(String[] args) throws NoSuchMethodException {
AspectJExpressionPointcut pt1 = new AspectJExpressionPointcut();
pt1.setExpression("execution(* bar())");
System.out.println(pt1.matches(T1.class.getMethod("foo"), T1.class));
System.out.println(pt1.matches(T1.class.getMethod("bar"), T1.class));

AspectJExpressionPointcut pt2 = new AspectJExpressionPointcut();
pt2.setExpression("@annotation(org.springframework.transaction.annotation.Transactional)");
System.out.println(pt2.matches(T1.class.getMethod("foo"), T1.class));
System.out.println(pt2.matches(T1.class.getMethod("bar"), T1.class));
}

static class T1 {
@Transactional
public void foo() {

}

public void bar() {

}
}

运行 main() 方法后,控制台打印出:

false
true
true
false

@Transactional 是 Spring 中使用频率非常高的注解,那它底层是通过 AspectJExpressionPointcut@annotation() 切点表达式相结合对目标方法进行匹配的吗?

答案是否定的。@Transactional 注解除了可以作用在方法上,还可以作用在类(或接口)上。

在底层 @Transactional 注解的匹配使用到了 StaticMethodMatcherPointcut,在此模拟一下:

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
public static void main(String[] args) throws NoSuchMethodException {

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);
return annotations.isPresent(Transactional.class);
}
};

System.out.println(pt3.matches(T1.class.getMethod("foo"), T1.class));
System.out.println(pt3.matches(T1.class.getMethod("bar"), T1.class));
System.out.println(pt3.matches(T2.class.getMethod("foo"), T2.class));
System.out.println(pt3.matches(T3.class.getMethod("foo"), T3.class));
}

static class T1 {
@Transactional
public void foo() {

}

public void bar() {

}
}

@Transactional
static class T2 {
public void foo() {

}
}

@Transactional
interface I3 {
void foo();
}

static class T3 implements I3 {
@Override
public void foo() {

}
}

运行 main() 方法后,控制台打印出:

true
false
true
true

无论是 AspectJExpressionPointcut 还是 StaticMethodMatcherPointcut,它们都实现了 MethodMatcher 接口,用来执行方法的匹配。

17. 从 @Aspect 到 Advisor

17.1 AnnotationAwareAspectJAutoProxyCreator

讲解之前,准备一下类:

  • 两个目标类
  • 一个使用 @Aspect 的高级切面
  • 一个利用配置类实现的低级切面 Advisor
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
static class Target1 {
public void foo() {
System.out.println("target1 foo");
}
}

static class Target2 {
public void bar() {
System.out.println("target2 bar");
}
}

/**
* 高级切面
*/
@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...");
}
}

@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 advices() {
return invocation -> {
System.out.println("advice3 before...");
Object result = invocation.proceed();
System.out.println("advice3 after...");
return result;
};
}
}

编写 main() 方法创建 Spring 容器,并添加必要的 Bean:

1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("aspect1", Aspect1.class);
context.registerBean("config", Config.class);
context.registerBean(ConfigurationClassPostProcessor.class);

context.refresh();
for (String name : context.getBeanDefinitionNames()) {
System.out.println(name);
}
context.close();
}

运行 main() 方法后,控制台打印出:

aspect1
config
org.springframework.context.annotation.ConfigurationClassPostProcessor
advisor3
advices

Spring 中存在一个名为 AnnotationAwareAspectJAutoProxyCreator 的 Bean 后置处理器,尽管它的名称中没有 BeanPostProcessor 的字样,但它确实是实现了 BeanPostProcessor 接口的。

AnnotationAwareAspectJAutoProxyCreator 有两个主要作用:

  1. 找到容器中所有的切面,针对高级切面,将其转换为低级切面;
  2. 根据切面信息,利用 ProxyFactory 创建代理对象。

AnnotationAwareAspectJAutoProxyCreator 实现了 BeanPostProcessor,可以在 Bean 生命周期中的一些阶段对 Bean 进行拓展。AnnotationAwareAspectJAutoProxyCreator 可以在 Bean 进行 依赖注入之前、Bean 初始化之后 对 Bean 进行拓展。

重点介绍 AnnotationAwareAspectJAutoProxyCreator 中的两个方法:

  • findEligibleAdvisors():位于父类 AbstractAdvisorAutoProxyCreator 中,用于找到符合条件的切面类。低级切面直接添加,高级切面转换为低级切面再添加。
  • wrapIfNecessary():位于父类 AbstractAutoProxyCreator 中,用于将有资格被代理的 Bean 进行包装,即创建代理对象。

findEligibleAdvisors() 方法

findEligibleAdvisors() 方法接收两个参数:

  • beanClass:配合切面使用的目标类 Class 信息
  • beanName:当前被代理的 Bean 的名称

修改 main() 方法,向容器中添加 AnnotationAwareAspectJAutoProxyCreator 后置处理器,测试 findEligibleAdvisors() 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("aspect1", Aspect1.class);
context.registerBean("config", Config.class);
context.registerBean(ConfigurationClassPostProcessor.class);

context.registerBean(AnnotationAwareAspectJAutoProxyCreator.class);

context.refresh();

// 测试 findEligibleAdvisors 方法
AnnotationAwareAspectJAutoProxyCreator creator = context.getBean(AnnotationAwareAspectJAutoProxyCreator.class);
// 获取能够配合 Target1 使用的切面
List<Advisor> advisors = creator.findEligibleAdvisors(Target1.class, "target1");
advisors.forEach(System.out::println);

context.close();
}

运行 main() 方法后,控制台打印出:

org.springframework.aop.interceptor.ExposeInvocationInterceptor.ADVISOR
org.springframework.aop.support.DefaultPointcutAdvisor: pointcut [AspectJExpressionPointcut: () execution(* foo())]; advice [org.springframework.aop.framework.autoproxy.A17$Config$$Lambda$56/802243390@7bd4937b]
InstantiationModelAwarePointcutAdvisor: expression [execution(* foo())]; advice method [public void org.springframework.aop.framework.autoproxy.A17$Aspect1.before()]; perClauseKind=SINGLETON
InstantiationModelAwarePointcutAdvisor: expression [execution(* foo())]; advice method [public void org.springframework.aop.framework.autoproxy.A17$Aspect1.after()]; perClauseKind=SINGLETON

打印出 4 个能配合 Target1 使用的切面信息,其中:

  • 第一个切面 ExposeInvocationInterceptor.ADVISOR 是 Spring 为每个代理对象都会添加的切面;
  • 第二个切面 DefaultPointcutAdvisor 是自行编写的低级切面;
  • 第三个和第四个切面 InstantiationModelAwarePointcutAdvisor 是由高级切面转换得到的两个低级切面。

若按照 creator.findEligibleAdvisors(Target2.class, "target2") 的方式进行调用,控制台不会打印出任何信息,因为没有任何切面能够配合 Target2 使用。

wrapIfNecessary() 方法

wrapIfNecessary() 方法内部调用了 findEligibleAdvisors() 方法,若 findEligibleAdvisors() 方法返回的集合不为空,则表示需要创建代理对象。

如果需要创建对象,wrapIfNecessary() 方法返回的是代理对象,否则仍然是原对象。

wrapIfNecessary() 方法接收三个参数:

  • bean:原始 Bean 实例
  • beanName:Bean 的名称
  • cacheKey:用于元数据访问的缓存 key
1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) {
// --snip--

Object o1 = creator.wrapIfNecessary(new Target1(), "target1", "target1");
System.out.println(o1.getClass());
Object o2 = creator.wrapIfNecessary(new Target2(), "target2", "target2");
System.out.println(o2.getClass());

context.close();
}

运行 main() 方法后,控制台打印出:

class org.springframework.aop.framework.autoproxy.A17$Target1$$EnhancerBySpringCGLIB$$634976f6
class org.springframework.aop.framework.autoproxy.A17$Target2

Target1 对象是被代理的,而 Target2 依旧是原对象。

如果将 o1 转换为 Target1,并调用 foo() 方法,foo() 方法将被增强:

1
2
3
4
5
6
7
public static void main(String[] args) {
// --snip--

((Target1) o1).foo();

context.close();
}
advice3 before...
aspect1 before...
target1 foo
aspect1 after...
advice3 after...

切面的顺序控制

根据上述打印的信息可知,低级切面相比于高级切面先一步被执行,这个执行顺序是可以被控制的。

针对高级切面来说,可以在类上使用 @Order 注解,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Aspect
@Order(1)
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...");
}
}

在高级切面中,@Order 只有放在类上才生效,放在方法上不会生效。比如高级切面中有多个前置通知,这些前置通知对应的方法上使用 @Order 注解是无法生效的。

针对低级切面,需要设置 advisororder 值,而不是向高级切面那样使用 @Order 注解,使用 @Order 注解设置在 advisor3() 方法上是无用的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Configuration
static class Config {
/**
* 低级切面,由一个切点和一个通知组成
*/
@Bean
public Advisor advisor3(MethodInterceptor advice3) {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(* foo())");
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, advice3);
// 设置切面执行顺序
advisor.setOrder(2);
return advisor;
}

// --snip--
}

设置完成后,高级切面的执行优先级高于低级切面。执行 main() 方法验证执行顺序是否改变:

aspect1 before...
advice3 before...
target1 foo
advice3 after...
aspect1 after...

17.2 代理对象创建时机

使用 AnnotationAwareAspectJAutoProxyCreator Bean 后置处理器创建代理对象的时机有以下两个选择:

  • Bean 的依赖注入之前
  • Bean 初始化完成之后

这两个时机二选一,不会重复创建代理对象。

以下述代码为例,查看代理对象的创建时机:

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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
package org.springframework.aop.framework.autoproxy;

/**
* @author mofan
* @date 2023/1/20 22:13
*/
public class A17_1 {
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean(ConfigurationClassPostProcessor.class);
context.registerBean(Config.class);
context.refresh();
context.close();
// 创建 -> (*) 依赖注入 -> 初始化 (*)

}

@Configuration
static class Config {
/**
* 解析 @AspectJ 注解,产生代理
*/
@Bean
public AnnotationAwareAspectJAutoProxyCreator annotationAwareAspectJAutoProxyCreator() {
return new AnnotationAwareAspectJAutoProxyCreator();
}

/**
* 解析 @Autowired
*/
@Bean
public AutowiredAnnotationBeanPostProcessor autowiredAnnotationBeanPostProcessor() {
return new AutowiredAnnotationBeanPostProcessor();
}

/**
* 解析 @PostConstruct
*/
@Bean
public CommonAnnotationBeanPostProcessor commonAnnotationBeanPostProcessor() {
return new CommonAnnotationBeanPostProcessor();
}

@Bean
public Advisor advisor(MethodInterceptor advice) {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(* foo())");
return new DefaultPointcutAdvisor(pointcut, advice);
}

@Bean
public MethodInterceptor advice() {
return invocation -> {
System.out.println("before...");
return invocation.proceed();
};
}

@Bean
public Bean1 bean1() {
return new Bean1();
}

@Bean
public Bean2 bean2() {
return new Bean2();
}
}

static class Bean1 {
public void foo() {}
public Bean1() {
System.out.println("Bean1()");
}
@PostConstruct
public void init() {
System.out.println("Bean1 init()");
}
}

static class Bean2 {
public Bean2() {
System.out.println("Bean2()");
}
@Autowired
public void setBean1(Bean1 bean1) {
System.out.println("Bean2 setBean1(bean1) class is: " + bean1.getClass());
}
@PostConstruct
public void init() {
System.out.println("Bean2 init()");
}
}
}

其中 bean2 中注入了 bean1。运行 main() 方法后,控制台打印出:

Bean1()
Bean1 init()
Creating implicit proxy for bean 'bean1' with 0 common interceptors and 2 specific interceptors 
Bean2()
Bean2 setBean1(bean1) class is: class org.springframework.aop.framework.autoproxy.A17_1$Bean1$$EnhancerBySpringCGLIB$$b7d6405
Bean2 init()

bean1 初始化完成后,额外打印了一句日志信息:

Creating implicit proxy for bean 'bean1' with 0 common interceptors and 2 specific interceptors

表示为 bean1 创建了隐式代理。

此时代理对象在 Bean 初始化完成之后创建。

之后为 bean2 进行依赖注入时,注入的 bean1 是代理对象。

Bean1 类中添加 setBean2() 方法,表示向 bean1 中注入 bean2,此时 bean1 依赖 bean2,而 bean2 原本就依赖了 bean1,出现循环依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static class Bean1 {
public void foo() {}
public Bean1() {
System.out.println("Bean1()");
}
@Autowired
public void setBean2(Bean2 bean2) {
System.out.println("Bean1 setBean2(bean2) class is: " + bean2.getClass());
}
@PostConstruct
public void init() {
System.out.println("Bean1 init()");
}
}

再次运行 main() 方法,查看 bean1 的代理对象的生成时机:

Bean1()
Bean2()
Creating implicit proxy for bean 'bean1' with 0 common interceptors and 2 specific interceptors 
Bean2 setBean1(bean1) class is: class org.springframework.aop.framework.autoproxy.A17_1$Bean1$$EnhancerBySpringCGLIB$$5cff48bf
Bean2 init()
Bean1 setBean2(bean2) class is: class org.springframework.aop.framework.autoproxy.A17_1$Bean2
Bean1 init()

首先进行 bean1 的实例化,然后进行 bean1 的依赖注入,但此时容器中并没有 bean2,因此需要进行 bean2 的实例化。

接下来进行 bean2 的依赖注入,向 bean2 中注入 bean1,注入的 bean1 应该是被增强的,即它的代理对象,因此创建 bean1 的代理对象后再完成 bean2 的依赖注入。

接着继续 bean2 的生命周期,完成 bean2 的初始化阶段,最后回到 bean1 的依赖注入阶段,向 bean1 中注入 bean2,最后完成 bean1 的初始化阶段。

总结

代理对象的创建时机:

  • 无循环依赖时,在 Bean 初始化阶段之后创建;
  • 有循环依赖时,在 Bean 实例化后、依赖注入之前创建,并将代理对象暂存于二级缓存。

Bean 的依赖注入阶段和初始化阶段不应该被增强,仍应被施加于原始对象。

17.3 高级切面转低级切面

调用 AnnotationAwareAspectJAutoProxyCreator 对象的 findEligibleAdvisors() 方法时,获取能配合目标 Class 使用的切面,最终返回 Advisor 列表。在搜索过程中,如果遇到高级切面,则会将其转换成低级切面。

现有切面类与目标类信息如下:

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
static class Aspect {
@Before("execution(* foo())")
public void before1() {
System.out.println("before1");
}

@Before("execution(* foo())")
public void before2() {
System.out.println("before2");
}

public void after() {
System.out.println("after");
}

public void afterReturning() {
System.out.println("afterReturning");
}

public void afterThrowing() {
System.out.println("afterThrowing");
}

public Object around(ProceedingJoinPoint pjp) throws Throwable {
try {
System.out.println("around...before");
return pjp.proceed();
} finally {
System.out.println("around...after");
}
}
}

static class Target {
public void foo() {
System.out.println("target foo");
}
}

高级切面中与通知类型相关的常用注解有 5 个:

  • @Before:前置通知
  • @AfterReturning:后置通知
  • @AfterThrowing:异常通知
  • @After:最终通知
  • @Around:环绕通知

以解析 @Before 注解为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static void main(String[] args) throws Throwable {
// 切面对象实例工厂,用于后续反射调用切面中的方法
AspectInstanceFactory factory = new SingletonAspectInstanceFactory(new Aspect());
// 高级切面转低级切面类
List<Advisor> list = new ArrayList<>();
for (Method method : Aspect.class.getDeclaredMethods()) {
if (method.isAnnotationPresent(Before.class)) {
// 解析切点
String expression = method.getAnnotation(Before.class).value();
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression(expression);
// 通知类。前置通知对应的通知类是 AspectJMethodBeforeAdvice
AspectJMethodBeforeAdvice advice = new AspectJMethodBeforeAdvice(method, pointcut, factory);
// 切面
Advisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
list.add(advisor);
}
}
for (Advisor advisor : list) {
System.out.println(advisor);
}
}

运行 main() 方法,控制台打印出:

org.springframework.aop.support.DefaultPointcutAdvisor: pointcut [AspectJExpressionPointcut: () execution(* foo())]; advice [org.springframework.aop.aspectj.AspectJMethodBeforeAdvice: advice method [public void org.springframework.aop.framework.autoproxy.A17_2$Aspect.before2()]; aspect name '']
org.springframework.aop.support.DefaultPointcutAdvisor: pointcut [AspectJExpressionPointcut: () execution(* foo())]; advice [org.springframework.aop.aspectj.AspectJMethodBeforeAdvice: advice method [public void org.springframework.aop.framework.autoproxy.A17_2$Aspect.before1()]; aspect name '']

@Before 标记的前置通知会被转换成原始的 AspectJMethodBeforeAdvice 形式,该对象包含了以下信息:

  • 通知对应的方法信息
  • 切点信息
  • 通知对象如何创建,本例公用一个 Aspect 对象

通知相关注解与原始通知类对应关系如下:

注解 对应的原始通知类
@Before AspectJMethodBeforeAdvice
@AfterReturning AspectJAfterReturningAdvice
@AfterThrowing AspectJAfterThrowingAdvice
@After AspectJAfterAdvice
@Around AspectJAroundAdvice

18. 静态通知调用

18.1 统一转换成环绕通知

通知相关注解都对应一个原始通知类,在 Spring 底层会将这些通知转换成环绕通知 MethodInterceptor。如果原始通知类本就实现了 MethodInterceptor 接口,则无需转换。

原始通知类 是否需要转换成 MethodInterceptor
AspectJMethodBeforeAdvice
AspectJAfterReturningAdvice
AspectJAfterThrowingAdvice
AspectJAfterAdvice
AspectJAroundAdvice

使用 ProxyFactory 无论基于哪种方式创建代理对象,最终调用 advice(通知,或者说通知对应的方法)的都是 MethodInvocation 对象。

项目中存在的 advisor(原本的低级切面和由高级切面转换得到的低级切面)往往不止一个,它们一个套一个地被调用,因此需要一个调用链对象,即 MethodInvocation

MethodInvocation 需要知道 advice 有哪些,还需要知道目标对象是哪个。调用次序如下:

通知调用次序

由上图可知,环绕 通知最适合作为 advice,而 Before、AfterReturning 都应该转换成环绕通知。

统一转换成环绕通知的形式,体现了设计模式中的适配器模式:

  • 对外更方便使用和区分各种通知类型
  • 对内统一都是环绕通知,统一使用 MethodInterceptor 表示

通过 ProxyFactory 对象的 getInterceptorsAndDynamicInterceptionAdvice() 方法将其他通知统一转换为 MethodInterceptor 环绕通知:

注解 原始通知类 适配器 拦截器
@Before AspectJMethodBeforeAdvice MethodBeforeAdviceAdapter MethodBeforeAdviceInterceptor
@AfterReturning AspectJAfterReturningAdvice AspectJAfterReturningAdvice AfterReturningAdviceInterceptor

转换得到的通知都是静态通知,体现在 getInterceptorsAndDynamicInterceptionAdvice() 方法中的 Interceptors 部分,这些通知在被调用时无需再次检查切点,直接调用即可。

代码测试

切面类与目标类:

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
static class Aspect {
@Before("execution(* foo())")
public void before1() {
System.out.println("before1");
}

@Before("execution(* foo())")
public void before2() {
System.out.println("before2");
}

public void after() {
System.out.println("after");
}

@AfterReturning("execution(* foo())")
public void afterReturning() {
System.out.println("afterReturning");
}

@AfterThrowing("execution(* foo())")
public void afterThrowing(Exception e) {
System.out.println("afterThrowing " + e.getMessage());
}

@Around("execution(* foo())")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
try {
System.out.println("around...before");
return pjp.proceed();
} finally {
System.out.println("around...after");
}
}
}

static class Target {
public void foo() {
System.out.println("target foo");
}
}

将高级切面转换成低级切面,并将通知统一转换成环绕通知:

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
@SuppressWarnings("all")
public static void main(String[] args) throws Throwable {

AspectInstanceFactory factory = new SingletonAspectInstanceFactory(new Aspect());
// 1. 高级切面转低级切面类
List<Advisor> list = new ArrayList<>();
for (Method method : Aspect.class.getDeclaredMethods()) {
if (method.isAnnotationPresent(Before.class)) {
// 解析切点
String expression = method.getAnnotation(Before.class).value();
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression(expression);
// 通知类
AspectJMethodBeforeAdvice advice = new AspectJMethodBeforeAdvice(method, pointcut, factory);
// 切面
Advisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
list.add(advisor);
} else if (method.isAnnotationPresent(AfterReturning.class)) {
// 解析切点
String expression = method.getAnnotation(AfterReturning.class).value();
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression(expression);
// 通知类
AspectJAfterReturningAdvice advice = new AspectJAfterReturningAdvice(method, pointcut, factory);
// 切面
Advisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
list.add(advisor);
} else if (method.isAnnotationPresent(Around.class)) {
// 解析切点
String expression = method.getAnnotation(Around.class).value();
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression(expression);
// 通知类
AspectJAroundAdvice advice = new AspectJAroundAdvice(method, pointcut, factory);
// 切面
Advisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
list.add(advisor);
}
}
for (Advisor advisor : list) {
System.out.println(advisor);
}

// 2. 通知统一转换为环绕通知 MethodInterceptor
Target target = new Target();
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(target);
proxyFactory.addAdvisors(list);

System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
List<Object> methodInterceptorList = proxyFactory.getInterceptorsAndDynamicInterceptionAdvice(Target.class.getMethod("foo"), Target.class);
for (Object o : methodInterceptorList) {
System.out.println(o);
}
}

运行 main() 方法后,控制台打印出:

org.springframework.aop.support.DefaultPointcutAdvisor: pointcut [AspectJExpressionPointcut: () execution(* foo())]; advice [org.springframework.aop.aspectj.AspectJMethodBeforeAdvice: advice method [public void org.springframework.aop.framework.A18$Aspect.before2()]; aspect name '']
org.springframework.aop.support.DefaultPointcutAdvisor: pointcut [AspectJExpressionPointcut: () execution(* foo())]; advice [org.springframework.aop.aspectj.AspectJAroundAdvice: advice method [public java.lang.Object org.springframework.aop.framework.A18$Aspect.around(org.aspectj.lang.ProceedingJoinPoint) throws java.lang.Throwable]; aspect name '']
org.springframework.aop.support.DefaultPointcutAdvisor: pointcut [AspectJExpressionPointcut: () execution(* foo())]; advice [org.springframework.aop.aspectj.AspectJMethodBeforeAdvice: advice method [public void org.springframework.aop.framework.A18$Aspect.before1()]; aspect name '']
org.springframework.aop.support.DefaultPointcutAdvisor: pointcut [AspectJExpressionPointcut: () execution(* foo())]; advice [org.springframework.aop.aspectj.AspectJAfterReturningAdvice: advice method [public void org.springframework.aop.framework.A18$Aspect.afterReturning()]; aspect name '']
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor@7ce6a65d
org.springframework.aop.aspectj.AspectJAroundAdvice: advice method [public java.lang.Object org.springframework.aop.framework.A18$Aspect.around(org.aspectj.lang.ProceedingJoinPoint) throws java.lang.Throwable]; aspect name ''
org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor@1500955a
org.springframework.aop.framework.adapter.AfterReturningAdviceInterceptor@e874448

根据打印信息可知:

  • 前置通知 AspectJMethodBeforeAdvice 被转换成 MethodBeforeAdviceInterceptor
  • 环绕通知 AspectJAroundAdvice 保持不变
  • 后置通知 AspectJAfterReturningAdvice 被转换成 AfterReturningAdviceInterceptor

18.2 调用链执行

高级切面成功转换成低级切面,切面中的通知也全部转换成环绕通知 MethodInterceptor,最后还要调用这些通知和目标方法。

这个调用交由调用链对象 MethodInvocation 来完成,在调用链对象中存放了所有经过转换得到的环绕通知和目标方法。

MethodInvocation 是一个接口,其最根本的实现是 ReflectiveMethodInvocation

构建 ReflectiveMethodInvocation 对象需要 6 个参数:

  1. proxy:代理对象
  2. target:目标对象
  3. method:目标对象中的方法对象
  4. arguments:调用目标对象中的方法需要的参数
  5. targetClass:目标对象的 Class 对象
  6. interceptorsAndDynamicMethodMatchers:转换得到的环绕通知列表
1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) throws Throwable {
// --snip--

System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
// 3. 创建并执行调用链 (环绕通知s + 目标)
MethodInvocation methodInvocation = new ReflectiveMethodInvocation(
null, target, Target.class.getMethod("foo"), new Object[0], Target.class, methodInterceptorList
);
methodInvocation.proceed();
}

运行 main() 方法后会抛出异常:

Exception in thread "main" java.lang.IllegalStateException: No MethodInvocation found:

提示没有找到 MethodInvocation。但调用链对象不是已经创建好了吗?

这是因为调用链在执行过程会调用到很多通知,而某些通知内部可能需要使用调用链对象。因此需要将调用链对象存放在某一位置,使所有通知都能获取到调用链对象。

这个“位置”就是 当前线程

那怎么将调用链对象放入当前线程呢?

可以在所有通知的最外层再添加一个环绕通知,其作用是将调用链对象放入当前线程。

将MethodInvocation放入当前线程

可以使用 Spring 提供的 ExposeInvocationInterceptor 作为最外层的环绕通知。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static void main(String[] args) throws Throwable {
// --snip--

// 2. 通知统一转换为环绕通知 MethodInterceptor
Target target = new Target();
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(target);
// 在最外层添加环绕通知,把 MethodInvocation 放入当前线程
proxyFactory.addAdvice(ExposeInvocationInterceptor.INSTANCE);
proxyFactory.addAdvisors(list);

// --snip--

System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
// 3. 创建并执行调用链 (环绕通知s + 目标)
MethodInvocation methodInvocation = new ReflectiveMethodInvocation(
null, target, Target.class.getMethod("foo"), new Object[0], Target.class, methodInterceptorList
);
methodInvocation.proceed();
}

再次运行 main() 方法不再报错,控制台打印出:

before1
around...before
before2
target foo
around...after
afterReturning

18.3 模拟实现调用链

调用链执行过程是一个递归过程。执行 proceed() 方法将调用调用链中下一个通知或目标方法。当调用链中没有通知时,就调用目标方法,反之调用下一个通知。

这体现了设计模式中的责任链模式。

目标类 Target

1
2
3
4
5
static class Target {
public void foo() {
System.out.println("Target foo()");
}
}

实现 MethodInterceptor 接口,编写两个环绕通知:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static class Advice1 implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("Advice1.before()");
Object result = invocation.proceed();
System.out.println("Advice1.after()");
return result;
}
}

static class Advice2 implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("Advice2.before()");
Object result = invocation.proceed();
System.out.println("Advice2.after()");
return result;
}
}

实现 MethodInvocation 接口,实现自己的调用链:

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
static class MyInvocation implements MethodInvocation {

private final Object target;
private final Method method;
private final Object[] args;
private final List<MethodInterceptor> methodInterceptorList;
private int count = 1;

public MyInvocation(Object target, Method method, Object[] args, List<MethodInterceptor> methodInterceptorList) {
this.target = target;
this.method = method;
this.args = args;
this.methodInterceptorList = methodInterceptorList;
}

@Override
public Method getMethod() {
return this.method;
}

@Override
public Object[] getArguments() {
return this.args;
}

@Override
public Object proceed() throws Throwable {
if (count > methodInterceptorList.size()) {
// 调用目标,结束递归并返回
return method.invoke(target, args);
}
// 逐一调用通知
MethodInterceptor interceptor = methodInterceptorList.get(count++ - 1);
// 递归操作交给通知类
return interceptor.invoke(this);
}

@Override
public Object getThis() {
return this.target;
}

@Override
public AccessibleObject getStaticPart() {
return method;
}
}

编写 main() 方法,执行调用链,查看控制台输出结果:

1
2
3
4
5
6
7
8
9
public static void main(String[] args) throws Throwable {
Target target = new Target();
List<MethodInterceptor> list = new ArrayList<>(Arrays.asList(
new Advice1(),
new Advice2()
));
MyInvocation invocation = new MyInvocation(target, Target.class.getMethod("foo"), new Object[0], list);
invocation.proceed();
}
Advice1.before()
Advice2.before()
Target foo()
Advice2.after()
Advice1.after()

18.4 代理对象调用流程

以 JDK 动态代理实现为例:

  • ProxyFactory 获得 Target 和环绕通知链,根据它们创建 MethodInvocation 对象,简称 mi
  • 首次执行 mi.proceed() 后发现有下一个环绕通知,调用它的 invoke(mi)
  • 进入环绕通知 1,执行前增强,再次调用 mi.proceed() 后又发现有下一个环绕通知,调用它的 invoke(mi)
  • 进入环绕通知 2,执行前增强,调用 mi.proceed() 发现没有环绕通知,调用 mi.invokeJoinPoint() 执行目标方法
  • 目标方法执行结束,将结果返回给环绕通知 2,执行环绕通知 2 的后增强
  • 环绕通知 2 继续将结果返回给环绕通知 1,执行环绕通知 1 的后增强
  • 环绕通知 1 返回最终的结果

下图中不同颜色对应一次环绕通知或目标的调用起始至终结:

sequenceDiagram
participant Proxy
participant ih as InvocationHandler
participant mi as MethodInvocation
participant Factory as ProxyFactory
participant mi1 as MethodInterceptor1
participant mi2 as MethodInterceptor2
participant Target

Proxy ->> +ih : invoke()
ih ->> +Factory : 获得 Target
Factory -->> -ih : 返回 Target
ih ->> +Factory : 获得 MethodInterceptor 链
Factory -->> -ih : 返回 MethodInterceptor 链
ih ->> +mi : 创建 mi
mi -->> -ih : 返回 mi
rect rgb(200, 223, 255)
ih ->> +mi : mi.proceed()
mi ->> +mi1 : invoke(mi)
mi1 ->> mi1 : 前增强
rect rgb(200, 190, 255)
mi1 ->> mi : mi.proceed()
mi ->> +mi2 : invoke(mi)
mi2 ->> mi2 : 前增强
rect rgb(150, 190, 155)
mi2 ->> mi : mi.proceed()
mi ->> +Target : mi.invokeJoinPoint()
Target ->> Target : 调用目标方法
Target -->> -mi2 : 结果
end
mi2 ->> mi2 : 后增强
mi2 -->> -mi1 : 结果
end
mi1 ->> mi1 : 后增强
mi1 -->> -mi : 结果
mi -->> -ih : 结果
end
ih -->> -Proxy : 结果

19. 动态通知调用

前文的示例都是静态通知调用,无需参数绑定,执行时无需切点信息,性能较高。

相应地就有动态通知调用,它需要参数绑定,执行时还需要切点信息,性能较低。比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Aspect
static class MyAspect {
/**
* 静态通知调用,无需参数绑定,性能较高
* 执行时无需切点信息
*/
@Before("execution(* foo(..))")
public void before1() {
System.out.println("before1");
}

/**
* 动态通知调用,需要参数绑定,性能较低
* 执行时还需要切点信息
*/
@Before("execution(* foo(..)) && args(x)")
public void before2(int x) {
System.out.printf("before(%d)\n", x);
}
}

目标类 Target

1
2
3
4
5
static class Target {
public void foo(int x) {
System.out.printf("target foo(%d)\n", x);
}
}

配置类 MyConfig

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
static class MyConfig {
@Bean
public AnnotationAwareAspectJAutoProxyCreator proxyCreator() {
return new AnnotationAwareAspectJAutoProxyCreator();
}

@Bean
public MyAspect myAspect() {
return new MyAspect();
}
}

编写 main() 方法,新建 Spring 容器,查找符合条件的切面,将所有通知转换成环绕通知:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void main(String[] args) throws Throwable {
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean(ConfigurationClassPostProcessor.class);
context.registerBean(MyConfig.class);
context.refresh();

AnnotationAwareAspectJAutoProxyCreator creator = context.getBean(AnnotationAwareAspectJAutoProxyCreator.class);
List<Advisor> list = creator.findEligibleAdvisors(Target.class, "target");

Target target = new Target();
ProxyFactory factory = new ProxyFactory();
factory.setTarget(target);
factory.addAdvisors(list);

List<Object> interceptorList = factory.getInterceptorsAndDynamicInterceptionAdvice(Target.class.getMethod("foo", int.class), Target.class);
for (Object o : interceptorList) {
System.out.println(o);
}
}

执行 main() 方法,控制台打印出:

org.springframework.aop.interceptor.ExposeInvocationInterceptor@73e22a3d
org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor@47faa49c
org.springframework.aop.framework.InterceptorAndDynamicMethodMatcher@28f2a10f

第一个 ExposeInvocationInterceptor 对象是 Spring 添加的环绕通知,第二个 MethodBeforeAdviceInterceptor 对象是前置通知转换得到的环绕通知,那 InterceptorAndDynamicMethodMatcher 对象是什么呢?

1
2
3
4
5
6
7
8
9
10
11
12
class InterceptorAndDynamicMethodMatcher {

final MethodInterceptor interceptor;

final MethodMatcher methodMatcher;

public InterceptorAndDynamicMethodMatcher(MethodInterceptor interceptor, MethodMatcher methodMatcher) {
this.interceptor = interceptor;
this.methodMatcher = methodMatcher;
}

}

InterceptorAndDynamicMethodMatcher 并没有实现 MethodInterceptor 接口,它 不是一个环绕通知,对应了动态通知调用。

因此 ProxyFactory 对象的 getInterceptorsAndDynamicInterceptionAdvice() 方法返回的不仅是转换得到的环绕通知,还有对应动态通知调用的 InterceptorAndDynamicMethodMatcher 对象。

InterceptorAndDynamicMethodMatcher 对象中包含了环绕通知 interceptor 对象和切点信息 methodMatcher(前文使用过的 AspectJExpressionPointcut 也实现了 MethodMatcher 接口)。

尝试查看 InterceptorAndDynamicMethodMatcher 对象中包含的信息,但该类并未声明成 public,其成员变量也未被 public 修饰,也没提供获取的方式,但可以使用反射:

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
public static void main(String[] args) throws Throwable {
// --snip--

for (Object o : interceptorList) {
showDetail(o);
}
}

public static void showDetail(Object o) {
try {
Class<?> clazz = Class.forName("org.springframework.aop.framework.InterceptorAndDynamicMethodMatcher");
if (clazz.isInstance(o)) {
Field methodMatcher = clazz.getDeclaredField("methodMatcher");
methodMatcher.setAccessible(true);
Field methodInterceptor = clazz.getDeclaredField("interceptor");
methodInterceptor.setAccessible(true);
System.out.println("环绕通知和切点:" + o);
System.out.println("\t切点为:" + methodMatcher.get(o));
System.out.println("\t通知为:" + methodInterceptor.get(o));
} else {
System.out.println("普通环绕通知:" + o);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}

运行 main() 方法后,控制台打印出:

普通环绕通知:org.springframework.aop.interceptor.ExposeInvocationInterceptor@73e22a3d
普通环绕通知:org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor@47faa49c
环绕通知和切点:org.springframework.aop.framework.InterceptorAndDynamicMethodMatcher@f736069
	切点为:AspectJExpressionPointcut: (int x) execution(* foo(..)) && args(x)
	通知为:org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor@6da21078

根据打印的切点信息可知,InterceptorAndDynamicMethodMatcher 对象的确对应了动态通知调用。

最后创建调用链对象,执行通知和原始方法:

1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String[] args) throws Throwable {
// --snip--

System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>");
Object proxy = factory.getProxy();
MethodInvocation methodInvocation = new ReflectiveMethodInvocation(
proxy, target, Target.class.getMethod("foo", int.class), new Object[]{100}, Target.class, interceptorList
) {
};

methodInvocation.proceed();
}
>>>>>>>>>>>>>>>>>>>>>>>>>>
before1
before(100)
target foo(100)    

动态通知调用需要切点信息,需要对参数进行匹配和绑定,复杂程度高,性能比静态通知调用低。