Spring四十九讲

BeanFactory和ApplicationContext

什么是BeanFactory

Ctrl+Alt+U 查看类图

BeanFactory是``ApplicationContext的父接口,它才是Spring的核心容器,主要的ApplicationContext` 实现都【组合】了它的功能。

BeanFactory能做什么

进入BeanFactory接口,在IDEA中使用快捷键Ctrl+F12 查看这个接口中的所有方法定义:

从表面上只有getBean()方法,实际上控制反转、基本的依赖注入、乃至Bean的生命周期的各种功能,都由它的实现类提供。

查看 DefaultListableBeanFactory 类的类图:

DefaultListableBeanFactory 实现了 BeanFactory接口,它能管理Spring中所有的Bean,当然也包含Spring容器中的那些单例对象。

DefaultListableBeanFactory 还继承了 DefaultSingletonBeanRegistry 类,这个类就是用来管理 Spring 容器中的单例对象。

在IDEA提供的类图中选中 DefaultSingletonBeanRegistry,然后按下F4 进入这个类。它有一个Map类型的成员变量singletonObjects:

1
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

Map的key就是Bean的名字,而value是对应的Bean,即单例对象。

创建两个类

1
2
3
4
5
6
7
8
@Component
public class ComponentA {
}

@Component
public class ComponentB {
}

查看singletonObjects中是否存在两个bean的信息。

1
2
3
4
5
6
7
8
9
10
11
12
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) throws Exception {
ConfigurableApplicationContext context = SpringApplication.run(DemoApplication.class, args);
Field singletonObjects = DefaultSingletonBeanRegistry.class.getDeclaredField("singletonObjects");
singletonObjects.setAccessible(true);
Map<String, Object> map = (Map<String, Object>) singletonObjects.get(context.getBeanFactory());
map.entrySet().stream()
.filter(entry -> entry.getKey().startsWith("component"))
.forEach(entry -> System.out.println(entry.getKey() + " ==> " + entry.getValue()));
}
}

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

componentA ==> com.atguigu.demo.ComponentA@10c72a6f

componentB ==> com.atguigu.demo.ComponentB@70e94ecb

ApplicationContext的功能

回顾ApplicationContext的类图:

它的拓展功能主要体现在继承的4个父接口上

  • MessageSource:使其具备处理国际化资源的能力
  • ResourcePatternResolver:使其具备使用通配符进行资源匹配的能力
  • EnvironmentCapable:使其具备读取 Spring 环境信息、配置文件信息的能力
  • ApplicationEventPublisher:使其具备发布事件的能力

MessageSource

读取一个Key翻译后的结果

在 SpringBoot 项目的 resources 目录下创建 messages.properties、messages_en.properties、messages_zh_CN.properties三个国际化文件

测试 MessageSource 接口中 getMessage() 方法的使用:

1
2
3
4
5
6
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(DemoApplication.class, args);
System.out.println(context.getMessage("thanks", null, Locale.ENGLISH));
System.out.println(context.getMessage("thanks", null, Locale.SIMPLIFIED_CHINESE));
}

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

Thank you

谢谢

ResourcePatternResolver

1
2
3
4
5
6
7
8
9
10
11
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) throws Exception {
ConfigurableApplicationContext context = SpringApplication.run(DemoApplication.class, args);
Resource[] resource = context.getResources("classpath*:META-INF/spring.factories");
for (Resource r : resource) {
System.out.println(r);
}
}
}

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

URL [jar:file:/C:/Users/Administrator/.m2/repository/org/springframework/boot/spring-boot/2.5.5/spring-boot-2.5.5.jar!/META-INF/spring.factories]

URL [jar:file:/C:/Users/Administrator/.m2/repository/org/springframework/boot/spring-boot-autoconfigure/2.5.5/spring-boot-autoconfigure-2.5.5.jar!/META-INF/spring.factories]

URL [jar:file:/C:/Users/Administrator/.m2/repository/org/springframework/spring-beans/5.3.10/spring-beans-5.3.10.jar!/META-INF/spring.factories]

EnvironmentCapable

1
2
3
4
5
6
7
8
9
10
11
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) throws Exception {
ConfigurableApplicationContext context = SpringApplication.run(DemoApplication.class, args);
//来自系统变量
System.out.println(context.getEnvironment().getProperty("java_home"));
//来自properties文件
System.out.println(context.getEnvironment().getProperty("server.port"));
}
}

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

C:\Program Files\Java\jdk1.8.0_60

8080

ApplicationEventPublisher

定义事件类 RegisteredEvent

1
2
3
4
5
6
public class RegisteredEvent extends ApplicationEvent {

public RegisteredEvent(Object source) {
super(source);
}
}

ComponentA 作为发送事件的 Bean:

1
2
3
4
5
6
7
8
9
10
11
@Component
@Slf4j
public class ComponentA {
@Autowired
private ApplicationEventPublisher context;

public void register() {
log.info("ComponentA 发布注册事件");
context.publishEvent(new RegisteredEvent(this));
}
}

将 ComponentB 作为事件监听器:

1
2
3
4
5
6
7
8
9
@Component
@Slf4j
public class ComponentB {
@EventListener
public void hander(RegisteredEvent event) {
log.info("{}",event);
log.info("ComponentB接收到事件,发送短信");
}
}

main() 方法中使用 ComponentA 发送事件:

1
2
3
4
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(DemoApplication.class, args);
context.getBean(ComponentA.class).register();
}

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

2024-12-11 22:36:05.267 INFO 19872 — [main] com.atguigu.demo.ComponentA : ComponentA 发布注册事件

2024-12-11 22:36:05.267 INFO 19872 — [main] com.atguigu.demo.ComponentB : com.atguigu.demo.RegisteredEvent[source=com.atguigu.demo.ComponentA@33425811]

2024-12-11 22:36:05.268 INFO 19872 — [main] com.atguigu.demo.ComponentB : ComponentB接收到事件,发送短信

容器实现

BeanFactory的实现

现有如下测试类

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
public class TestBeanFactory {

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

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

@Slf4j
static class Bean1 {
public Bean1() {
log.debug("构造 Bean1()");
}

@Autowired
private Bean2 bean2;

public Bean2 getBean2() {
return bean2;
}
}

@Slf4j
static class Bean2 {
public Bean2() {
log.debug("构造 Bean2()");
}
}

}

使用DefaultListableBeanFactory作为beanFactory的实现类,来进行演示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void main(String[] args) {
//beanFactory的一个重要实现
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
//刚创建好时,内部是没有任何的bean的,我们需要给beanFactory中添加一些bean的定义(class、scope、init、destroy)
//并不是添加bean对象,bean对象是由beanFactory控制反转帮我们创建出来的

//创建beanDefinition
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
.genericBeanDefinition(Config.class)
.setScope("singleton")
.getBeanDefinition();
//加入到beanFactory中
beanFactory.registerBeanDefinition("config", beanDefinition);
//查看beanFactory中有哪些beanDefinition,把它们的名字打印出来
for (String beanDefinitionName : beanFactory.getBeanDefinitionNames()) {
System.out.println(beanDefinitionName);
}
}

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

config

现在 Bean 工厂中 有且仅有一个 名为 config 的 Bean。

但我们的配置类明明加了@Configuration@Bean 两个注解,那么Bean 工厂中应该还存在 bean1bean2啊,那为什么现在没有呢?

很明显 BeanFactory 缺少了解析 @Configuration@Bean 两个注解的能力。

1
2
3
4
5
public static void main(String[] args) {
// 给 BeanFactory 添加一些常用的后置处理器
AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);
Arrays.stream(beanFactory.getBeanDefinitionNames()).forEach(System.out::println);
}

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

config

org.springframework.context.annotation.internalConfigurationAnnotationProcessor

org.springframework.context.annotation.internalAutowiredAnnotationProcessor

org.springframework.context.annotation.internalCommonAnnotationProcessor

org.springframework.context.event.internalEventListenerProcessor

org.springframework.context.event.internalEventListenerFactory

观察控制台输出,我们可以看到有一个名为:

org.springframework.context.annotation.internalConfigurationAnnotationProcessor的Bean,根据其所含的 ConfigurationAnnotationProcessor 字样,可以知道这个 Bean 就是用来处理 @Configuration@Bean 注解的,将配置类中定义的 Bean 信息补充到 BeanFactory 中。

但现在已经加入Bean工厂中了,为啥依旧没有 bean1bean2 呢?

现在仅仅是将处理器添加到了 Bean 工厂,还没有使用处理器。

internalConfigurationAnnotationProcessor 这样的 Bean,都有一个共同的类型,名为 BeanFactoryPostProcessor,因此可以我们拿到所有的后置处理器,逐一执行。

1
2
3
4
5
6
7
8
 beanFactory.getBeansOfType(BeanFactoryPostProcessor.class)
.values()
.stream()
.forEach(beanFactoryPostProcessor -> beanFactoryPostProcessor.postProcessBeanFactory(beanFactory));
//再次打印
for (String beanDefinitionName : beanFactory.getBeanDefinitionNames()) {
System.out.println(beanDefinitionName);
}

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

config

org.springframework.context.annotation.internalConfigurationAnnotationProcessor

org.springframework.context.annotation.internalAutowiredAnnotationProcessor

org.springframework.context.annotation.internalCommonAnnotationProcessor

org.springframework.context.event.internalEventListenerProcessor

org.springframework.context.event.internalEventListenerFactory

bean1

bean2

由此可见,beanFactory的原始功能并没有那么丰富,它的一些拓展功能是由一些后置处理器来完成的。

bean1bean2已经被补充到bean工厂中了,那我们可以使用吗?

1
2
//观察bean1中是不是成功依赖注入了bean2
System.out.println(beanFactory.getBean(Bean1.class).getBean2());

org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean ‘bean1’

org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean ‘config’

com.atguigu.demo.TestBeanFactory$Bean1 - 构造 Bean1()

null

控制台输出为null,bean2没有注入到bean1

beanFactory依赖注入的功能也没有…

我们刚才用到的是其中一种后置处理器,是针对beanFactory的后置处理器,作用是补充beanDefination,去解析这些@Bean@Configuration,但如果要依赖注入的话,还有一些叫bean的后置处理器,由它来去解析@Autowired

在先前添加到 BeanFactory 中的后置处理器里,有名为 internalAutowiredAnnotationProcessorinternalCommonAnnotationProcessor 的两个后置处理器。前者用于解析 @Autowired 注解,后者用于解析 @Resource 注解,这种我们称之为bean的后置处理器,它们会针对bean生命周期的各个阶段,提供一些拓展功能,它们都有一个共同的类型BeanPostProcessor,因此可以:

1
2
3
4
5
6
7
public static void main(String[] args) {
System.out.println("---------------------------------------------");
// Bean 后置处理器,对 Bean 的生命周期的各个阶段提供拓展,例如 @AutoWired @Resource...
beanFactory.getBeansOfType(BeanPostProcessor.class).values().forEach(beanFactory::addBeanPostProcessor);
System.out.println(beanFactory.getBean(Bean1.class).getBean2());
}

org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean ‘bean1’

org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean ‘config’

com.atguigu.demo.TestBeanFactory$Bean1 - 构造 Bean1()

org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean ‘bean2’

com.atguigu.demo.TestBeanFactory$Bean2 - 构造 Bean2()

com.atguigu.demo.TestBeanFactory$Bean2@31304f14

建立 BeanPostProcessorBeanFactory的关系后,bean2 被成功注入到 bean1 中了。

1
2
3
4
5
6
7
//只是将后置处理器添加到beanFactory中(后置处理器只是存在于beanFactory中的一个bean而已)
AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);
//调用(beanFactory::addBeanPostProcessor)建立 BeanPostProcessor 和 BeanFactory 的联系,
//将来我这个beanFactory中每个bean创建时需要哪些后置处理器
beanFactory.getBeansOfType(BeanPostProcessor.class)
.values()
.forEach(beanFactory::addBeanPostProcessor);

我们发现:刚开始beanFactory中都是一些BeanDefination,并没有去实例化、初始化,仅仅是保存了bean的描述信息在bean工厂中,当我们第一次调用getBean()时,才会去创建bean,即延时创建。

那有没有什么办法预先就初始化好单例对象呢?

1
2
3
4
5
6
7
8
9
public static void main(String[] args) {

//准备好所有单例
beanFactory.preInstantiateSingletons();
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
beanFactory.getBeansOfType(BeanPostProcessor.class).values().forEach(beanFactory::addBeanPostProcessor);
System.out.println(beanFactory.getBean(Bean1.class).getBean2());
}

org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean ‘org.springframework.context.annotation.internalAutowiredAnnotationProcessor’
org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean ‘org.springframework.context.annotation.internalCommonAnnotationProcessor’
org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean ‘config’
org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean ‘bean1’
com.atguigu.demo.TestBeanFactory$Bean1 - 构造 Bean1()
org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean ‘bean2’
com.atguigu.demo.TestBeanFactory$Bean2 - 构造 Bean2()

com.atguigu.demo.TestBeanFactory$Bean2@76505305

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
public class TestBeanFactory {
@Configuration
static class Config {
@Bean
public Bean1 bean1() {
return new Bean1();
}

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

@Bean
public Bean3 bean3() {
return new Bean3();
}

@Bean
public Bean4 bean4() {
return new Bean4();
}
}

interface Inter {

}

@Slf4j
static class Bean1 {
public Bean1() {
log.debug("构造 Bean1()");
}

@Autowired
private Bean2 bean2;

@Autowired //根据类型注入,同类型的bean有多个,则通过成员变量名和bean的名称匹配,注入bean3
@Resource(name = "bean4")//根据名称注入bean4
private Inter bean3;

private Inter getInter() {
return bean3;
}

public Bean2 getBean2() {
return bean2;
}
}

@Slf4j
static class Bean2 {
public Bean2() {
log.debug("构造 Bean2()");
}
}

@Slf4j
static class Bean3 implements Inter {
public Bean3() {
log.debug("构造 Bean3()");
}
}

@Slf4j
static class Bean4 implements Inter {
public Bean4() {
log.debug("构造 Bean4()");
}
}
}

向 Bean 工厂中添加了 bean3bean4,并且在 bean1 中注入 Inter 类型的 Bean。

现在 Bean 工厂中Inter 类型的 Bean 有两个,分别是 bean3bean4,那么会注入哪一个呢?

如果只使用 @Autowired,首先会按照类型注入,如果同类型的 Bean 有多个,再按照变量名称注入,如果再注入失败,就报错;如果只使用 @Resource,首先会按照name进行注入,同名的Bean有多个,再按照类型注入,否则就报错。

那如果即使用 @Autowired 又使用 @Resource(name = “bean4”) 呢?

1
2
3
4
public static void main(String[] args) {
System.out.println(beanFactory.getBean(Bean1.class).getInter());
}

com.atguigu.demo.TestBeanFactory$Bean3@175c2241

根据打印的结果可知,@Autowired 生效了,这是因为internalAutowiredAnnotationProcessor 排在 internalCommonAnnotationProcessor 之前。我们可以打印出它们的顺序:

1
2
3
4
5
6
beanFactory.getBeansOfType(BeanPostProcessor.class)
.values()
.forEach(beanPostProcessor -> {
System.out.println(">>>>>>>>" + beanPostProcessor);
beanFactory.addBeanPostProcessor(beanPostProcessor);
});

org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor@2e005c4b

org.springframework.context.annotation.CommonAnnotationBeanPostProcessor@4567f35d

然后我们改变它们的顺序

1
2
3
4
5
6
7
8
beanFactory.getBeansOfType(BeanPostProcessor.class)
.values()
.stream()
.sorted(beanFactory.getDependencyComparator())
.forEach(beanPostProcessor -> {
System.out.println(">>>>>>>>" + beanPostProcessor);
beanFactory.addBeanPostProcessor(beanPostProcessor);
});

org.springframework.context.annotation.CommonAnnotationBeanPostProcessor@4f18837a

org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor@359f7cdf

com.atguigu.demo.TestBeanFactory$Bean4@62379589

结果注入的是bean4

为什么使用beanFactory.getDependencyComparator() 后就改变了 BeanPostProcessor 的先后顺序呢?

我们在使用AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory)工具类时,翻看源码可以发现

1
2
3
4
5
6
7
8
9
10
11
12
13
public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(BeanDefinitionRegistry registry, @Nullable Object source) {
DefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry);
if (beanFactory != null) {
if (!(beanFactory.getDependencyComparator() instanceof AnnotationAwareOrderComparator)) {
//设置比较器对象
beanFactory.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
}

if (!(beanFactory.getAutowireCandidateResolver() instanceof ContextAnnotationAutowireCandidateResolver)) {
beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
}
}
}

比较器是根据后置处理器的getOrder(),返回值,从小到大进行排序。

AutowiredAnnotationBeanPostProcessor 设置的 order 是:

1
private int order = Ordered.LOWEST_PRECEDENCE - 2;

CommonAnnotationBeanPostProcessor 设置的 order 是:

1
2
3
4
public CommonAnnotationBeanPostProcessor() {
setOrder(Ordered.LOWEST_PRECEDENCE - 3);
}

因此当设置了AnnotationAwareOrderComparator 比较器后,CommonAnnotationBeanPostProcessor 排在更前面,@Resource 就先生效。

==总结==

  • beanFactory 可以通过 registerBeanDefinition 注册一个 bean definition 对象
    • 我们平时使用的配置类、xml、组件扫描等方式都是生成 bean definition 对象注册到 beanFactory 当中
    • bean definition 描述了这个 bean 的创建蓝图:scope 是什么、用构造还是工厂创建、初始化销毁方法是什么,等等
  • beanFactory 需要手动调用 beanFactory 后处理器对它做增强
    • 例如通过解析 @Bean、@ComponentScan 等注解,来补充一些 bean definition
  • beanFactory 需要手动添加 bean 后处理器,以便对后续 bean 的创建过程提供增强
    • 例如 @Autowired@Resource 等注解的解析都是 bean 后处理器完成的
    • bean 后处理的添加顺序会对解析结果有影响,见上文中同时加 @Autowired@Resource 的例子
  • beanFactory 需要手动调用方法来初始化单例
  • beanFactory 需要额外设置才能解析 ${} 与 #{}

ApplicationContext的实现

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

static class Bean1 {

}

static class Bean2 {
private Bean1 bean1;

public void setBean1(Bean1 bean1) {
this.bean1 = bean1;
}

public Bean1 getBean1() {
return bean1;
}
}

}

ClassPathXmlApplicationContext

较为经典的容器,基于classpath 下的 xml 格式的配置文件 来创建

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">


<bean id="bean1" class="com.atguigu.demo.A02Application.Bean1"></bean>


<bean id="bean2" class="com.atguigu.demo.A02Application.Bean2">
<property name="bean1" ref="bean1"></property>
</bean>
</beans>
1
2
3
4
5
6
7
8
private static void testClassPathXmlApplicationContext() {
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("bean1.xml");
for (String beanDefinitionName : context.getBeanDefinitionNames()) {
System.out.println(beanDefinitionName);
}
System.out.println(context.getBean(Bean2.class).getBean1());
}

bean1

bean2

com.atguigu.demo.A02Application$Bean1@6f1de4c7

FileSystemXmlApplicationContext

基于磁盘路径下 xml 格式的配置文件来创建。

1
2
3
4
5
6
7
8
9
private static void testFileSystemXmlApplicationContext() {
//使用相对路径时,以模块为起点(IDEA 中需要设置 Working directory),也支持绝对路径
FileSystemXmlApplicationContext context = new FileSystemXmlApplicationContext("D:\\EdgeDownload\\framwork\\demo\\src\\main\\resources\\bean1.xml");
for (String beanDefinitionName : context.getBeanDefinitionNames()) {
System.out.println(beanDefinitionName);
}
System.out.println(context.getBean(Bean2.class).getBean1());
}

bean1

bean2

com.atguigu.demo.A02Application$Bean1@128d2484

ClassPathXmlApplicationContextFileSystemXmlApplicationContext 都依赖于从 XML 文件中读取 Bean 的信息,而这都利用了 XmlBeanDefinitionReader 进行读取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 public static void main(String[] args) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
System.out.println("读取之前");
for (String beanDefinitionName : beanFactory.getBeanDefinitionNames()) {
System.out.println(beanDefinitionName);
}
System.out.println("读取之后");
XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
xmlBeanDefinitionReader.loadBeanDefinitions(new ClassPathResource("bean1.xml"));
// xmlBeanDefinitionReader.loadBeanDefinitions(new FileSystemResource("D:\\EdgeDownload\\framwork\\demo\\src\\main\\resources\\bean1.xml"));
for (String beanDefinitionName : beanFactory.getBeanDefinitionNames()) {
System.out.println(beanDefinitionName);
}
}

读取之前

读取之后

org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loaded 2 bean definitions from class path resource [bean1.xml]

bean1

bean2

AnnotationConfigApplicationContext

基于java配置类来创建。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private static void testAnnotationConfigApplicationContext() {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(Config.class);
for (String beanDefinitionName : context.getBeanDefinitionNames()) {
System.out.println(beanDefinitionName);
}
System.out.println(context.getBean(Bean2.class).getBean1());
}

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

@Bean
public Bean2 bean2(Bean1 bean1) {
Bean2 bean2 = new Bean2();
bean2.setBean1(bean1);
return bean2;
}
}
1
2
3
4
public class A02Application {
public static void main(String[] args) {
testAnnotationConfigApplicationContext();
}

org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
a02Application.Config
bean1
bean2
com.atguigu.demo.A02Application$Bean1@7748410a

与前面两种基于Xml创建ApplicationContext的方式相比,AnnotationConfigApplicationContext自动帮我们添加了一些常用的后置处理器。

如果想在基于Xml的方式中也添加上这些Bean,可以在Xml中进行配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">


<bean id="bean1" class="com.atguigu.demo.A02Application.Bean1"></bean>


<bean id="bean2" class="com.atguigu.demo.A02Application.Bean2">
<property name="bean1" ref="bean1"></property>
</bean>
<!-- 添加后置处理器-->
<context:annotation-config/>
</beans>

AnnotationConfigServletWebServerApplicationContext

基于 Java 配置类来创建,用于 web 环境。

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
@Configuration
static class WebConfig {
@Bean
public ServletWebServerFactory servletWebServerFactory() {
// 提供内嵌的 Web 容器
return new TomcatServletWebServerFactory();
}

@Bean
public DispatcherServlet dispatcherServlet() {
// 添加前端控制器
return new DispatcherServlet();
}

@Bean
public DispatcherServletRegistrationBean registrationBean(DispatcherServlet dispatcherServlet) {
// 将 DispatcherServlet 注册到 Tomcat 服务器
return new DispatcherServletRegistrationBean(dispatcherServlet, "/");
}

// 如果 bean 以 '/' 开头,将 '/' 后的 bean 的名称作为访问路径
@Bean("/hello")
public Controller controller1() {
// 添加控制器,用于展示
return (request, response) -> {
response.getWriter().println("hello");
return null;
};
}
}

1
2
3
4
5
private static void testAnnotationConfigServletWebServerApplicationContext() {
AnnotationConfigServletWebServerApplicationContext context =
new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);
}

运行代码,在浏览器中访问 http://localhost:8080/hello 路径则会显示出 hello 字样:

Bean的生命周期

1
2
3
4
5
6
7
8
@SpringBootApplication
public class A03Application {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(A03Application.class, args);
// 调用 close 方法,显示生命周期的销毁阶段
context.close();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Slf4j
@Component
public class LifeCycleBean {

public LifeCycleBean() {
log.info("构造");
}

@Autowired
public void autowire(@Value("${JAVA_HOME}") String home) {
log.info("依赖注入: {}", home);
}

@PostConstruct
public void init() {
log.info("初始化");
}

@PreDestroy
public void destroy() {
log.info("销毁");
}
}

运行主启动类,查看控制台的日志信息(只列举主要信息):

com.atguigu.a03.LifeCycleBean : 构造
com.atguigu.a03.LifeCycleBean : 依赖注入: C:\Program Files\Java\jdk1.8.0_60
com.atguigu.a03.LifeCycleBean : 初始化
com.atguigu.a03.LifeCycleBean : 销毁

除此之外,Spring还提供了一些对Bean生命周期的各个阶段进行拓展的BeanPostProcessor,比如

InstantiationAwareBeanPostProcessorDestructionAwareBeanPostProcessor

实现这两个接口,并使用 @Component 注解标记实现类:

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
@Slf4j
@Component
public class MyBeanPostProcessor implements InstantiationAwareBeanPostProcessor, DestructionAwareBeanPostProcessor {

@Override
public void postProcessBeforeDestruction(Object o, String beanName) throws BeansException {
if ("lifeCycleBean".equals(beanName)) {
log.info("<<<<<<<<<< 销毁执行之前,如 @PreDestroy");
}
}

@Override
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
if ("lifeCycleBean".equals(beanName)) {
log.info("<<<<<<<<<< 实例化之前执行,这里返回的对象会替换掉原本的 bean");
}
return null;
}

@Override
public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
if ("lifeCycleBean".equals(beanName)) {
log.info("<<<<<<<<<< 实例化之后执行,如果返回 false 会跳过依赖注入节点");
// return false;
}
return true;
}

@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {
if ("lifeCycleBean".equals(beanName)) {
log.info("<<<<<<<<<< 依赖注入阶段执行,如 @Autowired、@Value、@Resource");
}
return pvs;
}

@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if ("lifeCycleBean".equals(beanName)) {
log.info("<<<<<<<<<< 初始化执行之前,这里返回的对象会替换掉原本的 bean,如 @PostConstruct、@ConfigurationProperties");
}
return bean;
}

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if ("lifeCycleBean".equals(beanName)) {
log.info("<<<<<<<<<< 初始化之后执行,这里返回的对象会替换掉原本的 bean,如代理增强");
}
return bean;
}
}

再运行主启动类,查看控制台的日志信息(只列举主要信息):

com.atguigu.a03.MyBeanPostProcessor : <<<<<<<<<< 实例化之前执行,这里返回的对象会替换掉原本的 bean
com.atguigu.a03.LifeCycleBean : 构造
com.atguigu.a03.MyBeanPostProcessor : <<<<<<<<<< 实例化之后执行,如果返回 false 会跳过依赖注入节点
com.atguigu.a03.MyBeanPostProcessor : <<<<<<<<<< 依赖注入阶段执行,如 @Autowired、@Value、@Resource
com.atguigu.a03.LifeCycleBean : 依赖注入: C:\Program Files\Java\jdk1.8.0_60
com.atguigu.a03.MyBeanPostProcessor : <<<<初始化执行之前,这里返回的对象会替换掉原本的 bean,如 @PostConstruct、@ConfigurationProperties
com.atguigu.a03.LifeCycleBean : 初始化
com.atguigu.a03.MyBeanPostProcessor : <<<<<<<<<< 初始化之后执行,这里返回的对象会替换掉原本的 bean,如代理增强
com.atguigu.a03.MyBeanPostProcessor : <<<<<<<<<< 销毁执行之前,如 @PreDestroy
com.atguigu.a03.LifeCycleBean : 销毁

为什么实现了 BeanPostProcessor 接口后就能够在 Bean 生命周期的各个阶段进行拓展呢?

这使用了模板方法设计模式。

现有如下代码,模拟 BeanFactory 构造 Bean:

1
2
3
4
5
6
7
8
9
static class MyBeanFactory {
public Object getBean() {
Object bean = new Object();
System.out.println("构造 " + bean);
System.out.println("依赖注入 " + bean);
System.out.println("初始化 " + bean);
return bean;
}
}

如果现在需要在依赖注入之后,初始化之前进行其他的操作,那首先能想到的就是直接改写getBean()相关操作的代码,显然不是一种好方式。

可以定义一个接口:

1
2
3
interface BeanPostProcessor {
void inject(Object bean);
}

然后对 MyBeanFactory 进行修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static class MyBeanFactory {
private List<BeanPostProcessor> processors = new ArrayList<>();

public void addProcessor(BeanPostProcessor processor) {
processors.add(processor);
}

public Object getBean() {
Object bean = new Object();
System.out.println("构造 " + bean);
System.out.println("依赖注入 " + bean);
for (BeanPostProcessor processor : processors) {
processor.inject(bean);
}
System.out.println("初始化 " + bean);
return bean;
}
}

之后如果需要拓展,调用 MyBeanFactory 实例的 addProcessor() 方法添加拓展逻辑即可:

1
2
3
4
5
6
public static void main(String[] args) {
MyBeanFactory beanFactory = new MyBeanFactory();
beanFactory.addProcessor(bean -> System.out.println("解析 @Autowired"));
beanFactory.addProcessor(bean -> System.out.println("解析 @Resource"));
beanFactory.getBean();
}

构造 java.lang.Object@49097b5d
依赖注入 java.lang.Object@49097b5d
解析 @Autowired
解析 @Resource
初始化 java.lang.Object@49097b5d

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
@Slf4j
public class Bean1 {
private Bean2 bean2;

@Autowired
public void setBean2(Bean2 bean2) {
log.info("@Autowired 生效: {}", bean2);
this.bean2 = bean2;
}

private Bean3 bean3;

@Resource
public void setBean3(Bean3 bean3) {
log.info("@Resource 生效: {}", bean3);
this.bean3 = bean3;
}

private String home;

@Autowired
public void setHome(@Value("${JAVA_HOME}") String home) {
log.info("@Value 生效: {}", home);
this.home = home;
}

@PostConstruct
public void init() {
log.info("@PostConstruct 生效");
}

@PreDestroy
public void destroy() {
log.info("@PreDestroy 生效");
}
}
1
2
3
4
5
public class Bean2 {
}

public class Bean3 {
}

Bean2Bean3 很简单,而在 Bean1 中使用了多个注解以实现 Bean 注入和值注入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class A04Application {
public static void main(String[] args) {
// GenericApplicationContext 是一个干净的容器
GenericApplicationContext context = new GenericApplicationContext();
// 用原始方式注册三个 bean
context.registerBean("bean1", Bean1.class);
context.registerBean("bean2", Bean2.class);
context.registerBean("bean3", Bean3.class);

// 初始化容器。执行 beanFactory 后置处理器,添加 bean 后置处理器,初始化所有单例
context.refresh();

// 销毁容器
context.close();
}
}

运行上述方法后,控制台中只打印了与 Spring 相关的日志信息,也就是说 Bean1 中使用的注解并没有生效。

GenericApplicationContext 添加一些 Bean 后置处理器,使得 Bean1 中使用的注解能够生效。

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) {
//GenericApplicationContext是一个干净的容器
GenericApplicationContext context = new GenericApplicationContext();

context.registerBean("bean1", Bean1.class);
context.registerBean("bean2", Bean2.class);
context.registerBean("bean3", Bean3.class);
//解析 @Value 的值
context.getDefaultListableBeanFactory().setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
//解析@Autowired、@Value注解
context.registerBean(AutowiredAnnotationBeanPostProcessor.class);
//解析@Resource、@PostConstruct、@PreDestroy注解
context.registerBean(CommonAnnotationBeanPostProcessor.class);

//初始化容器。执行 beanFactory 后置处理器,添加 bean 后置处理器,初始化所有单例
context.refresh();

//销毁容器
context.close();
}

com.atguigu.a04.Bean1 - @Resource 生效: com.atguigu.a04.Bean3@1649b0e6
com.atguigu.a04.Bean1 - @Autowired 生效: com.atguigu.a04.Bean2@34f7cfd9
com.atguigu.a04.Bean1 - @Value 生效: C:\Program Files\Java\jdk1.8.0_60
com.atguigu.a04.Bean1 - @PostConstruct 生效
com.atguigu.a04.Bean1 - @PreDestroy 生效

解析 @ConfigurationProperties

1
2
3
4
5
6
7
8
@Getter
@Setter
@ToString
@ConfigurationProperties(prefix = "java")
public class Bean4 {
private String home;
private String version;
}

上述代码用于获取环境变量中 java.home 和 java.version 的信息。

对先前的 main() 方法进行补充:

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

context.registerBean("bean4", Bean4.class);

System.out.println(context.getBean(Bean4.class));

}

Bean4(home=null, version=null)

Bean4 成功添加到容器中,但值注入失败了,显然是缺少解析 @ConfigurationProperties 注解的后置处理器。

1
2
3
4
5
6
7
8
9
public static void main(String[] args) {
context.registerBean("bean4", Bean4.class);

// 解析@ConfigurationProperties
ConfigurationPropertiesBindingPostProcessor.register(context.getDefaultListableBeanFactory());

System.out.println(context.getBean(Bean4.class));
}

Bean4(home=C:\Users\Administrator.jdks\corretto-17.0.11, version=17.0.11)

AutowiredAnnotationBeanPostProcessor

AutowiredAnnotationBeanPostProcessor 可用于解析@Autowired@Value注解,下面我们对它的运行过程进行深入分析。

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 DigInAutowired {
public static void main(String[] args) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
//注册成品的bean,不再进行 bean的创建过程、依赖注入、初始化....
beanFactory.registerSingleton("bean2", new Bean2());
beanFactory.registerSingleton("bean3", new Bean3());
//解析 @Value 的值
beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
//${} 的解析器
beanFactory.addEmbeddedValueResolver(new StandardEnvironment()::resolvePlaceholders);

//之前AutowiredAnnotationBeanPostProcessor 是在beanFactor内部被调用,执行到 依赖注入阶段,获取到后置处理器,执行对应方法
// 查看哪些属性、方法加了 @Autowired,这称之为 InjectionMetadata
AutowiredAnnotationBeanPostProcessor processor = new AutowiredAnnotationBeanPostProcessor();
processor.setBeanFactory(beanFactory);

//不会依赖注入
Bean1 bean1 = new Bean1();
System.out.println(bean1);

//执行依赖注入 解析@Autowired @Value
processor.postProcessProperties(null, bean1, "bean1");
System.out.println(bean1);
}
}

Bean1(bean2=null, bean3=null, home=null)
com.atguigu.a04.Bean1 - @Autowired 生效: com.atguigu.a04.Bean2@de3a06f
com.atguigu.a04.Bean1 - @Value 生效: C:\Program Files\Java\jdk1.8.0_60
Bean1(bean2=com.atguigu.a04.Bean2@de3a06f, bean3=null, home=C:\Program Files\Java\jdk1.8.0_60)

在未调用 AutowiredAnnotationBeanPostProcessor#postProcessProperties() 方法时,bean1中的bean2bean3home都没有注入,而在调用之后,成功注入了bean2home,由于bean3是通过@Resource标注,所以不会进行注入。

postProcessProperties()源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
try {
metadata.inject(bean, beanName, pvs);
}
catch (BeanCreationException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
}
return pvs;
}

其中 findAutowiringMetadata() 用于查找哪些属性方法参数上标注@Autowired@Value ,将这些信息封装到InjectionMetadata 中,然后调用inject()反射完成注入。

findAutowiringMetadata()是一个私有方法,利用反射进行调用并打断点查看InjectionMetadata中的信息。

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 DigInAutowired {
public static void main(String[] args) throws Exception {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
//注册成品的bean,不再进行 bean的创建过程、依赖注入、初始化....
beanFactory.registerSingleton("bean2", new Bean2());
beanFactory.registerSingleton("bean3", new Bean3());
//解析 @Value 的值
beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
//${} 的解析器
beanFactory.addEmbeddedValueResolver(new StandardEnvironment()::resolvePlaceholders);

//之前AutowiredAnnotationBeanPostProcessor 是在beanFactor内部被调用,执行到 依赖注入阶段,获取到后置处理器,执行对应方法
// 查看哪些属性、方法加了 @Autowired,这称之为 InjectionMetadata
AutowiredAnnotationBeanPostProcessor processor = new AutowiredAnnotationBeanPostProcessor();
processor.setBeanFactory(beanFactory);

//不会依赖注入
Bean1 bean1 = new Bean1();
Method findAutowiringMetadata =
AutowiredAnnotationBeanPostProcessor.class.getDeclaredMethod("findAutowiringMetadata", String.class, Class.class, PropertyValues.class);
findAutowiringMetadata.setAccessible(true);
//获取 bean1类中 添加了@Autowired 或 @Value 的成员变量,方法参数信息
InjectionMetadata metadata = (InjectionMetadata) findAutowiringMetadata.invoke(processor, "bean1", Bean1.class, null);
//此处断点
System.out.println(metadata);

}
}

image-20241219224813677

InjectionMetadata 中有一个名为injectedElements的集合类型成员变量,其中存储了添加了@Autowired@Value 的成员变量,方法参数信息

然后调用InjectionMetadata#inject()注入

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

// 获取 Bean1 上加了 @Value、@Autowired 注解的成员变量、方法参数信息
InjectionMetadata metadata = (InjectionMetadata) method.invoke(postProcessor, "bean1", Bean1.class, null);

// 调用 InjectionMetadata 来进行依赖注入,注入时按类型查找值
metadata.inject(bean1, "bean1", null);
System.out.println(bean1);
}

com.atguigu.a04.Bean1 - @Autowired 生效: com.atguigu.a04.Bean2@1e4a7dd4
com.atguigu.a04.Bean1 - @Value 生效: C:\Program Files\Java\jdk1.8.0_60
Bean1(bean2=com.atguigu.a04.Bean2@1e4a7dd4, bean3=null, home=C:\Program Files\Java\jdk1.8.0_60)

既然InjectionMetadata 中保存了 @Value @Autowired 注解的成员变量、方法参数信息,那么inject()中又如何处理呢?

通过一下代码简单概括:

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

// 如何按类型查找值
Field bean3 = Bean1.class.getDeclaredField("bean3");
//成员变量信息 被封装为 DependencyDescriptor 对象
DependencyDescriptor dd1 = new DependencyDescriptor(bean3, false);
//要注入谁
Object o1 = beanFactory.doResolveDependency(dd1, null, null, null);
System.out.println(o1);

Method setBean2 = Bean1.class.getDeclaredMethod("setBean2", Bean2.class);
// MethodParameter 构造方法的第二个参数表示需要解析的方法中参数的索引
DependencyDescriptor dd2 = new DependencyDescriptor(new MethodParameter(setBean2, 0), false);
Object o2 = beanFactory.doResolveDependency(dd2, null, null, null);
System.out.println(o2);

Method setHome = Bean1.class.getDeclaredMethod("setHome", String.class);
DependencyDescriptor dd3 = new DependencyDescriptor(new MethodParameter(setHome, 0), true);
Object o3 = beanFactory.doResolveDependency(dd3, null, null, null);
System.out.println(o3);
}

com.atguigu.a04.Bean3@31206beb
com.atguigu.a04.Bean2@50de0926
C:\Program Files\Java\jdk1.8.0_60

BeanFactory后置处理器

常见的工厂后置处理器

引入所需依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.0</version>
</dependency>

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.15</version>
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>

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
@Configuration
@ComponentScan("com.atguigu.a05.component")
public class Config {

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

@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
return sqlSessionFactoryBean;
}

@Bean(initMethod = "init")
public DruidDataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl("jdbc:mysql://localhost:3306/advanced_spring");
dataSource.setName("root");
dataSource.setPassword("123456");
return dataSource;
}
}
1
2
3
4
5
6
7
@Slf4j
public class Bean1 {
public Bean1() {
System.out.println("我被 Spring 管理啦");
}
}

1
2
3
4
5
6
7
@Slf4j
@Component
public class Bean2 {
public Bean2() {
log.info("我被 Spring 管理啦");
}
}

使用GenericApplicationContext 作为容器,向容器中注册config

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class A05Application {
public static void main(String[] args) {
//GenericApplicationContext 是一个 【干净】的容器
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("config", Config.class);

//初始化容器
context.refresh();

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

//销毁容器
context.close();
}
}

config

并没有打印出config 外的Bean信息,@Configuration@Bean 并没有被解析。

向容器中注册ConfigurationClassPostProcessor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class A05Application {
public static void main(String[] args) {
//GenericApplicationContext 是一个 【干净】的容器
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("config", Config.class);
// @ComponentScan @Bean @Import @ImportResource
context.registerBean(ConfigurationClassPostProcessor.class);

//初始化容器
context.refresh();

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

//销毁容器
context.close();
}
}

com.atguigu.a05.component.Bean2 - 我被 Spring 管理啦
com.atguigu.a05.Bean1 - 我被 Spring 管理啦
com.alibaba.druid.pool.DruidDataSource - {dataSource-1,root} inited
config
org.springframework.context.annotation.ConfigurationClassPostProcessor
bean2
bean1
sqlSessionFactoryBean
dataSource

整合MyBatis时,@Mapper注解的解析,也需要特定的BeanFactory后置处理器

1
2
3
4
5
6
7
8
@Mapper
public interface Mapper1 {
}

@Mapper
public interface Mapper2 {
}

向容器中注册MapperScannerConfigurer

1
2
3
4
5
6
7
8
9
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("config", Config.class);
// @ComponentScan @Bean @Import @ImportResource
context.registerBean(ConfigurationClassPostProcessor.class);
context.registerBean(MapperScannerConfigurer.class,
i -> i.getPropertyValues().add("basePackage", "com.atguigu.a05.mapper"));

}

mapper1
mapper2

除此之外,还有一些常用的后置处理器并没有在上述信息中体现。

工厂后置处理器模拟实现

移除容器中添加的 ConfigurationClassPostProcessorMapperScannerConfigurer 两个后置处理器,编码模拟对应功能的实现。

组件扫描 @ComponentScan

Bean2所在包路径下,新增两个类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Slf4j
@Controller
public class Bean3 {
public Bean3() {
log.info("我被 Spring 管理啦");
}
}

@Slf4j
public class Bean4 {
public Bean4() {
log.info("我被 Spring 管理啦");
}
}

编写 ComponentScanPostProcessor,实现 @ComponentScan 注解的解析

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
public class ComponentScanPostProcessor implements BeanFactoryPostProcessor {
/**
* 调用 context.refresh() 方法时回调
*/
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
try {
ComponentScan componentScan = AnnotationUtils.findAnnotation(Config.class, ComponentScan.class);
if (componentScan != null) {
for (String packageName : componentScan.basePackages()) {
System.out.println(packageName);
CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
String path = "classpath*:" + packageName.replace(".", "/") + "/**/*.class";
Resource[] resources = new PathMatchingResourcePatternResolver().getResources(path);
AnnotationBeanNameGenerator generator = new AnnotationBeanNameGenerator();
for (Resource resource : resources) {
//System.out.println(resource);
MetadataReader reader = factory.getMetadataReader(resource);
//System.out.println("类信息 => " + reader.getClassMetadata().getClassName());
//System.out.println("是否包含@Component注解 => " + reader.getAnnotationMetadata().hasAnnotation(Component.class.getName()));
//System.out.println("是否包含@Component的派生注解 => " + reader.getAnnotationMetadata().hasMetaAnnotation(Component.class.getName()));
AnnotationMetadata annotationMetadata = reader.getAnnotationMetadata();
if (annotationMetadata.hasAnnotation(Component.class.getName())
|| annotationMetadata.hasMetaAnnotation(Component.class.getName())) {
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
.genericBeanDefinition(reader.getClassMetadata().getClassName())
.getBeanDefinition();
if (configurableListableBeanFactory instanceof DefaultListableBeanFactory beanFactory) {
String beanName = generator.generateBeanName(beanDefinition, beanFactory);
beanFactory.registerBeanDefinition(beanName, beanDefinition);
}
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void main(String[] args) throws Exception {
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("config", Config.class);
context.registerBean(ComponentScanPostProcessor.class);
//初始化容器
context.refresh();

for (String name : context.getBeanDefinitionNames()) {
System.out.println(name);
}
//销毁容器
context.close();
}

com.atguigu.a05.component
21:04:10.848 [main] INFO com.atguigu.a05.component.Bean2 - 我被 Spring 管理啦
21:04:10.848 [main] INFO com.atguigu.a05.component.Bean3 - 我被 Spring 管理啦
config
com.atguigu.a05.ComponentScanPostProcessor
bean2
bean3

@Bean的解析

Config类中新增一个方法作为对比项

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
@Configuration
@ComponentScan("com.atguigu.a05.component")
public class Config {

//新增方法,用于对比
public Bean2 bean2() {
return new Bean2();
}

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

@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
return sqlSessionFactoryBean;
}

@Bean(initMethod = "init")
public DruidDataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl("jdbc:mysql://localhost:3306/advanced_spring");
dataSource.setName("root");
dataSource.setPassword("123456");
return dataSource;
}
}

编写 AtBeanPostProcessor 实现 @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
public class AtBeanPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
try {
CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
MetadataReader reader = factory.getMetadataReader(new ClassPathResource("com/atguigu/a05/Config.class"));

Set<MethodMetadata> methods =
reader.getAnnotationMetadata().getAnnotatedMethods(Bean.class.getName());

for (MethodMetadata method : methods) {
String initMethod = method.getAnnotationAttributes(Bean.class.getName()).get("initMethod").toString();
System.out.println(method);
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
builder.setFactoryMethodOnBean(method.getMethodName(), "config");
// 工厂方法、构造方法的参数 自动装配选择的模式为AUTOWIRE_CONSTRUCTOR
builder.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
if (initMethod.length() > 0) {
builder.setInitMethodName(initMethod);
}
if (configurableListableBeanFactory instanceof DefaultListableBeanFactory beanFactory) {
beanFactory.registerBeanDefinition(method.getMethodName(), beanDefinition);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

sqlSessionFactoryBean 需要一个DataSource类型的参数,所以必须指定 自动装配模式

1
2
3
4
5
6
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
return sqlSessionFactoryBean;
}
1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean(AtBeanPostProcessor.class);

context.refresh();

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

context.close();
}

com.atguigu.a05.Config.bean1()
com.atguigu.a05.Config.sqlSessionFactoryBean(javax.sql.DataSource)
com.atguigu.a05.Config.dataSource()
com.atguigu.a05.Bean1 - 我被 Spring 管理啦
com.alibaba.druid.pool.DruidDataSource - {dataSource-1,root} inited

config
com.atguigu.a05.AtBeanPostProcessor
bean1
sqlSessionFactoryBean
dataSource

@Mapper的解析

@Mapper注解是标注在接口上的,Spring 并不能真的管理一个接口,最终管理的还是一个个的对象。

而这些接口依赖于 @MapperFactoryBean 将接口转换为对象。

在 Config 添加注册 Mapper1Mapper2 的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Bean
public MapperFactoryBean<Mapper1> mapper1(SqlSessionFactory sqlSessionFactory) {
MapperFactoryBean<Mapper1> factory = new MapperFactoryBean<>(Mapper1.class);
factory.setSqlSessionFactory(sqlSessionFactory);
return factory;
}

@Bean
public MapperFactoryBean<Mapper2> mapper2(SqlSessionFactory sqlSessionFactory) {
MapperFactoryBean<Mapper2> factory = new MapperFactoryBean<>(Mapper2.class);
factory.setSqlSessionFactory(sqlSessionFactory);
return factory;
}

运行后,容器中 包含 名为 mapper1 和``mapper2` 的Bean

