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
@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
@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();
}
}

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

1
2
3
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 编译,即:
image.png

编译之后查看生成的 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();
}
}
1
2
3
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();
}
}
1
2
di.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 进行设置,勾选 自动构建项目:
image.png

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

Agent 类加载

使用 Agent 类加载

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

一个 Service 类:

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

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

1
2
3
4
5
ndi.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:
image.png

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

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

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

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

JDK 代理

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
35
36
37
38
39
40
/**
* @version v1.0
* @ClassName: JdkProxyDemo
* @Description:
* @Author: 呆大王
* @Date: 2024/8/19
*/
public class JdkProxyDemo {
interface foo {
void foo();
}

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

public static void main(String[] args) {

FooImpl foo = new FooImpl();

foo proxy = (foo) Proxy.newProxyInstance(JdkProxyDemo.class.getClassLoader(), new Class[]{foo.class}, (proxy1, method, args1) -> {
System.out.println("before");
/// 目标.方法(参数) --> 方法.invoke(目标, 参数)
Object result = method.invoke(foo, args1);
System.out.println("after...");
return result;
});

proxy.foo();
System.out.println("-------");
proxy.toString();
System.out.println("-------");
proxy.hashCode();
System.out.println("-------");
System.out.println(proxy.equals(proxy)); }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
before
foo
after...
-------
before
after...
-------
before
after...
-------
before
after...
false

总结

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

JDK 动态代理原理

jdk 代理基于接口,底层是生成一个增强的 class
那么我们自定义类 $Proxy0实现代理接口 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
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();
}

可是 实际的 代理内容里的 功能增强 和 调用目标 是不确定的,
针对这种“不确定”的实现,可以提供一个抽象类,等到用户具体使用时才实现抽象类,重写抽象方法。创建抽象类 MyInvocationHandler

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();
}
1
2
before...
target foo

多个抽象方法的接口

是不是有点眉目了,别急别急如果代理接口 Foo 里多个抽象方法,那么会出现问题:

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

void boo();
}

目标类代理类都新增 boo 方法

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 boo() {
System.out.println("target boo");
}
}

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 boo() {
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.boo();
}
1
2
3
4
before...
target foo
before...
target foo

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

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

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

那么可以在 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 boo() {
Method method = A13.Foo.class.getMethod("boo");
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.boo();
}
1
2
3
4
before...
target foo
before...
target boo

异常的抛出处理

jdk 分 运行异常 和 检查异常
在代理类里,运行异常 直接抛出
而检查异常创建UndeclaredThrowableException 后抛出

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

// --snip--

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

有返回值的抽象方法

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

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

int boo();
}

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

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 boo() {
System.out.println("target bar");
return 100;
}
}

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

InvocationHandler 的 invoke() 方法是对“功能增强”和“调用目标”的抽象,因此可以使 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 boo() {
try {
Method boo = 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() 方法,打印 boo() 方法的返回值:

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.boo());
}
1
2
3
4
5
before...
target foo
before...
target boo
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
43

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 boo() {
try {
return (int) h.invoke(boo, new Object[0]);
} catch (RuntimeException | Error e) {
throw e;
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}

static Method foo;
static Method boo;

static {
try {
foo = A13.Foo.class.getMethod("foo");
bar = A13.Foo.class.getMethod("boo");
} 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.boo());
}

向 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--
}

JDK 代理类字节码生成

JDK 生成代理类,没有经历源码阶段、编译阶段,而是直接字节码阶段,使用了 ASM 完成
ASM 的学习成本较高,在此不做过多介绍,本节将采用一直“曲线求国”的方式,使用 IDEA 的 ASM Bytecode Viewer 插件将 Java 源码转换成使用 ASM 编写的代码。
ASM Bytecode Viewer插件建议在 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 Viewer 浏览当前类对应的字节码信息:
image.png
查看 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
package com.example.asm;


import org.springframework.asm.*;