这种方式虽然可以完成 Mapper 接口的注册,但每次只能单个注册,不能批量注册。

移除 Config 类中的 mapper1()mapper2() 方法,编写 MapperPostProcessor 实现@Mapper 注解的解析

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
public class MapperPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {

}

@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanFactory) throws BeansException {

try {
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
//扫描mapper包下的资源
Resource[] resources = resolver.getResources("classpath:com/atguigu/a05/mapper/**/*.class");
//beanName生成器
AnnotationBeanNameGenerator generator = new AnnotationBeanNameGenerator();
//读取类的元数据信息
CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
for (Resource resource : resources) {
MetadataReader reader = factory.getMetadataReader(resource);
ClassMetadata classMetadata = reader.getClassMetadata();
if (classMetadata.isInterface()) {
//根据接口生成
AbstractBeanDefinition beanDefinition1 = BeanDefinitionBuilder.genericBeanDefinition(MapperFactoryBean.class)
.addConstructorArgValue(classMetadata.getClassName())
.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE)
.getBeanDefinition();

//根据Mapper接口生成BeanDefinition
AbstractBeanDefinition beanDefinition2 = BeanDefinitionBuilder.genericBeanDefinition(classMetadata.getClassName())
.getBeanDefinition();

String beanName = generator.generateBeanName(beanDefinition2, beanFactory);
beanFactory.registerBeanDefinition(beanName, beanDefinition1);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

测试

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

context.registerBean(AtBeanPostProcessor.class);
context.registerBean(MapperPostProcessor.class);

context.refresh();

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

context.close();
}

com.atguigu.a05.Config.bean1()
com.atguigu.a05.Config.sqlSessionFactoryBean(javax.sql.DataSource)
com.atguigu.a05.Config.dataSource()
22:24:18.940 [main] INFO com.alibaba.druid.pool.DruidDataSource - {dataSource-1,root} inited
22:24:19.042 [main] INFO com.atguigu.a05.Bean1 - 我被 Spring 管理啦

config
com.atguigu.a05.AtBeanPostProcessor
com.atguigu.a05.MapperPostProcessor
mapper1
mapper2
bean1
sqlSessionFactoryBean
dataSource

Aware 接口

Aware 接口

Aware接口用于注入一些与容器相关的信息,例如

  1. BeanNameAware 注入bean的名字
  2. BeanFactoryAware 注入BeanFactory容器
  3. ApplicationContextAware 注入ApplicationContext 容器
  4. EmbeddedValueResolverAware ${}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MyBean implements BeanNameAware, ApplicationContextAware {
private static final Logger logger = LoggerFactory.getLogger(MyBean.class);

@Override
public void setBeanName(String name) {
logger.debug("\n当前bean =>" + this + " \n名称 => " + name);
}

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
logger.debug("\n当前bean =>" + this + " \nApplicationContext => " + applicationContext);
}
}

1
2
3
4
5
6
7
8
9
10
@Slf4j
public class A06Application {
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("myBean", MyBean.class);

context.refresh();
context.close();
}
}

当前bean =>com.atguigu.a06.MyBean@1a3869f4
名称 => myBean

当前bean =>com.atguigu.a06.MyBean@1a3869f4
ApplicationContext => org.springframework.context.support.GenericApplicationContext@75bd9247, started on Mon Dec 23 23:08:39 CST 2024

InitializingBean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class MyBean implements BeanNameAware, ApplicationContextAware,InitializingBean {
private static final Logger logger = LoggerFactory.getLogger(MyBean.class);

@Override
public void setBeanName(String name) {
logger.debug("\n当前bean =>" + this + " \n名称 => " + name);
}

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
logger.debug("\n当前bean =>" + this + " \nApplicationContext => " + applicationContext);
}

@Override
public void afterPropertiesSet() throws Exception {
logger.debug("\n当前bean =>" + this + " 初始化");
}

}

当前bean =>com.atguigu.a06.MyBean@63440df3
名称 => myBean
com.atguigu.a06.MyBean - 当前bean =>com.atguigu.a06.MyBean@63440df3
ApplicationContext => org.springframework.context.support.GenericApplicationContext@75bd9247, started on Mon Dec 23 23:10:58 CST 2024
com.atguigu.a06.MyBean - 当前bean =>com.atguigu.a06.MyBean@63440df3 初始化