public class $Proxy0Dump implements Opcodes {

public static byte[] dump() throws Exception {

ClassWriter classWriter = new ClassWriter(0);
FieldVisitor fieldVisitor;
RecordComponentVisitor recordComponentVisitor;
MethodVisitor methodVisitor;
AnnotationVisitor annotationVisitor0;

classWriter.visit(V1_8, ACC_PUBLIC | ACC_SUPER, "com/example/asm/$Proxy0", null, "java/lang/reflect/Proxy", new String[]{"com/example/asm/Foo"});

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

{
fieldVisitor = classWriter.visitField(ACC_STATIC, "foo", "Ljava/lang/reflect/Method;", null, null);
fieldVisitor.visitEnd();
}
{
methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "(Ljava/lang/reflect/InvocationHandler;)V", null, null);
methodVisitor.visitParameter("invocationHandler", 0);
methodVisitor.visitCode();
Label label0 = new Label();
methodVisitor.visitLabel(label0);
methodVisitor.visitLineNumber(18, label0);
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitVarInsn(ALOAD, 1);
methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/reflect/Proxy", "<init>", "(Ljava/lang/reflect/InvocationHandler;)V", false);
Label label1 = new Label();
methodVisitor.visitLabel(label1);
methodVisitor.visitLineNumber(19, label1);
methodVisitor.visitInsn(RETURN);
Label label2 = new Label();
methodVisitor.visitLabel(label2);
methodVisitor.visitLocalVariable("this", "Lcom/example/asm/$Proxy0;", null, label0, label2, 0);
methodVisitor.visitLocalVariable("invocationHandler", "Ljava/lang/reflect/InvocationHandler;", null, label0, label2, 1);
methodVisitor.visitMaxs(2, 2);
methodVisitor.visitEnd();
}
{
methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "foo", "()V", null, null);
methodVisitor.visitCode();
Label label0 = new Label();
Label label1 = new Label();
Label label2 = new Label();
methodVisitor.visitTryCatchBlock(label0, label1, label2, "java/lang/Throwable");
methodVisitor.visitLabel(label0);
methodVisitor.visitLineNumber(23, label0);
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitFieldInsn(GETFIELD, "com/example/asm/$Proxy0", "h", "Ljava/lang/reflect/InvocationHandler;");
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitFieldInsn(GETSTATIC, "com/example/asm/$Proxy0", "foo", "Ljava/lang/reflect/Method;");
methodVisitor.visitInsn(ACONST_NULL);
methodVisitor.visitMethodInsn(INVOKEINTERFACE, "java/lang/reflect/InvocationHandler", "invoke", "(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;", true);
methodVisitor.visitInsn(POP);
methodVisitor.visitLabel(label1);
methodVisitor.visitLineNumber(26, label1);
Label label3 = new Label();
methodVisitor.visitJumpInsn(GOTO, label3);
methodVisitor.visitLabel(label2);
methodVisitor.visitLineNumber(24, label2);
methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[]{"java/lang/Throwable"});
methodVisitor.visitVarInsn(ASTORE, 1);
Label label4 = new Label();
methodVisitor.visitLabel(label4);
methodVisitor.visitLineNumber(25, label4);
methodVisitor.visitTypeInsn(NEW, "java/lang/reflect/UndeclaredThrowableException");
methodVisitor.visitInsn(DUP);
methodVisitor.visitVarInsn(ALOAD, 1);
methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/reflect/UndeclaredThrowableException", "<init>", "(Ljava/lang/Throwable;)V", false);
methodVisitor.visitInsn(ATHROW);
methodVisitor.visitLabel(label3);
methodVisitor.visitLineNumber(27, label3);
methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
methodVisitor.visitInsn(RETURN);
Label label5 = new Label();
methodVisitor.visitLabel(label5);
methodVisitor.visitLocalVariable("e", "Ljava/lang/Throwable;", null, label4, label3, 1);
methodVisitor.visitLocalVariable("this", "Lcom/example/asm/$Proxy0;", null, label0, label5, 0);
methodVisitor.visitMaxs(4, 2);
methodVisitor.visitEnd();
}
{
methodVisitor = classWriter.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
methodVisitor.visitCode();
Label label0 = new Label();
Label label1 = new Label();
Label label2 = new Label();
methodVisitor.visitTryCatchBlock(label0, label1, label2, "java/lang/NoSuchMethodException");
methodVisitor.visitLabel(label0);
methodVisitor.visitLineNumber(32, label0);
methodVisitor.visitLdcInsn(Type.getType("Lcom/example/asm/Foo;"));
methodVisitor.visitLdcInsn("foo");
methodVisitor.visitInsn(ICONST_0);
methodVisitor.visitTypeInsn(ANEWARRAY, "java/lang/Class");
methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getMethod", "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;", false);
methodVisitor.visitFieldInsn(PUTSTATIC, "com/example/asm/$Proxy0", "foo", "Ljava/lang/reflect/Method;");
methodVisitor.visitLabel(label1);
methodVisitor.visitLineNumber(35, label1);
Label label3 = new Label();
methodVisitor.visitJumpInsn(GOTO, label3);
methodVisitor.visitLabel(label2);
methodVisitor.visitLineNumber(33, label2);
methodVisitor.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[]{"java/lang/NoSuchMethodException"});
methodVisitor.visitVarInsn(ASTORE, 0);
Label label4 = new Label();
methodVisitor.visitLabel(label4);
methodVisitor.visitLineNumber(34, label4);
methodVisitor.visitTypeInsn(NEW, "java/lang/NoSuchMethodError");
methodVisitor.visitInsn(DUP);
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/NoSuchMethodException", "getMessage", "()Ljava/lang/String;", false);
methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/NoSuchMethodError", "<init>", "(Ljava/lang/String;)V", false);
methodVisitor.visitInsn(ATHROW);
methodVisitor.visitLabel(label3);
methodVisitor.visitLineNumber(36, label3);
methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
methodVisitor.visitInsn(RETURN);
methodVisitor.visitLocalVariable("e", "Ljava/lang/NoSuchMethodException;", null, label4, label3, 0);
methodVisitor.visitMaxs(3, 1);
methodVisitor.visitEnd();
}
classWriter.visitEnd();