BeanFactoryAwareApplicationContextAwareEmbeddedValueResolverAware 三个接口的功能可以使用 @Autowired 注解实现,InitializingBean 接口的功能也可以使用 @PostConstruct 注解实现,为什么还要使用接口呢?

@Autowired@PostConstruct 注解的解析需要使用 Bean 后置处理器,属于拓展功能,而Aware接口属于内置功能,不加任何拓展 Spring 就能识别。

在某些情况下,拓展功能会失效,而内置功能不会失效。

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 MyBean implements BeanNameAware, ApplicationContextAware,InitializingBean {
private static final Logger logger = LoggerFactory.getLogger(MyBean.class);

@Override
public void setBeanName(String name) {
logger.debug("\n当前bean =>" + this + " \n名称 => " + name);
}

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
logger.debug("\n当前bean =>" + this + " \nApplicationContext => " + applicationContext);
}

@Override
public void afterPropertiesSet() throws Exception {
logger.debug("\n当前bean =>" + this + " 初始化");
}

@Autowired
public void method(ApplicationContext applicationContext) {
logger.debug("\n当前bean =>" + this + " \n使用@AutoWired注入,ApplicationContext => " + applicationContext);
}

@PostConstruct
public void init() {
logger.debug("\n当前bean =>" + this + " 使用@PostConstruct 初始化");
}
}

com.atguigu.a06.MyBean -
当前bean =>com.atguigu.a06.MyBean@63440df3
名称 => myBean

com.atguigu.a06.MyBean -
当前bean =>com.atguigu.a06.MyBean@63440df3
ApplicationContext => org.springframework.context.support.GenericApplicationContext@75bd9247, started on Mon Dec 23 23:23:43 CST 2024

com.atguigu.a06.MyBean -
当前bean =>com.atguigu.a06.MyBean@63440df3 初始化

运行main()方法会发现使用的注解没有被成功解析,原因很简单,GenericApplicationContext 是一个干净的容器,其内部没有用于解析这些注解的bean后置处理器。如果想要这些注解生效,则需要添加必要的bean后置处理器:

1
2
context.registerBean(AutowiredAnnotationBeanPostProcessor.class);
context.registerBean(CommonAnnotationBeanPostProcessor.class);

失效的@Autowired 注解

在某些情况下,尽管容器中存在必要的后置处理器,但@Autowired @PostConstruct 等注解也会失效。

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

@Autowired
private void setApplicationContext(ApplicationContext applicationContext) {
logger.info("注入 ApplicationContext");
}

@PostConstruct
public void init() {
logger.info("执行 MyConfig1 的 init 方法");
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("myConfig1", MyConfig1.class);
// 解析 @Autowired 注解的Bean后处理器
context.registerBean(AutowiredAnnotationBeanPostProcessor.class);
// 解析 @PostConstruct 注解的Bean后处理器
context.registerBean(CommonAnnotationBeanPostProcessor.class);
// 解析@ComponentScan、@Bean、@Import、@ImportResource注解的后处理器
context.registerBean(ConfigurationClassPostProcessor.class);

context.refresh();
context.close();
}

com.atguigu.a06.MyConfig1 - 注入 ApplicationContext
com.atguigu.a06.MyConfig1 - 执行 MyConfig1 的 init 方法

@Autowired@PostConstruct 注解解析成功。

然后我们向Config1 中添加BeanFactoryPostProcessor

1
2
3
4
5
6
7
8
9
10
11
@Configuration
public class MyConfig1 {
private static final Logger logger = LoggerFactory.getLogger(MyConfig1.class);
// --snip--

@Bean //beanFactory后置处理器
public BeanFactoryPostProcessor processor1() {
return beanFactory -> logger.info("执行 processor1");
}
}