return classWriter.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。
工作目录查看方式:
image.png

也就是说会在 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
21
22
23
24
25
26
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();

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

Class<?> proxyClass = classLoader.loadClass("com.example.asm.$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();
}
}
1
2
before...
模拟调用目标

JDK 反射优化

jdk 动态代理时,会使用反射调用方法:
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
35
36
37
38
39
40
41
42
43
44
45
package com.example.asm;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

/**
* @version v1.0
* @ClassName: TestMethodProxy
* @Description:
* @Author: 呆大王
* @Date: 2024/8/29
*/
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 {
// 在虚拟机选择配置里添加如下,使用jdk17
// --add-opens java.base/java.lang.reflect=ALL-UNNAMED
// --add-opens java.base/jdk.internal.reflect=ALL-UNNAMED
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("jdk.internal.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");
}
}

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
已连接到地址为 ''127.0.0.1:63163',传输: '套接字'' 的目标虚拟机
1: null
1: foo
2: jdk.internal.reflect.NativeMethodAccessorImpl@5f184fc6
2: foo
3: jdk.internal.reflect.NativeMethodAccessorImpl@5f184fc6
3: foo
4: jdk.internal.reflect.NativeMethodAccessorImpl@5f184fc6
4: foo
5: jdk.internal.reflect.NativeMethodAccessorImpl@5f184fc6
5: foo
6: jdk.internal.reflect.NativeMethodAccessorImpl@5f184fc6
6: foo
7: jdk.internal.reflect.NativeMethodAccessorImpl@5f184fc6
7: foo
8: jdk.internal.reflect.NativeMethodAccessorImpl@5f184fc6
8: foo
9: jdk.internal.reflect.NativeMethodAccessorImpl@5f184fc6
9: foo
10: jdk.internal.reflect.NativeMethodAccessorImpl@5f184fc6
10: foo
11: jdk.internal.reflect.NativeMethodAccessorImpl@5f184fc6
11: foo
12: jdk.internal.reflect.NativeMethodAccessorImpl@5f184fc6
12: foo
13: jdk.internal.reflect.NativeMethodAccessorImpl@5f184fc6
13: foo
14: jdk.internal.reflect.NativeMethodAccessorImpl@5f184fc6
14: foo
15: jdk.internal.reflect.NativeMethodAccessorImpl@5f184fc6
15: foo
16: jdk.internal.reflect.NativeMethodAccessorImpl@5f184fc6
16: foo
17: jdk.internal.reflect.GeneratedMethodAccessor2@312b1dae
17: foo

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

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