Config1 中添加了一个被 @Bean 注解标记的 processor1() 方法,用于向容器中添加 BeanFactoryPostProcessor。

再次运行

com.atguigu.a06.MyConfig1 - 执行 processor1

用传统接口方式的注入和初始化依然成功,processor1() 方法成功生效,但@Autowired @PostConstruct 的注入和初始化失败。

那是什么原因导致的呢?

配置类 @Autowired 注解失效分析

  • Java 配置类不包含 BeanFactoryPostProcessor 的情况
1
2
3
4
5
6
7
8
9
10
11
12
sequenceDiagram 
participant ac as ApplicationContext
participant bfpp as BeanFactoryPostProcessor
participant bpp as BeanPostProcessor
participant config as Java配置类
ac ->> bfpp : 1. 执行 BeanFactoryPostProcessor
ac ->> bpp : 2. 注册 BeanPostProcessor
ac ->> +config : 3. 创建和初始化
bpp ->> config : 3.1 依赖注入扩展(如 @Value 和 @Autowired)
bpp ->> config : 3.2 初始化扩展(如 @PostConstruct)
ac ->> config : 3.3 执行 Aware 及 InitializingBean
config -->> -ac : 3.4 创建成功
  • Java 配置类包含 BeanFactoryPostProcessor 的情况
    • 当 Java 配置类中定义了BeanFactoryPostProcessor 时,如果要创建其中的 BeanFactoryPostProcessor 必须提前创建 Java 配置类,而此时的 BeanPostProcessor 还未准备好,导致 @Autowired 等注解失效
1
2
3
4
5
6
7
8
9
10
11
12
13
14
sequenceDiagram 
participant ac as ApplicationContext
participant bfpp as BeanFactoryPostProcessor
participant bpp as BeanPostProcessor
participant config as Java配置类
ac ->> +config : 3. 创建和初始化
ac ->> config : 3.1 执行 Aware 及 InitializingBean
config -->> -ac : 3.2 创建成功

ac ->> bfpp : 1. 执行 BeanFactoryPostProcessor
ac ->> bpp : 2. 注册 BeanPostProcessor



可以使用Spring内置的接口,避免类似问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class MyConfig2 implements InitializingBean, ApplicationContextAware {
private static final Logger logger = LoggerFactory.getLogger(MyConfig2.class);

@Override
@Autowired
public void setApplicationContext(ApplicationContext applicationContext) {
logger.info("注入 ApplicationContext");
}


@Override
public void afterPropertiesSet() throws Exception {
logger.info("初始化");
}
@Bean //beanFactory后置处理器
public BeanFactoryPostProcessor processor2() {
return beanFactory -> logger.info("执行 processor2");
}

}
1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("myConfig2", MyConfig2.class);
context.registerBean(AutowiredAnnotationBeanPostProcessor.class);
context.registerBean(CommonAnnotationBeanPostProcessor.class);
context.registerBean(ConfigurationClassPostProcessor.class);

context.refresh();
context.close();
}

com.atguigu.a06.MyConfig2 - 注入 ApplicationContext
com.atguigu.a06.MyConfig2 - 初始化
com.atguigu.a06.MyConfig2 - 执行 processor2

==总结==

  • Aware接口提供了一种内置的注入手段,可以注入BeanFactory ,ApplicationContext
  • InitializingBean接口提供了一种内置的初始化手段
  • 内置的注入和初始化不受扩展功能的影响,总会执行,因此Spring框架内部的类常用它们

初始化与销毁

初始化和销毁 Bean 的实现有三种:

  1. 依赖于后置处理器提供的拓展功能

  2. 相关接口的功能

  3. 使用 @Bean 注解中的属性进行指定

    三种初始化方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//3种初始化方法
@Slf4j
public class Bean1 implements InitializingBean {
@PostConstruct
public void init() {
log.info("@PostConstruct 初始化");
}

@Override
public void afterPropertiesSet() throws Exception {
log.info("InitializingBean 初始化");
}

public void init3() {
log.info("@Bean 初始化");
}
}

三种销毁方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Slf4j
public class Bean2 implements DisposableBean {
@PreDestroy
public void destroy1() {
log.info("@PreDestroy 销毁");
}

@Override
public void destroy() throws Exception {
log.info("DisposableBean 销毁");
}

public void destroy3() {
log.info("@Bean 销毁");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@SpringBootApplication
public class A07Application {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(A07Application.class, args);
context.close();
}

@Bean(initMethod = "init3")
public Bean1 bean1() {
return new Bean1();
}

@Bean(destroyMethod = "destroy3")
public Bean2 bean2() {
return new Bean2();
}
}

执行顺序

com.atguigu.a07.Bean1 : @PostConstruct 初始化
com.atguigu.a07.Bean1 : InitializingBean 初始化
com.atguigu.a07.Bean1 : @Bean 初始化
com.atguigu.a07.Bean2 : @PreDestroy 销毁
com.atguigu.a07.Bean2 : DisposableBean 销毁
com.atguigu.a07.Bean2 : @Bean 销毁

Scope

Scope 的类型与销毁

Scope 用于指定 Bean 的作用范围,有如下五个取值:

  • singleton:单例(默认值)。容器启动时创建(未设置延迟),容器关闭时销毁
  • prototype:多例。每次使用时创建,不会自动销毁,需要调用 DefaultListableBeanFactory#destroyBean() 进行销毁
  • request:作用于 Web 应用的请求范围。每次请求用到此 Bean 时创建,请求结束时销毁
  • session:作用于 Web 应用的会话范围。每个会话用到此 Bean 时创建,会话结束时销毁
  • application:作用于 Web 应用的 ServletContext。Web 容器用到此 Bean 时创建,容器关闭时销毁
    前两个取值不再赘述,重点看下后三个取值。
1
2
3
4
5
6
7
8
9
@Slf4j
@Component
@Scope(WebApplicationContext.SCOPE_REQUEST)
public class BeanForRequest {
@PreDestroy
public void destroy() {
log.info("destroy");
}
}
1
2
3
4
5
6
7
8
9
@Slf4j
@Component
@Scope(WebApplicationContext.SCOPE_SESSION)
public class BeanForSession {
@PreDestroy
public void destroy() {
log.info("destroy");
}
}
1
2
3
4
5
6
7
8
9
@Slf4j
@Component
@Scope(WebApplicationContext.SCOPE_APPLICATION)
public class BeanForApplication {
@PreDestroy
public void destroy() {
log.info("destroy");
}
}
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
@RestController
public class MyController {
@Lazy
@Autowired
private BeanForRequest beanForRequest;

@Lazy
@Autowired
private BeanForSession beanForSession;

@Lazy
@Autowired
private BeanForApplication beanForApplication;

@GetMapping(value = "/test", produces = "text/html")
public String test(HttpServletRequest request, HttpSession session) {
// 设置 session 过期时间为 10 秒
session.setMaxInactiveInterval(10);
// ServletContext sc = request.getServletContext();
return "<ul>" +
"<li>request scope: " + beanForRequest + "</li>" +
"<li>session scope: " + beanForSession + "</li>" +
"<li>application scope: " + beanForApplication + "</li>" +
"</ul>";
}
}

主启动类

1
2
3
4
5
6
7
@SpringBootApplication
public class A08Application {

public static void main(String[] args) {
SpringApplication.run(A08Application.class, args);
}
}

如果使用的 JDK 版本大于 8,需要要启动参数中添加如下信息避免报错:

1
--add-opens java.base/java.lang=ALL-UNNAMED

但更建议在 pom.xml 中添加以下配置,一劳永逸:

1
2
3
4
5
6
7
8
9
10
11
12
13
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>
--add-opens java.base/java.lang=ALL-UNNAMED
</argLine>
</configuration>
</plugin>
</plugins>
</build>

运行主启动类,在浏览器中访问 http://localhost:8080/test,页面上显示:

  • request scope: com.atguigu.a08.BeanForRequest@2b6ecfd4
  • session scope: com.atguigu.a08.BeanForSession@54ecf133
  • application scope:com.atguigu.a08.BeanForApplication@29b1126e

刷新页面,页面上的信息变化为:

  • request scope: com.atguigu.a08.BeanForRequest@3ef32346
  • session scope: com.atguigu.a08.BeanForSession@54ecf133
  • application scope:com.atguigu.a08.BeanForApplication@29b1126e

可以看到 request scope 发生了变化,session scopeapplication scope 没有变化。

换一个浏览器访问 http://localhost:8080/test,两个浏览器中的会话肯定不是同一个,此时 session scope 应该会发生变化:

  • request scope: com.atguigu.a08.BeanForRequest@49575379
  • session scope: com.atguigu.a08.BeanForSession@86a5942
  • application scope:com.atguigu.a08.BeanForApplication@29b1126e

application 的作用范围是 ServletContext,要想 application scope 发生变化可以重启程序。

当刷新页面后, request scope 的值发生变化,request 作用范围的 Bean 执行了销毁方法。

com.atguigu.a08.BeanForRequest : 销毁

如果想看到 session 作用范围的 Bean 执行销毁方法,可以等 session 过期时在控制台上看到对应的信息。默认情况下,session 的过期时间是 30 分钟,为了更好地测试,可以在配置文件中添加:

1
2
# 修改 session 过期时间为 10s
server.servlet.session.timeout=10s

这个配置是全局的,如果只想针对某个请求进行配置,则可以:

1
2
3
4
5
6
7
@GetMapping(value = "/test", produces = "text/html")
public String test(HttpServletRequest request, HttpSession session) {
// 设置 session 过期时间为 10 秒
session.setMaxInactiveInterval(10);

// --snip--
}

设置 session 过期时间为 10 秒后,并不表示不进行任何操作 10 秒后就能在控制台上看到执行销毁方法的信息,经过测试,大概会等 1 分钟,静静等待 1 分钟左右,控制台上显示:

com.atguigu.a08.BeanForSession : 销毁

很遗憾没有办法看到application 作用范围的 Bean 执行销毁方法,因为 Spring 似乎并没有对 application 作用范围的 Bean 进行正确的销毁处理,因此在 Servlet 容器销毁时看不到 application 作用范围的 Bean 执行销毁方法。

Scope 失效分析

现有两个类

1
2
3
4
@Scope("prototype")
@Component
public class F1 {
}
1
2
3
4
5
6
@Component
@Getter
public class E {
@Autowired
private F1 f1;
}

执行测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Slf4j
@ComponentScan("com.atguigu.a09")
public class A09Application {
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(A09Application.class);

E e = context.getBean(E.class);
log.info("{}", e.getF1());
log.info("{}", e.getF1());
log.info("{}", e.getF1());

context.close();
}
}

F1Scopeprototype,向 E 中注入后,log.info("{}", e.getF1()) 打印出的应该是不同的对象,可结果却相同

com.atguigu.a09.A09Application - com.atguigu.a09.F1@55183b20
com.atguigu.a09.A09Application - com.atguigu.a09.F1@55183b20
com.atguigu.a09.A09Application - com.atguigu.a09.F1@55183b20

获取到的f1居然都是同一个,也就是说向单例对象中注入多例对象失败了。

对于单例对象来说,依赖注入仅发生了一次,后续不会再注入其他的 f1,因此 e 始终使用的是第一次注入的 f1

1
2
3
4
5
6
7
8
graph LR

e1(e 创建)
e2(e set 注入 f)

f1(f 创建)

e1-->f1-->e2

可以使用 @Lazy 生成代理对象,虽然代理对象依旧是同一个,但每次使用代理对象中的方法时,会由代理对象创建新的目标对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
graph LR

e1(e 创建)
e2(e set 注入 f代理)

f1(f 创建)
f2(f 创建)
f3(f 创建)

e1-->e2
e2--使用f方法-->f1
e2--使用f方法-->f2
e2--使用f方法-->f3

解决方案

方案一
1
2
3
4
5
6
7
@Component
@Getter
public class E {
@Lazy
@Autowired
private F1 f1;
}

执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Slf4j
@ComponentScan("com.atguigu.a09")
public class A09Application {
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(A09Application.class);

E e = context.getBean(E.class);
log.info("{}", e.getF1().getClass());
log.info("{}", e.getF1());
log.info("{}", e.getF1());
log.info("{}", e.getF1());

context.close();
}
}

com.atguigu.a09.A09Application - class com.atguigu.a09.F1$$EnhancerBySpringCGLIB$$f49b54ad
com.atguigu.a09.A09Application - com.atguigu.a09.F1@429bffaa
com.atguigu.a09.A09Application - com.atguigu.a09.F1@483f6d77
com.atguigu.a09.A09Application - com.atguigu.a09.F1@63a12c68

使用@Lazy注解后,注入的是代理对象,每次获取到的f1不再是同一个。

方案二

除了使用 @Lazy 注解外,可以使用 @Scope 注解的 proxyMode 属性指定代理模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
@Component
public class F2 {
}


@Getter
@Component
public class E {

@Autowired
private F2 f2;
}

执行测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Slf4j
@ComponentScan("com.atguigu.a09")
public class A09Application {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(A09Application.class);

E e = context.getBean(E.class);

log.info("{}", e.getF2().getClass());
log.info("{}", e.getF2());
log.info("{}", e.getF2());
log.info("{}", e.getF2());

context.close();
}
}

com.atguigu.a09.A09Application - class com.atguigu.a09.F2$$EnhancerBySpringCGLIB$$fbdf70d3
com.atguigu.a09.A09Application - com.atguigu.a09.F2@6d4d66d2
com.atguigu.a09.A09Application - com.atguigu.a09.F2@2a265ea9
com.atguigu.a09.A09Application - com.atguigu.a09.F2@11392934

方案三
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Component
@Scope(value = "prototype")
public class F3 {
}

@Component
public class E {

@Autowired
private ObjectFactory<F3> f3;

public F3 getF3() {
return f3.getObject();
}
}

执行测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Slf4j
@ComponentScan("com.atguigu.a09")
public class A09Application {
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(A09Application.class);

E e = context.getBean(E.class);
log.info("{}", e.getF3().getClass());
log.info("{}", e.getF3());
log.info("{}", e.getF3());
log.info("{}", e.getF3());

context.close();
}
}

com.atguigu.a09.A09Application - class com.atguigu.a09.F3
com.atguigu.a09.A09Application - com.atguigu.a09.F3@25ce9dc4
com.atguigu.a09.A09Application - com.atguigu.a09.F3@17f62e33
com.atguigu.a09.A09Application - com.atguigu.a09.F3@27406a17

方案四
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Component
@Scope(value = "prototype")
public class F4 {
}

@Component
public class E {

@Autowired
private ApplicationContext applicationContext;

public F4 getF4() {
return applicationContext.getBean(F4.class);
}
}

执行测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Slf4j
@ComponentScan("com.atguigu.a09")
public class A09Application {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(A09Application.class);

E e = context.getBean(E.class);

log.info("{}", e.getF4());
log.info("{}", e.getF4());

context.close();
}
}

com.atguigu.a09.A09Application - com.atguigu.a09.F4@54422e18
com.atguigu.a09.A09Application - com.atguigu.a09.F4@117159c0

如果对性能要求较高,则推荐使用后两种方式,前两种使用代理会有一定的性能损耗;如果不在乎那点性能损耗,则可以使用第一种方式,这种方式最简单。

四种解决方式虽然不同,但在理念上殊途同归,都是推迟了其他 Scope Bean 的获取,或者说按需加载。

AOP

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
@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 //切面类并没有被Spring管理
public class MyAspect {
private static final Logger logger = LoggerFactory.getLogger(MyAspect.class);

@Before("execution(* com.itheima.service.MyService.foo())")
public void before() {
logger.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();
}
}

com.itheima.A10Application : service class: class com.itheima.service.MyService
com.itheima.aop.MyAspect : before()
com.itheima.service.MyService : foo()

service.getClass()打印出的是原始类的Class信息,而非代理类Class信息。

实际并没有通过代理进行增强,而是用AspectJ编译器进行增强,原理是 通过改写目标类源文件来增强

在pom中引入插件

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>

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

编译后的代码中新增加了一行代码,MyAspect.aspectOf().before(),编译时增强

既然如此,那么不依赖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();
}
}

com.itheima.A10Application : service class: class com.itheima.service.MyService
com.itheima.aop.MyAspect : before()
com.itheima.service.MyService : foo()

这种增强方式,可以突破一些代理的限制,代理本质上是通过方法重写来实现,如果目标是static的,那么代理是不能增强的

Agent增强

Service类

1
2
3
4
5
6
7
8
9
10
11
12
13
@Slf4j
@Service
public class MyService {
final public void foo() {
log.info("foo()");
bar();
}

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

切面类

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

}

com.itheima.A11Application : service class: class com.itheima.service.MyService
com.itheima.aop.MyAspect : before()
com.itheima.service.MyService : foo()
com.itheima.aop.MyAspect : before()
com.itheima.service.MyService : bar()

前提需要在resource目录下新建 METE-INF 文件夹,并在 目录下新建 aop.xml

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

运行时添加 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 关键词,表示调用的是原类中的方法,而不是代理类中的方法

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

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


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

Foo proxy = (Foo) Proxy.newProxyInstance(JDKProxyDemo.class.getClassLoader(), //加载在运行期间动态生成的字节码文件
new Class[]{Foo.class},
(p, method, args) -> {
System.out.println("before");
Object result = method.invoke(target, args);//让代理返回目标方法执行的结果
System.out.println("after");
return result;
});
proxy.foo();
}
}

before
Target foo
after

代理对象和目标对象类似兄弟关系,都实现了相同接口,因此不能将代理对象强转成目标对象类型

代理类与目标类之间没有继承关系,因此目标类可以被final修饰

CGLib动态代理

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

public static void main(String[] param) {

Target t = (Target) Enhancer.create(Target.class, (MethodInterceptor) (proxy, method, args, methodProxy) -> {
System.out.println("before");
// Object result = method.invoke(new Target(), args); //方法反射调用目标
//可以避免反射调用
// Object result = methodProxy.invoke(new Target(), args); //内部没有用反射,需要目标(Spring)
Object result = methodProxy.invokeSuper(proxy, args);//内部没有用反射,需要代理
System.out.println("after");

return result;
});

t.foo();
}
}

before
foo
after

调用目标方法的方式有三种

1
2
3
4
Object result = method.invoke(new Target(), args); //方法反射调用目标
//methodProxy 可以避免反射调用
Object result = methodProxy.invoke(new Target(), args); //内部没有用反射,需要目标(Spring)
Object result = methodProxy.invokeSuper(proxy, args);//内部没有用反射,需要代理
  • 与 JDK 动态代理相比,CGLib 动态代理无需实现接口

  • 代理对象和目标对象是父子关系,也就是说代理类继承了目标类

  • 由于代理类继承了目标类,因此目标类不能被 final 修饰,否则将报异常信息

  • 代理类继承目标类后,通过重写目标类中的方法进行增强,因此方法不能被 final 修饰,否则将无法被增强,但不会抛出异常

JDK动态代理 原理

模拟JDK动态代理的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class A13 {
interface Foo {
void foo();
}

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

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

代理类

1
2
3
4
5
6
7
8
9
public class $Proxy0 implements Foo {
@Override
public void foo() {
//1.功能增强
System.out.println("before");
//2.调用目标方法
new Target().foo();
}
}

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

interface InvocationHandler {
void invoke();
}

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

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

代理类

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

private InvocationHandler h;

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

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

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

void bar();
}

interface InvocationHandler {
void invoke();
}

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

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

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

代理类

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

private InvocationHandler h;

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

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

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

before
Target.foo
before
Target.foo

proxy.foo()proxy.bar()输出的都是Target.foo,因为实现 InvocationHandlerinvoke() 方法时,依旧只调用了目标类的 foo() 方法,而不是 bar() 方法。