使用 Arthas 反编译查看 GeneratedMethodAccessor2 类中的信息,
内容如下:
image.png
在反编译得到的代码中,不再是通过反射调用方法,而是直接正常调用方法,即:

1
TestMethodProxy.foo((int)c);

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

CGLib 代理

MethodProxy 原理

JDK 和 CGLib 的统一

advisor

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

Spring 中的切点、通知、切面的抽象如下:
image.png

  • 切点:Ponintcut,其典型实现是 AspectJExpressionPonintcut
  • 通知:Advice,其典型子类接口是 MethodInterceptor ,表示环绕通知
  • 切面:Advisor,仅包含一个切点和通知

切面与代理对象的创建

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

  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();
}
1
2
3
4
5
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());
1
2
3
4
5
class indi.mofan.a15.$Proxy0
before...
target1 foo
after...
target1 bar

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

再设置 factory 对象的 proxyTargetClass 为 true:

1
factory.setProxyTargetClass(true);
1
2
3
4
5
class indi.mofan.a15.A15$Target1$$EnhancerBySpringCGLIB$$34c2d9b8
before...
target1 foo
after...
target1 bar

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

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();
}
1
2
3
4
5
class indi.mofan.a15.A15$Target2$$EnhancerBySpringCGLIB$$4bb2ac74
before...
target2 foo
after...
target2 bar

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

  • JdkDynamicAopProxy
  • ObjenesisCglibAopProxy

image.png

AopProxyFactory 根据 proxyTargetClass 等设置选择 AopProxy 实现,AopProxy 通过 getProxy() 方法创建代理对象。
上述类图中的类与接口都实现了 Advised 接口,能够获得关联的切面集合与目标(实际上是从 ProxyFactory 中获取的)。

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

切点匹配的方法

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() {

}
}
1
2
3
4
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() {

}
}
1
2
3
4
true
false
true
true

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

从@Aspect 到 Advisor

@Aspect 是高级切面,Advisor 是低级切面,在运行时会将高级转为低级。

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

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

AnnotationAwareAspectAutoProxyCreator

准备如下类

两个目标类

一个使用@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
51
52
53
54
55
56
57
58
59
60
61
/**
* @version v1.0
* @ClassName: A17
* @Description:
* @Author: 呆大王
* @Date: 2024/8/29
*/
public class A17 {
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 MyAspect {

@Before("execution(* com.example.aop.a17.A17.Target1.foo())")
public void before() {
System.out.println("aspect1 before...");
}

@After("execution(* com.example.aop.a17.A17.Target1.foo())")
public void after() {
System.out.println("aspect1 after...");
}
}

@Configuration
static class Config {
/**
* 低级切面,由一个切点和一个通知组成
*/
@Bean
public Advisor advisor() {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(* com.example.aop.a17.A17.Target1.foo())");

MethodInterceptor adivce3 = invocation -> {
System.out.println("adivce3 before");
Object result = invocation.proceed();
System.out.println("adivce3 after");
return result;
};

DefaultPointcutAdvisor defaultPointcutAdvisor = new DefaultPointcutAdvisor();
defaultPointcutAdvisor.setPointcut(pointcut);
defaultPointcutAdvisor.setAdvice(adivce3);
return defaultPointcutAdvisor;
}
}
}

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

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