执行invoke时,应该知道调用目标的哪个方法,通过 接口.class.getMethod() 获取,调用``invoke`时传入

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

void bar();
}

interface InvocationHandler {
void invoke(Method method, Object... args) throws Throwable;
}

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

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

public static void main(String[] args) {
Foo proxy = new $Proxy0(new InvocationHandler() {
@Override
public void invoke(Method method, Object... args) throws Throwable {
//1.功能增强
System.out.println("before");
//2.调用目标方法
//new Target().foo();
method.invoke(new Target(), args);
}
});
proxy.foo();
proxy.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
public class $Proxy0 implements Foo {

private InvocationHandler h;

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

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

@Override
public void bar() {
try {
Method bar = Foo.class.getMethod("bar");
h.invoke(bar, new Object[0]);
} catch (Throwable e) {
e.printStackTrace();
}
}
}

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 1;
}
}

目标类可以直接返回,那代理类返回什么?

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

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

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

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

private InvocationHandler h;

static Method foo;

static Method bar;

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

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

@Override
public void foo() {
try {
Method foo = Foo.class.getMethod("foo");
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 {
Method bar = Foo.class.getMethod("bar");
Object result = h.invoke(this, bar, new Object[0]);
return (int) result;
} catch (RuntimeException | Error e) {
throw e;
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
}

同时对异常进行处理,运行时异常直接抛出,编译时异常转换为运行时异常抛出。

将方法对象的获取放入静态代码块中。

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

before…
target foo
before…
target bar
1

对照JDK

到目前为止,我们自定义的InvocationHandler接口,和JDK提供的接口基本无异,注释自定义的InvocationHandler接口,替换为JDK提供的。

在 JDK 提供的 InvocationHandler 接口的注释中有一句:See Also: 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
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
public class $Proxy0 extends Proxy implements Foo {

// private InvocationHandler h;

static Method foo;

static Method bar;

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

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

@Override
public void foo() {
try {
Method foo = Foo.class.getMethod("foo");
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 {
Method bar = Foo.class.getMethod("bar");
Object result = h.invoke(this, bar, new Object[0]);
return (int) result;
} catch (RuntimeException | Error e) {
throw e;
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
}

JDK代理源码

JDK动态代理生成的代理类是以字节码的形式存在的,利用 Arthas 反编译代理类字节码文件.

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

  1. 知道被反编译文件的全限定类名
  2. 程序不能中断,需要存在 Java 进程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class JDKProxyDemo {
interface Foo {
void foo();
}

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


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

Foo proxy = (Foo) Proxy.newProxyInstance(JDKProxyDemo.class.getClassLoader(), //加载在运行期间动态生成的字节码文件
new Class[]{Foo.class},
(p, method, args) -> {
System.out.println("before");
Object result = method.invoke(target, args);//让代理返回目标方法执行的结果
System.out.println("after");
return result;
});
//打印全限定类名
System.out.println(proxy.getClass());
proxy.foo();
//阻塞进程
System.in.read();
}
}

class com.atguigu.a12.$Proxy0
before
Target foo
after

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
package com.atguigu.a12;

import com.atguigu.a12.JDKProxyDemo;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

final class $Proxy0
extends Proxy
implements JDKProxyDemo.Foo {
private static final Method m0;
private static final Method m1;
private static final Method m2;
private static final Method m3;

private static MethodHandles.Lookup proxyClassLookup(MethodHandles.Lookup lookup) throws IllegalAccessException {
if (lookup.lookupClass() == Proxy.class && lookup.hasFullPrivilegeAccess()) {
return MethodHandles.lookup();
}
throw new IllegalAccessException(lookup.toString());
}

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

static {
try {
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m3 = Class.forName("com.atguigu.a12.JDKProxyDemo$Foo").getMethod("foo", new Class[0]);
return;
}
catch (NoSuchMethodException noSuchMethodException) {
throw new NoSuchMethodError(noSuchMethodException.getMessage());
}
catch (ClassNotFoundException classNotFoundException) {
throw new NoClassDefFoundError(classNotFoundException.getMessage());
}
}

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

public final String toString() {
try {
return (String)this.h.invoke(this, m2, null);
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}

public final int hashCode() {
try {
return (Integer)this.h.invoke(this, m0, null);
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}

public final void foo() {
try {
this.h.invoke(this, m3, null);
return;
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
}

Affect(row-cnt:1) cost in 639 ms.
[arthas@2996]$

JDK代理字节码

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

ASM 的学习成本较高,在此不做过多介绍,本节将采用一直“曲线求国”的方式,使用 IDEA 的 Byte Code Analyzer插件将 Java 源码转换成使用 ASM 编写的代码,建议在 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 Method method;

static {
try {
method = 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, method, null);
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
}

编译后,右键 Analyze Byte Code ,查看ASM,拷贝其内容,复制到$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
package com.itheima;

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/itheima/$Proxy0", null, "java/lang/reflect/Proxy", new String[]{"com/itheima/Foo"});

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

{
fieldVisitor = classWriter.visitField(ACC_PRIVATE | ACC_STATIC, "method", "Ljava/lang/reflect/Method;", null, null);
fieldVisitor.visitEnd();
}
{
methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "(Ljava/lang/reflect/InvocationHandler;)V", null, null);
methodVisitor.visitCode();
Label label0 = new Label();
methodVisitor.visitLabel(label0);
methodVisitor.visitLineNumber(25, 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(26, label1);
methodVisitor.visitInsn(RETURN);
Label label2 = new Label();
methodVisitor.visitLabel(label2);
methodVisitor.visitLocalVariable("this", "Lcom/itheima/$Proxy0;", null, label0, label2, 0);
methodVisitor.visitLocalVariable("h", "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(31, label0);
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitFieldInsn(GETFIELD, "com/itheima/$Proxy0", "h", "Ljava/lang/reflect/InvocationHandler;");
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitFieldInsn(GETSTATIC, "com/itheima/$Proxy0", "method", "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(34, label1);
Label label3 = new Label();
methodVisitor.visitJumpInsn(GOTO, label3);
methodVisitor.visitLabel(label2);
methodVisitor.visitLineNumber(32, 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(33, 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(35, 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/itheima/$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(18, label0);
methodVisitor.visitLdcInsn(Type.getType("Lcom/itheima/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/itheima/$Proxy0", "method", "Ljava/lang/reflect/Method;");
methodVisitor.visitLabel(label1);
methodVisitor.visitLineNumber(21, label1);
Label label3 = new Label();
methodVisitor.visitJumpInsn(GOTO, label3);
methodVisitor.visitLabel(label2);
methodVisitor.visitLineNumber(19, 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(20, 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(22, 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();
}
}

反编译后的内容与手动编写的 $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
27
public class TestProxy {
public static void main(String[] args) throws Exception {
byte[] dump = $Proxy0Dump.dump();
// FileOutputStream outputStream = new FileOutputStream("$Proxy0.class");
// outputStream.write(dump);
// outputStream.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.itheima.$Proxy0");
Constructor<?> constructor = proxyClass.getConstructor(InvocationHandler.class);
Foo proxy = (Foo) constructor.newInstance(new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before");
System.out.println("调用目标");
System.out.println("after");
return null;
}
});

proxy.foo();
}
}

before
调用目标
after

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

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 代理类。

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

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

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 指方法描述符;
  • 参数 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);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class TestProxy {
public static void main(String[] args) {
Proxy proxy = new Proxy();
proxy.setMethodInterceptor(new MethodInterceptor() {
@Override
public Object intercept(Object p, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("before");
// return method.invoke(new Target(), args);//反射调用
// return methodProxy.invoke(new Target(), args);//内部无反射,结合目标用
return methodProxy.invokeSuper(p, args);//内部无反射,结合代理用
}
});
proxy.save();
proxy.save(1);
proxy.save(2L);
}
}
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
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");

//获取目标方法的编号
public int getIndex(Signature signature) {
if (s0.equals(signature)) {
return 0;
} else if (s1.equals(signature)) {
return 1;
} else if (s2.equals(signature)) {
return 2;
}
return -1;
}

//根据方法编号,正常调用目标对象方法
public Object invoke(int index, Object target, Object[] args) {
if (index == 0) {
((Target) target).save();
return null;
} else if (index == 1) {
((Target) target).save((int) args[0]);
return null;
} else if (index == 2) {
((Target) target).save((long) args[0]);
return null;
} else {
throw new RuntimeException("无此异常");
}
}

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

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

//获取代理方法的编号
public int getIndex(Signature signature) {
if (s0.equals(signature)) {
return 0;
} else if (s1.equals(signature)) {
return 1;
} else if (s2.equals(signature)) {
return 2;
}
return -1;
}

//根据方法编号,正常调用代理对象方法
public Object invoke(int index, Object proxy, Object[] args) {
if (index == 0) {
((Proxy) proxy).save();
return null;
} else if (index == 1) {
((Proxy) proxy).save((int) args[0]);
return null;
} else if (index == 2) {
((Proxy) proxy).save((long) args[0]);
return null;
} else {
throw new RuntimeException("无此异常");
}
}

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

save()
save(long)

总结

  • 调用 MethodProxy.create() 方法创建 MethodProxy 对象时,要求传递带增强功能的方法名、带原始功能的方法名以及方法描述符。
  • 根据方法名和方法描述符可以在调用生成的两个代理类中的 getIndex() 方法时获取方法的编号,之后:
  • 调用 methodProxy.invoke() 方法时,就相当于调用 TargetFastClass 中的 invoke() 方法,并在这个 invoke() 方法中正常调用目标对象方法(Spring 底层的选择)。
  • 调用 methodProxy.invokeSuper() 方法时,就相当于调用 ProxyFastClass 中的 invoke() 方法,并在这个 invoke() 方法中正常调用代理对象中带原始功能的方法。

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

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

JDK和CGLib的统一

advisor

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
classDiagram

class Advice
class MethodInterceptor
class Advisor
class PointcutAdvisor

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

<<interface>> Advice
<<interface>> MethodInterceptor
<<interface>> Pointcut
<<interface>> Advisor
<<interface>> PointcutAdvisor
  • 切点:即 Pointcut,其典型实现是 AspectJExpressionPointcut

  • 通知:即 Advice,其典型子类接口为 MethodInterceptor,表示环绕通知

  • 切面:即 Advisor,仅包含一个切点和通知

本节将重点介绍 advisor 切面。

切面与代理对象的创建

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

class com.atguigu.a15.A15$Target1$$EnhancerBySpringCGLIB$$933570ca
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());

class com.atguigu.a15.$Proxy0
before
target1 foo
after
target1 bar

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

再设置 factory 对象的 proxyTargetClasstrue

1
factory.setProxyTargetClass(true);

class com.atguigu.a15.A15$Target1$$EnhancerBySpringCGLIB$$933570ca
before…
target1 foo
after…
target1 bar

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

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

class com.atguigu.a15.A15$Target1$$EnhancerBySpringCGLIB$$933570ca
before…
target1 foo
after…
target1 bar

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

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

  • JdkDynamicAopProxy
  • ObjenesisCglibAopProxy
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
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

切点匹配

上一节中,选择 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() {

}
}

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

}
}

true
false
true
true

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

从@Aspect 到 Adivisor

AnnotationAwareAspectJAutoProxyCreator

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

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

org.springframework.aop.interceptor.ExposeInvocationInterceptor.ADVISOR

org.springframework.aop.support.DefaultPointcutAdvisor: pointcut [AspectJExpressionPointcut: () execution(* foo())]; advice [org.springframework.aop.framework.autoproxy.A17$Config$$Lambda$105/0x000001bf4b0fe948@626abbd0]

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

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…

代理对象创建时机

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


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

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 初始化完成后,额外打印了一句日志信息:

1
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 的依赖注入阶段和初始化阶段不应该被增强,仍应被施加于原始对象。

高级切面转低级切面

调用 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);
}
}

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

静态通知调用

统一转换成环绕通知

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

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

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

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

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

通知调用次序

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

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

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

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

1
2
3
4
| 注解 | 原始通知类 | 适配器 |  拦截器 |
| :-----: || :-----: | :-----: | :-----: |
| @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);
}
}

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

调用链执行

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

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

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

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

  • proxy:代理对象
  • target:目标对象
  • method:目标对象中的方法对象
  • arguments:调用目标对象中的方法需要的参数
  • targetClass:目标对象的 Class 对象
  • 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();
}

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

调用链对象明明已经创建好了呀!

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

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

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

这里我们使用 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();
}

before2
before1
around…before
target foo
around…after
afterReturning

模拟实现调用链

调用链执行过程是一个递归过程。执行 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;
}
}
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()

代理对象调用流程

以 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 返回最终的结果

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
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 :
ih ->> +Factory : 获得 MethodInterceptor 链
Factory -->> -ih :
ih ->> +mi : 创建 mi
mi -->> -ih :
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 :

动态通知调用

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

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

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

org.springframework.aop.interceptor.ExposeInvocationInterceptor@12591ac8
org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor@5a7fe64f
org.springframework.aop.framework.InterceptorAndDynamicMethodMatcher@38145825

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

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

普通环绕通知:org.springframework.aop.interceptor.ExposeInvocationInterceptor@5a7fe64f
普通环绕通知:org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor@38145825
环绕通知和切点:org.springframework.aop.framework.InterceptorAndDynamicMethodMatcher@41330d4f
切点为:AspectJExpressionPointcut: (int x) execution(* foo(…)) && args(x)
通知为:org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor@24c1b2d2

根据打印的切点信息可知,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)

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