context.refresh();
for (String beanDefinitionName : context.getBeanDefinitionNames()) {
System.out.println(beanDefinitionName);
}
}
1
2
3
4
com.example.aop.a17.A17$Target1
com.example.aop.a17.A17$Config
org.springframework.context.annotation.ConfigurationClassPostProcessor
advisor

Spring 中存在名为 AnnotationAwareAspectJAutoProxyCreator 的 Bean 后置处理器(虽然这个类后缀没有 BeanPostProcessor,但是他实现了BeanPostProcessor)

AnnotationAwareAspectJAutoProxyCreator 有两个作用:

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

AnnotationAwareAspectJAutoProxyCreator 实现了BeanPostProcessor,可以在 Bean 的生命周期一些阶段对 Bean 扩展。可以在** 依赖注入之前**** *或者 Bean 初始化之后* **对 Bean 扩展。

AnnotationAwareAspectJAutoProxyCreator 有两个重要的方法:

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

findEligibleAdvisors()

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean(MyAspect.class);
context.registerBean(Config.class);
context.registerBean(ConfigurationClassPostProcessor.class);
context.registerBean(AnnotationAwareAspectJAutoProxyCreator.class);

context.refresh();
for (String beanDefinitionName : context.getBeanDefinitionNames()) {
// System.out.println(beanDefinitionName);
}

/**
* findEligibleAdvisors() 找到符合条件的Advisor
*
* 1. 符合条件的Advisor一部分是低级的,如下列中的Config里的advisor
* 2. 符合条件的Advisor一部分是高级的,如下列中的MyAspect里的@Aspect
*/
AnnotationAwareAspectJAutoProxyCreator creator = context.getBean(AnnotationAwareAspectJAutoProxyCreator.class);
List<Advisor> advisors = creator.findEligibleAdvisors(Target1.class, "target1");
for (Advisor advisor : advisors) {
System.out.println(advisor);
}
}
1
2
3
4
org.springframework.aop.interceptor.ExposeInvocationInterceptor.ADVISOR
InstantiationModelAwarePointcutAdvisor: expression [execution(* foo())]; advice method [public void org.springframework.aop.framework.autoproxy.A17$MyAspect.before()]; perClauseKind=SINGLETON
InstantiationModelAwarePointcutAdvisor: expression [execution(* foo())]; advice method [public void org.springframework.aop.framework.autoproxy.A17$MyAspect.after()]; perClauseKind=SINGLETON
org.springframework.aop.support.DefaultPointcutAdvisor: pointcut [AspectJExpressionPointcut: () execution(* org.springframework.aop.framework.autoproxy.A17.Target1.foo())]; advice [org.springframework.aop.framework.autoproxy.A17$Config$$Lambda$118/0x00000249b01134c8@264f218]

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

  • 第一个切面 ExposeInvocationInterceptor.ADVISOR是 Spring 为每个代理对象都会添加的切面;
  • 第二个切面 DefaultPointcutAdvisor 是自行编写的低级切面(Config 类里的);
  • 第三个和第四个切面 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();
}
1
2
class org.springframework.aop.framework.autoproxy.A17$Target1$$SpringCGLIB$$0
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();
}
1
2
3
4
5
adivce3 before
aspect1 before...
target1 foo
aspect1 after...
adivce3 after

其中 adivce3 before adivce3 after 是低级切面的增强,aspect1 before… aspect1 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 注解是无法生效的。

针对低级切面,需要设置 advisor 的 order 值,而不是向高级切面那样使用 @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() 方法验证执行顺序是否改变:

1
2
3
4
5
aspect1 before...
adivce3 before
target1 foo
adivce3 after
aspect1 after...

代理对象创建时机

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

  • 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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
package org.springframework.aop.framework.autoproxy;

import jakarta.annotation.PostConstruct;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.CommonAnnotationBeanPostProcessor;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ConfigurationClassPostProcessor;
import org.springframework.context.support.GenericApplicationContext;

/**
* @version v1.0
* @ClassName: A17_1代理创建的时机
* @Description: 代理创建的时机
* @Author: 呆大王
* @Date: 2024/9/2
*/
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 static AnnotationAwareAspectJAutoProxyCreator annotationAwareAspectJAutoProxyCreator() {
return new AnnotationAwareAspectJAutoProxyCreator();
}

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

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

@Bean
public static Advisor advisor() {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(* foo())");

MethodInterceptor interceptor = new MethodInterceptor() {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("before");
return invocation.proceed();
}
};

return new DefaultPointcutAdvisor(pointcut, interceptor);
}

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

@Bean
public static 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()");
}
}
}
1
2
3
4
5
6
7
8
9
10
11
已连接到地址为 ''127.0.0.1:53708',传输: '套接字'' 的目标虚拟机
[TRACE] 09:06:24.481 [main] o.s.a.a.a.AnnotationAwareAspectJAutoProxyCreator - Did not attempt to auto-proxy infrastructure class [org.springframework.aop.Advisor]
Bean1()
Bean1 init()
AnnotationAwareAspectJAutoProxyCreator - 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$$SpringCGLIB$$0
Bean2 init()
已与地址为 ''127.0.0.1:53708',传输: '套接字'' 的目标虚拟机断开连接

进程已结束,退出代码为 0

bean1 初始化后,打印了

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

表示 bean1 创建了隐式代理。

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


Bean 初始化完成之后

现在改变 bean1,添加 setBean2() 方法,表示向 bean1 中注入 bean2,

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()");
}
}

此时出现循环依赖。

1
2
3
4
5
6
7
Bean1()
Bean2()
AnnotationAwareAspectJAutoProxyCreator - 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$$SpringCGLIB$$0
Bean2 init()
Bean1 setBean1(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 的依赖注入阶段和初始化阶段不应该被增强,仍应被施加于原始对象。

高级切面转低级切面

调用 AnnotationAwareAspectJAutoProxyCreator 对象的 findEligibleAdvisors() 方法时,

获取能配合目标 Class 使用的切面,最终返回 Advisor 列表。

在搜索过程中如果碰到高级切面 @Aspect 讲其转成低级切面。

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

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
package org.springframework.aop.framework.autoproxy;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Before;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectInstanceFactory;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.aspectj.AspectJMethodBeforeAdvice;
import org.springframework.aop.aspectj.SingletonAspectInstanceFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

/**
* @version v1.0
* @ClassName: A17_2高级切面转低级切面
* @Description:
* @Author: 呆大王
* @Date: 2024/9/2
*/
public class A17_2高级切面转低级切面 {
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 void around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("around...before");
try {
joinPoint.proceed();
} finally {
System.out.println("around...after");
}
}
}

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

public static void main(String[] args) {
//切面对象实例工厂,用于后续反射调用切面中的方法
AspectInstanceFactory singletonAspectInstanceFactory = new SingletonAspectInstanceFactory(new Aspect());
//高级切面转低级切面类
ArrayList<Advisor> advisorArrayList = new ArrayList<>();
for (Method method : Aspect.class.getDeclaredMethods()) {
//这里用@Before注解来演示
if (method.isAnnotationPresent(Before.class)){
//解析切面
String exprssion = method.getAnnotation(Before.class).value();
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression(exprssion);
//通知类。前置通知对应的通知类是 AspectJMethodBeforeAdvice
AspectJMethodBeforeAdvice advice = new AspectJMethodBeforeAdvice(method, pointcut, singletonAspectInstanceFactory);
//切面
DefaultPointcutAdvisor defaultPointcutAdvisor = new DefaultPointcutAdvisor(pointcut, advice);
advisorArrayList.add(defaultPointcutAdvisor);
}
}

for (Advisor advisor : advisorArrayList) {
System.out.println(advisor);
System.out.println();
}
}
}

1
2
3
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 '']

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

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

通知相关最佳与原始通知类对应关系如下:

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

静态通知调用

动态通知调用