mini-spring原理解析
导言
在Spring框架中,BeanFactory
是最基本的容器,负责依赖注入和 Bean 的生命周期管理;
ApplicationContext
是建立在这个基础之上的更加丰富和完善的容器环境,不仅包含了 BeanFactory
的所有功能,还扩展了很多企业级支持;
两个主要的后处理器 BeanFactoryPostProcessor
和 BeanPostProcessor
构成了Spring功能扩展的主要分支,通过介入Bean的生命周期和容器的配置处理,进一步增强了Spring容器的灵活性和功能性。
建议
对于源码的阅读,每一个类就像一个螺丝钉,一定要大致了解每个类的用处,然后再去理解源码的执行流程。
一定要重视单元测试,debug打好断点一步步跟着走一遍,当然要先理清核心类的构建思路。
悟从疑得,乐自苦生。
BeanFactory
主要负责单个bean的构建
- bean实例化策略
InstantiationStrategy
- 构造函数实例化
- 无参构造
- 有参构造
- 工厂方式实例化
- 静态工厂方式
- 动态工厂方式
- FactoryBean
- 构造函数实例化
- 作用域
- 懒加载
- 占位符解析(这个跟
BeanFactoryPostProcessor
有关) - 初始化和销毁方法
- 三种方式:xml文件定义属性、实现接口,加注解(第三种基于
BeanPostProcessor
)
- 三种方式:xml文件定义属性、实现接口,加注解(第三种基于
- 类型转换
- 循环依赖(
DefaultSingletonBeanRegister
单例注册表)
ApplicationContext
资源管理体系的集成(资源的识别、加载、解析)
自动生命周期管理
- 自动启动(在启动时自动创建和配置所有的bean,即时实例化)
- 集成的两个后处理器提供更加细致灵活的管理和扩展
[上下文感知](#2. ApplicationContextAware)
额外企业级支持
- 事件和监听机制
- 国际化支持
- 环境抽象
BeanFactoryPostProcessor
[占位符解析](#3. 占位符解析)
PropertyPlaceholderConfigurer
BeanPostProcessor
[注解方式属性注入](#2. 实例化bean,@Value注解扫描,为bean注入属性)
AutowiredAnnotationBeanPostProcessor
注解方式生命周期回调
CommonAnnotationBeanPostProcessor
如
@PostConstruct
、@PreDestroy
-
ApplicationContextAwareProcessor
-
DefaultAdvisorAutoProxyCreator
生命周期回调:Spring 允许Bean在关键生命周期点执行自定义的逻辑,如初始化后或销毁前。
接口
1. 接口的多态性
问题:Java中,一个接口,两个实现了接口的类,这两个类不相关,一个类实现了接口中的方法,一个类调用了接口中的方法,为什么调用接口方法的类能够调用到另一个类中对该接口的实现?
在Java中,多态允许一个接口引用变量来引用多种具体实现的对象,这意味着一个接口的引用可以指向任何实现了该接口的类的对象。这种特性使得程序可以在不改变接口的前提下,使用由不同类创建的对象,从而实现代码的灵活性和复用性。
2. 接口做识别功能
Spring框架中经常使用接口做识别功能
例如:Spring容器通过自动检测实现了特定接口的Bean,然后添加额外逻辑。
当然,开发者也可以借助此功能自定义额外功能扩展
例如:如果开发者想使用 BeanPostProcessor
做额外功能的扩展,可以先做好逻辑,然后重写一个接口继承自 BeanPostProcessor
,实现该接口的独有方法,然后以此为识别条件插入生命周期当中。
Spring-软件设计原则
1. 接口隔离原则
Spring将接口 按功能 划分的尽可能小而精简,避免了不必要的依赖和接口的臃肿。
2. 合成复用原则
Spring框架将应用程序拆分为小的可重用组件,并通过对象组合的方式将这些组件组合在一起构建应用程序。通过组件化设计,使得系统更加模块化、可维护和可扩展。
Spring-设计模式
1. 工厂模式
BeanFactory
和 ApplicationContext
使用工厂模式创建对象,不通过构造函数创建,而将职责移交给内部复杂的IoC容器,大大增强了Spring的模块性、扩展性与灵活性。
二者还将bean的创建细节封装在容器内部,大大简化了bean的配置与管理,使开发者更专注于业务逻辑的实现。
2. 单例模式
每个单例bean只被创建一次,在后续重用。
3. 模板方法模式
定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。
在Bean工厂中模板方法模式运用的非常彻底,调用者直接调用父类的方法,父类方法的具体实现延伸到了子类中来实现。
4. 建造者模式
将一个复杂对象的构建拆解开来,使用指挥者类来调用方法创建对象。doCreateBean()
和 refresh()
方法体现了分步创建复杂对象的思想,将复杂对象的构建拆解开来,通过不同的子过程来”建造”一个完整的Bean对象,用指挥者指挥建造者的方法来调用方法,清晰明了。
5. 外观模式
最典型的就是 BeanFactory
的 getBean()
,封装内部的复杂逻辑,对外只暴露简单的接口。
另一个例子是 ApplicationContext
,它集成了 BeanFactory
功能并提供更高级的功能(如事件发布、国际化支持等),同样为复杂的底层实现提供了简单的调用接口。
6. 策略模式
Spring的实例化策略、资源加载器、AOP代理、类型转换等都可以根据传入参数的不同在运行时决定使用的策略,实现策略的切换。
这种模式解耦了算法的选择和实施的过程,使得算法可以独立于使用它们的客户端变化。策略模式的应用帮助框架提高了灵活性和可扩展性,使得开发者可以根据具体的需求轻松地切换和配置不同的行为。
7. 适配器模式
[bean的销毁方法](#3. 适配器模式的应用):DisposableBeanAdapter
将不同的销毁机制适配到一个统一的接口
注解的解析&类型转换:都通过内部类的方式将不同接口的子实现类结合到一起
8. 观察者模式
9. 代理模式
AOP动态代理
10. 责任链模式
最简单的bean容器
DefaultListableBeanFactory
是 Spring IoC 容器的默认实现之一,具有注册和管理 Bean 定义、创建和管理 Bean 实例、管理 Bean 生命周期等功能。
BeanFactory
的子实现接口:
HierarchicalBeanFactory
用于表示具有层次结构的BeanFactory
ConfigurableBeanFactory
用于表示可配置的BeanFactory
AutowireCapableBeanFactory
用于表示可自动装配的BeanFactory
。可以实现对 Bean 的自动装配,包括按名称、按类型、构造器自动装配等。ListableBeanFactory
用于表示可列举的BeanFactory
。提供了对 Bean 列表进行操作的方法,而不仅仅是获取单个 Bean 实例。ConfigurableListableBeanFactory
继承自以上三个接口,可以实现对BeanFactory
配置和 Bean 列表操作的同时,还支持对 Bean 的自动装配。
Bean实例化策略
InstantiationStrategy
使用了策略模式
InstantiationStrategy
接口
用于定义创建 Bean 实例的策略,例如通过调用构造函数、通过工厂方法、或者通过其他方式创建。
SimpleInstantiationStrategy
Spring 框架中的一个简单的 Bean 实例化策略,用于通过调用类的无参构造方法来创建 Bean 实例。它是默认的 Bean 实例化策略之一,在大多数情况下都可以满足需求。
CglibSubclassingInstantiationStrategy
Spring 框架中用于基于 CGLIB 进行子类化的 Bean 实例化策略,用于创建 Bean 实例的代理对象。
目前只支持无参构造实例化。
注入属性
分为两类
- 为Bean注入普通属性
- 为Bean注入Bean
BeanReference
Spring 框架中用于表示对其他 Bean 的引用的类,它提供了延迟解析的功能,并支持循环依赖的处理。
在 XML 配置文件中,可以使用 <ref>
元素来声明对其他 bean 的引用,而在 Spring 的内部处理过程中,这些引用将会被解析为 BeanReference
对象。
资源和资源加载器
这个也算是一个策略模式
Resource
接口是 Spring 框架中用于表示外部资源的抽象接口,它提供了统一的资源访问接口,并支持不同类型的资源以及与其他 Spring 模块的集成。
ClassPathResource
是 Spring 框架中用于表示类路径下资源的类。FileSystemResource
是 Spring 框架中用于表示文件系统中资源的类。UrlResource
是 Spring 框架中用于表示 URL 资源的类。
这三个类可以使得应用程序能够方便地访问类路径下、文件系统中、各种类型的URL的资源,而不需要关心资源的具体位置和加载方式。这样可以增加应用程序的灵活性和可移植性,同时也使得代码更加清晰和易于维护。(根据IO流获取)
ResourceLoader
是 Spring 框架中的一个接口,用于加载资源。通过它可以方便地获取各种类型的资源对象,从而实现资源的统一管理和使用。
DefaultResourceLoader
是 Spring 框架中资源加载的默认实现,提供了一种简单方便的方式来加载资源,支持多种资源类型,具有良好的扩展性和定制性。
1 | public class ResourceAndResourceLoaderTest { |
执行流程:
- 使用resourceLoader.getResource根据类型返回resuorce接口实现类对象
- 调用resource.getInputStream获得字节输入流,然后解析
XML解析
BeanDefinitionReader
是 Spring 框架中用于读取和解析 Bean 定义信息的接口,它将外部资源中的配置信息解析为内部数据结构,并将其注册到 IoC 容器中,以便后续的 Bean 实例化和管理。
AbstractBeanDefinitionReader
是 Spring 框架中用于读取和解析 Bean 定义信息的抽象基类,提供了一些通用的方法和属性,用于封装读取外部资源和解析 Bean 定义的通用功能,并定义了一些扩展点,支持不同格式配置文件的解析和加载。
模板方法和实际执行方法,例如 loadBeanDefinitions
和 doLoadBeanDefinitions
,还有子类和父类方法的配合
后处理器
两个后处理器何时执行?怎么执行?
首先,二者都要被注入到Spring容器当中
1. BeanFactoryPostProcessor
bean工厂后处理器使用 getBeanOfType()
获得工厂后处理器Bean,然后调用方法for循环全都执行了
2. BeanPostProcessor
bean后处理器也是先被注入,但是要等实例化bean时,在 doCreateBean()
方法中被插入、调用
应用上下文
Application
变动:创建了一个context包
1. 继承体系

2. refresh和getBean
refresh()
方法用于初始化和刷新 Spring 容器,而 getBean()
方法用于从容器中获取 Bean 实例。<span style="color:red">
在实际应用中,通常是先调用 refresh()
方法初始化容器,然后再通过 getBean()
方法获取所需的 Bean 实例来进行业务操作。
例如:
这一步执行 refresh()
方法(内部可能会提前执行一些 getBean()
方法)
1 | ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:auto-proxy.xml"); |
这一步才执行对应的 getBean()
方法
1 | WorldService worldService = applicationContext.getBean("worldService", WorldService.class); |
2.1 getBean从容器中获取Bean实例
DefaultListableBeanFactory的正常加载
当创建容器解析文件后,Bean只是被创建和初始化,尚未被实例化。
当用户传递名称确定要获取的bean时,才会对Bean进行实例化。
具体步骤:见上面
1 |
|
先进行判断单例池中是否存在要查找的对象,若没有,那就根据BeanDefinition实时创建一个,并添加到单例池中。
2.2_refresh初始化和刷新Spring容器
ClassPathXmlApplicationContext的提前加载
通过调用
refresh()
方法,会执行beanFactory.preInstantiateSingletons()
来提前实例化所有的单例 Bean,以提高应用程序的启动性能和响应速度。具体步骤:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public void refresh() throws BeansException {
/*
1. 刷新或创建一个完整的Bean工厂对象
这一个方法相当于DefaultListableBeanFactory的三步
// 1. 先创建一个DefaultListableBeanFactory对象作为容器
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
// 2. 创建读取器并将容器传给它
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// 3. 读取器从xml文件中读取bean定义,并将其加载到容器中
beanDefinitionReader.loadBeanDefinitions("classpath:spring.xml");
*/
refreshBeanFactory();
// 2. 获得刚刚刷新或创建的Bean工厂实例
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
// 3. 在Bean实例化之前,执行BeanFactoryPostProcessor
invokeBeanFactoryPostProcessors(beanFactory);
// 4. BeanPostProcessor需要提前在其他bean实例化之前注册
registerBeanPostProcessors(beanFactory);
// 5. 提前实例化单例Bean,提高应用程序的启动性能和响应速度
beanFactory.preInstantiateSingletons();
}
3. 目前认为的重点
- 设置容器的地方要清楚,通常是
AbstractXXX
抽象类 getBean()
和refresh()
的执行流程要清楚(这个明白后基本也就明白ApplicationContext
和BeanFactory
的区别了)- 提前加载
refresh
、正常加载getBean
、延时加载
初始化和销毁方法
(将初始化和销毁方法添加到doCreate生命周期中)
1. 用到的类
BeanDefinition
中增加了initMethodName
和destroyMethodName
XmlBeanDefinitionReader
增加了对init-method
和destroy-method
属性的读取增加了
DisposableBean
接口和InitializingBean
接口ConfigurableBeanFactory
接口增加了destroySingletons
销毁单例Bean方法DefaultSingletonBeanRegistry
增加了存储可被销毁Bean的容器,增加并实现了注册和销毁Bean的方法新增了
<span style="color:red">DisposableBeanAdapter
类,实现了DisposableBean
接口该类封装了对Bean的销毁过程,用来处理Bean的销毁逻辑。
对于销毁逻辑,运用了适配器模式和桥接模式的思想,同时实现了接口的销毁操作和自定义的销毁操作
- 用到的适配器模式思想:允许那些没有实现
DisposableBean
接口的类,通过指定一个销毁方法名来整合进Spring的生命周期管理。
- 用到的适配器模式思想:允许那些没有实现
AbstractAutowireCapableBeanFactory
通过invokeInitMethods
方法和initializeBean
方法<span style="color:blue">
将初始化操作加到了生命周期中,然后将可被销毁的bean注册到一个专门的注册表中,用来追踪需要执行销毁方法的Bean
2. 重点
对于可被销毁的bean,Spring将其专门在 doCreateBean()
方法中被识别,将其注册在一个专门的集合中,用来理那些具体需要销毁操作的Bean。
这是一个用来追踪、管理和调度销毁逻辑的机制。通过这种机制,再加上 DisposableBeanAdapter
和销毁方法的具体逻辑,确保每个可被销毁的Bean只被注册一次,销毁的时候也只被销毁一次。
3._适配器模式的应用
适配器模式主要用于连接两个不兼容的接口,使它们可以一起工作。在 DisposableBeanAdapter
的情况下,这个模式主要体现在它允许那些没有实现 DisposableBean
接口的类,通过指定一个销毁方法名来整合进Spring的生命周期管理。适配器在这里作用是允许不同类型的对象(实现了 DisposableBean
和未实现的)以统一的方式进行销毁处理。将不同的销毁机制适配到一个统一的接口。
与传统适配器模式的区别
- 接口转换:在传统的适配器模式中,适配器通常是为了转换一个已存在的接口到另一个接口,以满足客户端的期望。而在
DisposableBeanAdapter
的情况中,适配的不是接口之间的直接转换,而是提供一种机制来适配那些未直接实现特定接口(这里是DisposableBean
)的类,使其能够以统一的方式被处理。 - 逻辑增强:
DisposableBeanAdapter
还包含了一些逻辑判断,例如防止同名方法被重复调用的情况,这超出了传统适配器模式的简单接口转换功能。
Aware接口
重点就在一个感知,使实现了接口的类能够访问完整的 BeanFactory
或 ApplicationContext
增加了 Aware
接口、BeanFactoryAware
接口、ApplicationContextAware
接口
1. BeanFactoryAware
实现方式:在 initializeBean
方法中判断bean是否实现了 BeanFactoryAware
接口,如果实现,那就在 initializeBean
方法中 执行该Bean的 setBeanFactory
方法,将 BeanFactory
的引用传递给Bean的成员变量,doCreate
方法将 initializeBean
加入实例化过程中
1 | // AbstractAutowireCapableBeanFactory |
2._ApplicationContextAware
实现方式:通过 BeanPostProcessor
,定义一个 BeanPostProcessor
的实现类 ApplicationContextAwareProcessor
,通过refresh方法将其添加进生命周期中,将 ApplicationConext
赋给bean
1 | // ApplicationContextAwareProcessor |
1 | // AbstractApplicationContext |
设计模式:体现了建造者模式的好处,现在要添加功能,直接实现好子功能,将其添加进指挥者方法中就好,解耦,拓展性强
作用域
增加prototype的支持
- BeanDefiniton加属性和方法(
<span style="color:red">
重点,有点绕)
1 | // BeanDefinition |
- XmlBeanDefinitionReader增加了解析scope的步骤
- 在doCreateBean方法中加了两步判断,添加单例池方法与销毁方法执行之前要先判断该Bean是否为单例,否则不执行这两个方法
1 | // AbstractAutowireCapableBeanFactory |
- 在ApplicationContext提前实例化Bean之前,也加了一步该Bean是否为单例的判断
1 | // DefaultListableBeanFactory |
FactoryBean
自定义一个实现了FactoryBean的工厂类,此工厂方式第一次产生的Bean实例不会存储到单例池singletonObjects中,会存储到 factoryBeanObjectCache 缓存池中。首次请求会创建一个对象并缓存该对象,后续请求将直接把返回缓存的对象存到单例池中,提高性能。
就是一种注入Bean的方法,FactoryBean接口是Spring内部为工厂方法注入提供了内部的实现,使之更规范,更高效。
代码演示(底层+应用):
1 | // FactoryBean接口:一个用于创建特定类型对象(T)的工厂模式接口。实现这个接口允许自定义Bean的创建过程 |
1 | // AbstractBeanFactory:支持单例注册和Bean的配置管理 |
1 | public class CarFactoryBean implements FactoryBean<Car> { |
1 |
|
1 | // Test方法 |
有三种工厂方法实例化的方式,前两种只会使用,factory-bean和factory-method Bean标签底层还没看
容器事件和事件监听器
1._观察者模式
1.1 结构
主要分为四块儿:广播器、发布、事件、监听器
通俗说就是:谁要发、怎么发、发的是什么、谁接收
1.2 实现方式
广播器中创建一个存储监听器的容器,当广播器要广播事件时,遍历容器中的监听器,逐个将事件作为参数传递给监听器的 onApplicationEvent
定义当特定类型的事件发生时应该执行的行为
方法
1.3 Spring对该模式的使用
利用了Spring IoC和DI的特性:
监听器类通过配置文件被Spring容器自动管理
内部将其分为两步
存储监听器阶段
解析完配置文件后,Spring提前创建监听器类,将自定义监听器存储进监听器容器中,方便后续调用
1
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:event-and-event-listener.xml");
发布事件 监听器接收阶段
广播器发送事件后,Spring从监听器容器中获取监听器,并传递事件参数 执行监听器对应方法
1
applicationContext.publishEvent(new CustomEvent(applicationContext));
Spring中内嵌了
上下文刷新事件及监听器
和上下文关闭事件及监听器
这些都在AbstractApplicationContext中配置,如果你忘了,建议看着refresh方法自己过一遍(就在这个类里)
2. 广播器
ApplicationEventMulticaster
广播器顶层接口(聚合了ApplicationListener),实现了广播器最基础的几个方法1
2
3
4
5public interface ApplicationEventMulticaster {
void addApplicationListener(ApplicationListener<?> listener);
void removeApplicationListener(ApplicationListener<?> listener);
void multicastEvent(ApplicationEvent event);
}AbstractApplicationEventMulticaster
,实现BeanFactoryAware接口、存储事件监听器对象1
2
3
4
5public abstract class AbstractApplicationEventMulticaster implements ApplicationEventMulticaster, BeanFactoryAware {
public final Set<ApplicationListener<ApplicationEvent>> applicationListeners = new HashSet<>();
private BeanFactory beanFactory;
//...
}SimpleApplicationEventMulticaster
广播器的简单实现,主要是用来实现将一个特定的事件广播给所有注册的监听器
这个方法1
2
3
4
5
6
7
8// ApplicationEventMulticaster接口方法的实现
public void multicastEvent(ApplicationEvent event) {
for (ApplicationListener<ApplicationEvent> applicationListener : applicationListeners) {
if (supportsEvent(applicationListener, event)) {
applicationListener.onApplicationEvent(event);
}
}
}
3. 发布
分为两种,Spring内嵌发布和手动发布
3.1 内嵌发布
关键类在于 ApplicationContext
ApplicationContext
继承了 ApplicationEventPublisher
,AbstractApplicationContext
实现了该接口的 publishEvent
发布事件方法,用来发布 ContextRefreshedEvent
上下文刷新事件和 ContextClosedEvent
上下文关闭事件。
3.2 手动发布
1 | // 就是自己在Test方法中手动传入自定义事件并将其发布 |
3.3 发布的实现过程
(见广播器第三点)
遍历容器中的监听器,逐个将事件作为参数传递给监听器的 onApplicationEvent
定义当特定类型的事件发生时应该执行的行为
方法,触发该方法说明广播器成功发布,监听器也从BeanFactory中获得了想要的信息
4. 事件+监听器
都分为Spring内嵌和自定义
1 | public abstract class ApplicationEvent extends EventObject { |
1 | public abstract class ApplicationContextEvent extends ApplicationEvent { |
1 | public class ContextClosedEvent extends ApplicationContextEvent { |
1 | public class ContextClosedEventListener implements ApplicationListener<ContextClosedEvent> { |
1 | public class ContextRefreshedEvent extends ApplicationContextEvent { |
1 | public class ContextRefreshedEventListener implements ApplicationListener<ContextRefreshedEvent> { |
1 | public class CustomEvent extends ApplicationContextEvent { |
1 | public class CustomEventListener implements ApplicationListener<CustomEvent> { |
5. 实现BeanFactoryAware接口的作用
以下内容生成字ChatGPT4,截止4.24 13:29,我还没太明白
让我通过一个具体的例子来解释为什么 AbstractApplicationEventMulticaster
实现 BeanFactoryAware
接口是有用的,以及这种感知 BeanFactory
的能力如何在实际应用中发挥作用。
背景
在 Spring 的事件驱动架构中,ApplicationEventMulticaster
负责处理所有的事件发布任务,即将事件从发布者发送到所有相关的监听器。为了做到这一点,事件多播器需要知道所有注册的事件监听器。在大型应用中,监听器可能会很多,并且它们可能有不同的依赖和初始化时间。
使用场景
假设你有一个大型电商平台,其中包含一个订单服务,订单服务在创建订单后会发布一个 OrderCreatedEvent
。你希望在订单创建后执行多个操作,比如发送确认邮件给用户、更新库存系统、记录订单统计信息等。每个操作由不同的服务(或监听器)处理。
实现方式
- 定义事件: 首先定义
OrderCreatedEvent
,这个事件包含了订单的所有相关信息。 - 事件监听器: 你可能会定义多个监听器如
EmailNotificationListener
、InventoryUpdateListener
、OrderStatisticsListener
等,每个监听器监听OrderCreatedEvent
。 - 事件多播器: 你使用
AbstractApplicationEventMulticaster
来广播OrderCreatedEvent
到所有相关的监听器。
BeanFactoryAware 的作用
在这个场景中,AbstractApplicationEventMulticaster
需要知道所有关心 OrderCreatedEvent
的监听器。这些监听器可能是在不同的地方定义并通过 Spring 管理。AbstractApplicationEventMulticaster
实现 BeanFactoryAware
的作用就体现出来了:
- 动态感知监听器:
AbstractApplicationEventMulticaster
可以利用BeanFactory
动态地获取所有注册为ApplicationListener
的 Bean。这意味着无论何时添加新的监听器或移除旧的监听器,AbstractApplicationEventMulticaster
都能即时地更新其监听器列表,无需重新启动应用或重新配置。 - 灵活的依赖注入: 有些监听器可能依赖于其他的服务或组件,这些依赖需要在监听器实例化之前解决。通过
BeanFactory
,AbstractApplicationEventMulticaster
可以确保在调用监听器前,所有依赖都已正确注入。 - 按需加载: 在某些情况下,监听器可能配置为懒加载,即只有在实际需要时才创建实例。
AbstractApplicationEventMulticaster
通过BeanFactory
可以按需创建和获取这些懒加载的监听器,从而优化资源使用和启动时间。
总结
通过实现 BeanFactoryAware
接口,AbstractApplicationEventMulticaster
获得了一个关键的能力:直接访问 Spring 的 BeanFactory
,从而能够动态管理和调用应用中的所有事件监听器。这种设计大大增加了应用的灵活性和可扩展性,使得开发者可以更容易地添加和修改事件监听器,而不会影响到应用的其他部分。这对于维护大型、复杂的 Spring 应用来说是非常重要的。
优化性能
提前将类创建好,使用时直接调用以提高性能加快速度,这体现了什么设计模式或者设计思想呢?
by ChatGPT4
提前将类创建好,使用时直接调用以提高性能和加快速度的做法体现了对象池(Object Pool)模式的设计思想。此外,这种策略还涉及到懒加载(Lazy Initialization)和预先初始化(Eager Initialization)的概念,这两者通常用来平衡启动性能与运行时性能。以下是这些设计模式或设计思想的详细介绍:
1. 对象池模式
对象池模式是一种创建对象并在池中保持它们的设计模式,目的是重用昂贵的对象来提高应用程序的性能和资源利用率。当系统需要一个对象时,它会从池中取出一个,使用后再放回池中而不是销毁。这种模式在需要频繁创建和销毁对象的情况下特别有用,比如数据库连接池。
在 Spring 框架中,单例模式的使用在某种程度上类似于对象池模式,因为每个单例 Bean 只被创建一次,并在整个容器中重用。
2. 懒加载
懒加载是指仅在首次需要时才创建对象的策略。这种方式可以延迟对象的创建成本,直到确实需要该对象时才发生,有助于减少应用程序启动时的初始化负载。Spring 允许 Bean 在被首次访问时才进行初始化,这通常通过设置 lazy-init="true"
来配置。
3. 预先初始化(预加载)
预先初始化或预加载是指在应用程序启动时提前创建并初始化对象,这有助于减少对象首次使用时的延迟。这在 Spring 中通过容器的启动过程中的 Bean 初始化阶段体现,其中所有非懒加载的单例 Bean 都会在应用程序上下文启动时创建和配置。
预先初始化可以减少运行时的延迟,因为所有必需的 Bean 在应用程序需要它们之前已经准备就绪,从而提高了响应性能。然而,这可能会增加应用程序启动时间。
总结
通过预先创建和初始化对象,Spring 通过减少每次对象请求的创建时间来优化性能。这种策略非常适合那些创建成本高但使用频率高的对象。结合懒加载,开发者可以根据具体的性能和资源利用需求灵活选择初始化策略。这两种策略的应用表明了 Spring 的设计非常注重提供灵活的性能优化工具,使得开发者可以根据应用的具体需要来调整其行为。
Spring-AOP
AOP动态代理,这种方式允许在不修改目标对象代码的情况下,动态地添加额外的行为(如日志、事务管理等),这是AOP编程范式的核心优势。
从以下几个角度讲起
- 动态代理-JDK
- 动态代理-CGLIB
- AOP代理工厂
- 常用Advice
PointcutAdvisor
: Pointcut和Advice的组合- 动态代理融入生命周期
- 拦截器链
切点表达式
AspectJExpressionPointcut
1. 原语
原语(Primitives)通常指的是提供基本功能的最小构件。
在 AspectJ(一种面向切面的编程语言)中,切点原语(Pointcut Primitives)是构建切点表达式的基础元素,它们定义了何处和何时应用切面的规则。切点原语直接对应于程序执行的某些具体事件或动作,如方法调用 execution
、方法执行 call
等。
2. 常用对象
2.1 PointcutPrimitive
枚举类型,定义了不同的切点原语(切点的基本类型)
2.2 PointcutParser
解析切点表达式的解析器。它将字符串形式的切点表达式转换成 PointcutExpression
对象
2.3 PointcutExpression
这个类代表一个切点表达式,通常用于存储和管理切点表达式字符串
3. 代码:
1 | public class AspectJExpressionPointcut implements Pointcut, ClassFilter, MethodMatcher { |
动态代理-JDK
<span style="color:red">
强调:JDK动态代理一切基于接口,原始类和代理类是基于同一个接口的不同子实现类,二者之间的转换要通过接口来实现,倘若二者直接相互转换,便会出现报错。
1. 简单示例
(简单了解JDK动态代理)
1 | public class ProxyFactory { |
1.1 缺点
耦合度太高
- 目标对象耦合死了
- 增强逻辑耦合死了
- 逻辑都集中在一个方法中
2. AdvisedSupport
用来封装AOP需要的代理对象信息(目标对象、拦截器、方法匹配器)
AdvisedSupport
TargetSource
MethodInterceptor
MethodMatcher
1 | // "被建议的支持",用来保存AOP相关的配置信息 |
3. ReflectiveMethodInvocation
ReflectiveMethodInvocation
implements MethodInvocation
与Aware接口功能类似,使拦截器能够感知到目标对象、目标方法和方法参数等信息,在保证低耦合的同时还能在重写的invoke方法中调用外部信息,执行更多操作
1 | package org.springframework.aop.framework; |
4. JdkDynamicAopProxy
JdkDynamicAopProxy
implements AopProxy
, InvocationHandler
就是刚刚那段简易示例的Spring-AOP版,解决了高耦合的缺点
1 | /** |
5. InvocationHandler
java.lang.reflect包下的一个接口
InvocationHandler
就像是代理对象的 “管家”,当有人调用代理对象的方法时,实际上是由这个 “管家” 接收到了请求。这个 “管家” 的主要工作就是:
- 接收代理对象的方法调用请求。
- 根据请求的方法和参数,决定如何处理这个调用。
- 最后,返回调用结果给调用者。
定义了一个方法 invoke()
,当代理对象的方法被调用时,invoke()
方法会被调用,代理对象的方法调用会委托给 invoke()
方法来处理。
在 invoke()
方法中,有三个参数:
proxy
:代理对象本身。method
:被调用的方法对象,是被代理对象的一个方法。args
:方法调用时传递的参数。
所以,InvocationHandler
就是一个用来处理代理对象方法调用的接口,它决定了代理对象的行为。
6. 执行流程
开发者实现 MethodInterceptor
接口,重写 invoke
方法自定义逻辑
1 | public class WorldServiceInterceptor implements MethodInterceptor { |
创建 AdvisedSupport
对象,并为之赋值
1 | private AdvisedSupport advisedSupport; |
传递 adviseSupport
参数,执行 getProxy
方法
1 | WorldService proxy = (WorldService) new JdkDynamicAopProxy(advisedSupport).getProxy(); |
getProxy
内部调用 Proxy
.newProxyInstance
方法,创建代理对象
1 |
|
第三个参数为 InvocationHandler
的子实现类,重写 InvocationHandler
的 invoke
方法
1 |
|
调用 advised
.getMethodInterceptor
方法得到用户自定义的拦截器对象
1 | MethodInterceptor methodInterceptor = advised.getMethodInterceptor(); |
调用自定义拦截器的 invoke
方法(此处 invoke
方法的返回值为增强方法的返回值),传递 ReflectiveMethodInvocation
参数(使自定义拦截器能够感知目标对象、目标方法和方法参数等信息)。至此执行完成
1 | return methodInterceptor.invoke(new ReflectiveMethodInvocation(advised.getTargetSource().getTarget(), method, args)); |
7. 部分原理分析
Proxy
.newProxyInstance
方法在内存中动态生成了一个代理对象
7.1 查看内存中动态代理对象
使用阿里巴巴开源的 Java 诊断工具(Arthas【阿尔萨斯】)查看代理类的结构
下载好jar包
打开终端,进入jar包所在目录
运行jar包
1
java -jar arthas-boot.jar
选择内存中的Java进程
1
2
3
4
5[INFO] arthas-boot version: 3.1.1
[INFO] Found existing java process, please choose one and hit RETURN.
* [1]: 23712 org.jetbrains.jps.cmdline.Launcher
[2]: 22388 com.intellij.rt.junit.JUnitStarter
[3]: 20776现在idea中得到代理类的全限定类名,不要让程序停止,否则会释放内存
1
System.out.println(proxyObject.getClass().toString());
然后输入jar + 全限定类名
1
jad jdk.proxy2.$Proxy5
然后终端就会打印出该类的Java代码,完成了!
7.2 动态代理类分析
java.lang.reflect.Proxy
类中,有 InvocationHandler
成员变量
1 | public class Proxy { |
只保留关键信息,动态代理类如下(重点全写注释上了):
1 | // 继承了java.lang.reflect.Proxy类 |
8. 还不会的地方
java.lang.reflect包下
- Method.invoke底层原理(应该要学完JVM再看jdk源码)
- Proxy底层原理(主要是用到了
newProxyInstance
方法)
9. 设计模式
9.1 代理模式
9.2 感知设计模式
Aware和ReflectiveMethodInvocation
这类设计模式使得组件能够感知并操作其所处的上下文环境,从而提供更为灵活和动态的行为。
在某种程度上,ReflectiveMethodInvocation
和 Spring 中的 Aware 接口确实有一些共通之处,因为它们都涉及到对象在运行时对外部资源的感知或依赖。让我们来分析一下它们之间的相似之处:
- 感知外部资源:
Aware
接口:Spring 中的Aware
接口(如ApplicationContextAware
、BeanFactoryAware
)使 Bean 能够感知到容器的存在或者其他资源,比如获取应用程序上下文或者 Bean 工厂。ReflectiveMethodInvocation
:在这行代码中,ReflectiveMethodInvocation
对象被创建,并传递给methodInterceptor
的invoke()
方法。这个对象使拦截器能够感知到目标对象、目标方法和方法参数等信息。
- 依赖注入:
Aware
接口:实现了Aware
接口的类通常会通过依赖注入的方式获取所需的资源。ReflectiveMethodInvocation
:这行代码中的ReflectiveMethodInvocation
对象通过构造函数的方式接收了目标对象、目标方法和方法参数等信息,这可以看作是一种依赖注入的方式。
- 解耦合:
Aware
接口:通过 Aware 接口,Bean 与容器或其他资源之间的耦合度降低了,使得 Bean 更加灵活和可扩展。ReflectiveMethodInvocation
:这行代码中的ReflectiveMethodInvocation
对象充当了方法调用的上下文,使得拦截器可以在方法调用时执行额外的逻辑,而不需要直接与目标对象进行交互,从而降低了拦截器与目标对象之间的耦合度。
虽然这两者在具体实现和应用场景上有所不同,但它们确实都涉及到了对象在运行时对外部资源的感知或依赖,以及为了解耦合而采用的设计思想。
动态代理-CGLIB
AOP代理工厂
判断是否使用cglib代理
1. JDK CGLIB 动态代理比较
面试题
都还学的不够深,jdk要看反射源码去,cglib要去看cglib源码
常用Advice
以 BeforeAdvice
为例
1. 执行流程
(跟JDK动态代理执行流程差不多,中间有点不一样的地方)
开发者实现 MethodBeforeAdvice
,重写 before()
方法
1 | public class WorldServiceBeforeAdvice implements MethodBeforeAdvice { |
关键类 MethodBeforeAdviceInterceptor
拦截器 聚合 MethodBeforeAdvice
通知类,在 invoke()
方法中调用通知类的 before()
方法
1 | // MethodBeforeAdviceInterceptor implements MethodInterceptor 拦截器 |
然后实现通知类,将通知类作为对象new前置方法拦截器,将拦截器赋给 advisedSupport
1 |
|
就是拦截器那里跟JDK动态代理有点不一样,剩下完全一样,将拦截器插入动态代理的过程中
CGLIB动态代理也是一样
PointcutAdvisor: Pointcut和Advice的组合
Spring-AOP配置切面的另一种方式,通过 Advisor
配置
正常来说
PointcutAdvisor
是将 Pointcut
切点表达式对象和 Advice
通知类对象在内部组合起来,开发者只需要设置目标对象和 Advisor
对象 (内部有通知类和切点表达式,通知类在底层会根据接口自动创建拦截器对象)
便能完成AOP的配置。
但是,mini-spring没有实现这部分内容,所以拦截器对象要自己手动设置。
1. <aspect>
和 <advisor>
的区别
代码上 使用上
<aspect>
方式是直接根据 Aware
接口获得的 BeanFactory
对象来给 adviceSupport
传递参数,更加灵活
<advisor>
方式将 Pointcut
和 Advice
封装,只使用 AspectJExpressionPointcutAdvisor
即可,底层通过得到通知类的接口来得到拦截器,对外的接口少,更简单
动态代理融入bean生命周期
先分清aspect标签配置与advisor配置方式的区别
aspect就是什么都自己指定,自己执行通知类型是before还是after等等
advisor通知内部会通过接口的方式来确认通知的类型
所以advisor就可以说是上面的pointcut和advice的组合(待会儿再看看去)
1. 执行流程
xml配置文件
1 |
|
解析配置文件,将以上bean全部注入Spring容器
1 | ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:auto-proxy.xml"); |
进入 getBean()
方法执行流程
1 | WorldService worldService = applicationContext.getBean("worldService", WorldService.class); |
AbstractBeanFactory
中的 getBean()
方法,调用 AbstractAutowireCapableBeanFactory
的 createBean()
方法,传递 beanName
和 beanDefinition
参数(后面需要用它俩来生成)。
1 |
|
调用 resolveBeforeInstantiation()
方法获取实例化好的代理Bean
1 | protected Object resolveBeforeInstantiation(String beanName, BeanDefinition beanDefinition) { |
调用 applyBeanPostProcessorsBeforeInstantiation
方法,遍历已注册的 BeanPostProcessor
,如果存在 BeanPostProcessor
实现了 InstantiationAwareBeanPostProcessor
接口,则说明该 BeanPostProcessor
会内部实现了 postProcessBeforeInstantiation()
方法,能够根据 beanClass
和 beanName
生成增强后的代理对象,从而替代原来的对象。
1 | protected Object applyBeanPostProcessorsBeforeInstantiation(Class beanClass, String beanName) { |
也就是说 DefaultAdvisorAutoProxyCreator
就相当于一个自动创建代理对象的 BeanPostProcessor
,需要在配置文件中将其注入Spring容器。
1 | <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/> |
DefaultAdvisorAutoProxyCreator
实现 InstantiationAwareBeanPostProcessor
接口和 BeanFactoryAware
接口。
postProcessorBeforeInstantiation()
方法实现逻辑,就是通过 Aware
接口感知到的 BeanFactory
来获取Spring容器中拦截器的各种信息,并由此创建出 advisedSupport
,然后使用 ProxyFactory.getProxy()
方法创建出对应的代理对象。
1 | public class DefaultAdvisorAutoProxyCreator implements InstantiationAwareBeanPostProcessor, BeanFactoryAware { |
通过之前的方法一层层将代理对象返回给 creatBean
方法,然后被创建的对象在 AbstractBeanFactory
的 getBean()
方法中调用的 getObjectForBeanInstance()
方法存储进单例池中。
至此流程结束。(但还不完美,如下)
2. 缺陷
代码中
1 | MethodBeforeAdviceInterceptor methodBeforeAdviceInterceptor = new MethodBeforeAdviceInterceptor(new WorldServiceBeforeAdvice()); |
手动将实现了前置通知接口的通知类当作参数赋给Spring内部的 MethodBeforeAdviceInterceptor
类,而 advisor
配置方式应当能够根据接口自动识别要创建的拦截器对象
xml文件中,与Spring官方配置的对比
1 |
|
如果自己补全这一块儿的话:
xml部分,要自己设置一遍 aop-config
对应的xml标签,然后自己封装并解析。
代码部分,要自己能根据自定义通知类实现的接口 实例化对应的拦截器对象并将其传递给 adviceSupport
对象做参数。
有点内容呀,能实现,但是暂时来不及了,现在是4月27号,进度有点慢了,这块儿的存疑及解决方案写的挺清楚了,先了解就好,暂时的重点是过完mini-spring、spring-mvc、spring-boot源码,等这些弄完再回头看,<span style="color:red">
插眼
注解
此处注解的底层实现并融入生命周期 全靠两个后处理器。
常见注解:Spring基础 - Spring 常用七大类注解 | 素文宅 (yoodb.com)
1 | <!--xml文件解析器解析到包--> |
1 | <!--将PropertyPlaceholderConfigurer注入Spring容器中,将properties文件作为参数传递进去--> |
分为三部分:注解替代bean注入,注解替代bean的属性property注入,xml和@Value注解占位符的解析
1._包扫描、@Component注解识别,注入beanDefinition,注册AutowiredAnnotationBeanPostProcessor
向 XmlBeanDefinitionReader
xml解析器类中添加对 component-scan
和 base-package
的解析,接收到 base-package
字符串信息。调用 doScan()
方法,该方法内部就完成了 beanDefinition
的注册等操作。
在xml解析器中调用 ClassPathBeanDefinitionScanner
的 doScan()
方法,使用注解的方式完全替代了xml解析的方式,除了包扫描还需要xml文件来触发。
该部分先扫描包路径下所有标记了 @Component
注解的类,并将其封装为 BeanDefinition
对象。然后针对每一个 BeanDefinition
设置作用域、确定名称、并注册 AutowiredAnnotationBeanPostProcessor
为 BeanDefinition
用于后续的属性注入。
主要涉及 XmlBeanDefinitionReader
,ClassPathBeanDefinitionScanner
,ClassPathScanningCandidateComponentProvider
,详见代码。
2._实例化bean,@Value注解扫描,为bean注入属性
AutowiredAnnotationBeanPostProcessor
在上面被实例化为 BeanDefinition
并注册进 BeanDefinition
容器,后续 refresh()
方法才能通过 beanFactory.preInstantiateSingletons()
提前实例化Bean方法调用并执行后处理器方法,从而做到自动执行。
执行流程
实例化Bean
1 | // AbstractApplicationContext |
内部调用 AbstractAutowireCapableBeanFactory
的 doCreateBean
方法中的 applyBeanPostprocessorsBeforeApplyingPropertyValues
方法,触发 AutowiredAnnotationBeanPostProcessor
bean后处理器的执行,返回被属性注入、占位符被替换为真实值的完整的Bean。
1 | // AbstractAutowireCapableBeanFactory |
通过该接口识别指定解析注解的后处理器
1 | public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor { |
AutowiredAnnotationBeanPostProcessor
postProcessPropertyValues
方法的内部
1 | /** |
添加@Autowired操作逻辑
一、BeanFactory
接口,DefaultListableBeanFactory
类,AbstractApplicationContext
抽象类,多实现了一个 getBeanOfType()
的方法,根据类型获取Bean。、
二、@Autowired
注解、@Qualifier
注解用于实现依赖注入
@Autowired
注解:可以用于类的成员变量、构造函数、设置方法上,用于自动注入依赖。当Spring容器启动时,它会自动查找相应的Bean,并注入到标注了@Autowired
的字段或方法中。默认情况下,Spring按照类型(Type)进行匹配,找到合适的Bean进行注入。@Qualifier
注解:当Spring上下文中存在多个相同类型的Bean时,使用@Autowired
进行注入可能会出现无法确定注入哪一个Bean的情况。此时可以使用@Qualifier
注解来指定注入哪一个Bean。@Qualifier
需要与@Autowired
一起使用,用于指定Bean的名称。
例如:
1 |
|
1 |
|
三、AutowiredAnnotationBeanPostProcessor
类中加入对这两个注解解析的逻辑(见上面的代码)
3._占位符解析
主要分为两部分
3.1 解析xml文件中的占位符
refresh()
方法中自动执行 BeanFactoryPostProcessor
子实现类
1 | public class PropertyPlaceholderConfigurer implements BeanFactoryPostProcessor { |
3.2 解析@Value注解中的占位符
注意,refresh()
方法中,BeanFactoryPostProcessor
是自动执行的,BeanPostProcessor
是在实例化Bean时在 getBean()
方法的 doCreateBean()
方法中自动执行的
基于第二部分,中间穿插了一个解析嵌入值的方法
执行流程
1 | // AutowiredAnnotationBeanPostProcessor implements InstantiationAwareBeanPostProcessor, BeanFactoryAware |
1 | public interface ConfigurableBeanFactory extends HierarchicalBeanFactory, SingletonBeanRegistry { |
遍历嵌入值解析器,调用解析方法 resolveStringValue
1 | // AbstractBeanFactory |
1 | public interface StringValueResolver { |
然后 resolveStringValue()
方法就调用到了 PropertyPlaceholderConfigurer
中对占位符的处理
3.3_适配器模式
通过内部类的方式将两个不同的接口的子实现类结合到了一起。
PlaceholderResolvingStringValueResolver
是 PropertyPlaceholderConfigurer
的内部类,实现了额外的标记接口,调用外部类中的方法作为自己内部对接口的重写方法。
fix
第一次提交改动了一个东西
DefaultAdvisorAutoProxyCreator
和 AbstractAutowireCapableBeanFactory
以前是直接在 creatBean()
方法中单开了一条逻辑处理动态代理,没有注入属性那一步操作。
现在是将对动态代理的操作插入到了 initializeBean()
方法的 BeanPostProcessor
的前置和后置处理中,每一个后处理器都会被应用到每一个bean上,即每个bean都会进入动态代理的判断,判断该bean是否符合切点表达式,如果符合,则返回代理对象,如果不符合,则返回原对象。通过 BeanPostProcessor
真正融入生命周期。
随后进一步完善了Bean的生命周期,增加了前置处理和后置处理的操作
1. 生命周期
Bean的生命周期是一个从创建到销毁的过程,此类中定义的Bean的生命周期如下:
前置处理(Pre-Instantiation)
在Bean实际被创建之前,resolveBeforeInstantiation
方法允许应用BeanPostProcessors
进行预处理,这可能会返回一个完全不同的对象。如果这个方法返回一个代理或增强过的对象,那么后续的标准实例化过程可能会被跳过。实例化(Instantiation)
首先,Spring容器通过BeanDefinition
(包含类的全名、作用域信息、属性描述等)来创建Bean的实例。实例化的方式可能通过构造器实例化、工厂方法实例化或其他方式。在AbstractAutowireCapableBeanFactory
中,实例化策略由instantiationStrategy
决定,并通过createBeanInstance
方法具体执行。后置处理(Post-Instantiation)
通过applyBeanPostProcessorsAfterInstantiation
方法可以进行一些后处理。如果此步骤中任何处理器返回false,将不会继续后续的属性填充。修改属性值
例如将占位符修改为真实值
属性填充(Populate Properties)
如果postProcessAfterInstantiation
方法返回true
(默认行为),Spring将继续进行属性注入。这包括通过反射技术设置Bean的属性值。applyPropertyValues
方法会将BeanDefinition
中定义的属性值应用到新创建的对象上。初始化(Initialization)
初始化是一个多步骤的过程,包括: a.调用BeanPostProcessors
的前置方法。 b.如果Bean实现了InitializingBean
接口,则调用其afterPropertiesSet
方法。 c.如果在BeanDefinition
中指定了自定义的初始化方法,该方法也将被调用。 d.调用BeanPostProcessors
的后置方法。使用(Usage)
这时,Bean已完全准备好,可以被应用程序使用。在这个阶段,Bean处于完全激活状态,可以响应对其方法的调用。销毁(Destruction)
对于单例作用域的Bean,当Spring容器关闭时,容器将调用注册的DisposableBean
的destroy
方法以及自定义的销毁方法。这是清理资源和执行必要的清除任务的好机会。
在整个生命周期中,Spring通过使用多种钩子和操作提供了极大的灵活性,使得可以在创建和管理Bean时插入自定义逻辑和处理流程。AbstractAutowireCapableBeanFactory
是实现这一生命周期复杂管理的核心组件之一,它通过多个扩展点允许开发者定制和优化Bean的行为和生命周期。
2. 前置处理
前置处理指的是在Bean实例化之前的处理。在这个阶段,可以通过Bean的后置处理器(BeanPostProcessor)的 postProcessBeforeInstantiation
方法来进行操作。这个方法允许开发者在Bean的实际实例化之前修改Bean的定义或提供代理实现。这个处理阶段的主要作用和优势包括:
- 修改或替换Bean定义:
- 开发者可以在Bean实例化前修改Bean的定义或配置,如改变Bean的作用域、调整Bean的属性等。
- 可以完全替换掉原来的Bean定义,返回一个全新的代理或其他对象实例,从而影响Spring容器最终所创建的Bean对象。
- 提前介入Bean的创建过程:
- 允许开发者在Bean的任何属性被设置前介入,提供一种方式来执行一些预处理操作,比如检查或准备一些必要的资源或条件。
- 可以用于实现一些基于条件的逻辑,例如根据特定的配置信息或环境变量决定是否或如何创建某个Bean。
- 提供代理实现:
- 常用于AOP(面向切面编程)中,允许在Bean实际创建之前插入代理,从而为Bean提供额外的功能,如安全检查、事务管理、日志记录等。
- 这种代理实现通常涉及创建一个实现了原Bean接口的代理类,这个代理类在调用原始Bean的方法前后可以执行额外的操作。
- 优化性能:
- 通过提前处理或替换Bean,可以避免不必要的Bean实例化,特别是在Bean的创建成本很高或者需要根据条件判断后才决定创建的情况下。
- 这可以显著提高应用启动和运行的效率,尤其是在大型或复杂的Spring应用中。
- 灵活性和可扩展性:
- 提供了一种机制,通过它开发者可以根据需要灵活地控制和扩展Bean的创建过程,使得应用能够更好地适应不断变化的需求和环境。
简单示例:
1 | public class MyBeanPostProcessor implements BeanPostProcessor { |
3. 后置处理
后置处理指的是在Bean实例化之后的处理。这个阶段也是通过实现BeanPostProcessor接口来进行,具体是通过 postProcessAfterInstantiation
方法。在这个方法中,可以对Bean实例进行额外的配置或初始化。这个方法在Bean的属性被设置之后调用,可以用来进行一些依赖注入之外的自定义初始化逻辑。这个阶段的主要作用和优点包括:
- 自定义修改或检查Bean实例:
- 在Bean属性设置之前,可以检查或修改Bean实例。这允许开发者对Bean实例执行一些状态或数据的验证,确保Bean在属性设置前已经满足某些条件。
- 可以用来设置一些不通过Spring配置注入的属性或依赖。
- 条件阻止属性注入:
- 通过返回值可以控制是否允许Spring继续对Bean进行属性注入。如果
postProcessAfterInstantiation
方法返回false
,则Spring容器将不会对该Bean进行后续的属性设置操作,这可以用于条件化地阻止自动装配。
- 通过返回值可以控制是否允许Spring继续对Bean进行属性注入。如果
- 增强Bean的功能:
- 可以在Bean完全配置前增加一些功能,比如对Bean实现一些自动化的配置检测或初始化一些在构造函数中无法完成的复杂成员。
- 保证Bean的完整性和一致性:
- 在Bean的属性被注入前进行操作,可以确保当Bean的属性被注入时,Bean处于一个预期的状态,这对于保证Bean的一致性和完整性至关重要。
简单示例:
1 | public class MyBeanPostProcessor implements BeanPostProcessor { |
类型转换
1. 基础类关系
ConversionService
接口,顶层接口,定义转换方法和是否能转换方法
Converter
接口,一对一转换器接口,用于开发者自己实现
ConverterFactory
接口,一对多转换器接口,开发者自己实现
GenericConverter
接口,通用类型转换器接口
ConverterAdapter
内部类 implements GenericConverter
,聚合了 Converter
ConverterFactoryAdapter
内部类 implements GenericConverter
,聚合了 ConverterFactory
ConverterRedistry
接口,注册接口,定义了写向转换器注册表中添加类型转换器的三个模板方法
GenericConverterService
类 implements ConversionService
,ConverterRedistry
,类型转换中心,用于管理和执行从一个数据类型到另一个数据类型的转换
StringToNumberConverterFactory
类,Spring内嵌的一个 ConverterFactory
子实现类
DefaultConversionService
类,预定义了一个转换器 StringToNumberConverterFactory
,通过默认转换器来简化常见的数据转换任务,允许开发者轻松扩展或定制转换逻辑,以适应不同的应用场景,提高了开发效率和代码的可维护性。
2. 设计模式
2.1_适配器模式
- 使用场景: 用于将一个类的接口转换成客户期望的另一个接口。
Converter
和ConverterFactory
通过ConverterAdapter
和ConverterFactoryAdapter
被适配为GenericConverter
,从而可以统一处理。 - 角色分配:
Converter
和ConverterFactory
: 这些接口定义了简单的转换逻辑和基于类型的工厂转换逻辑。ConverterAdapter
和ConverterFactoryAdapter
: 作为适配器,它们实现了GenericConverter
接口,允许Converter
和ConverterFactory
在GenericConversionService
中统一管理。
2.2 策略模式
- 使用场景: 策略模式用于定义一系列的算法,把它们一个个封装起来,并使它们可相互替换。此模式让算法的变化独立于使用算法的客户。
- 角色分配:
GenericConverter
: 充当具体策略角色,定义了不同的类型转换逻辑。GenericConversionService
: 充当上下文角色,使用不同的GenericConverter
策略来完成类型转换的任务。
2.3 其他
工厂模式、单例模式、模板方法模式这些就不多说了
3. 执行流程
BeanFactory接口
ConfigurableBeanFactory接口
AbstractBeanFactory抽象类
AbstractAutowireCapableBeanFactory抽象类
如何嵌入Bean的生命周期?
主要可以分为四部分:
- 转换器和类型转换中心本身
- 注入
- 设置
- 使用
xml文件
1 |
|
3.1 转换器和类型转换中心本身
GenericConversionService
类型转换中心使用适配器模式整合两种不同的转换器
1 | public class GenericConversionService implements ConversionService, ConverterRegistry { |
<span style="color:red">
ConversionService是类型转换体系的核心接口,通过内部类适配器模式将以上三种类型转换器整合到一起,GenericConversionService是其实现类,DefaultConversionService在GenericConversionService的基础上添加内置转换器。
3.2 注入类型转换中心
首先,ConversionServiceFactoryBean
有两个成员变量,其中一个是 GenericConversionService
类型转换中心,ConversionServiceFactoryBean
将所有的转换器都注册进类型转换中心当中,然后将类型转换中心作为bean注入到Spring容器当中。<span style="color:red">
FactoryBean工厂注入方式 发挥了大作用,工厂方式允许在创建 Bean 实例时进行更复杂的逻辑处理,例如这里就通过该方式为Spring的GenericConversionService类型转换中心注入多个类型转换器,从而提供更大的灵活性和定制性
3.3 设置类型转换中心
AbstractApplicationContext
的 finishBeanFactoryInitialization()
方法中,给 AbstractBeanFactory
设置类型转换中心作为成员变量:先根据 getBean()
方法获取到类型转换中心,然后调用 beanFactory.setConversionService()
方法类型转换中心设置上去。
1 | // AbstractApplicationContext#refresh() |
3.4 使用类型转换中心中的转换器
然后就是使用类型转换中心的转换器了
类型转换的时机有两个:
- 为bean填充属性时,见AbstractAutowireCapableBeanFactory#applyPropertyValues
- 处理@Value注解时,见AutowiredAnnotationBeanPostProcessor#postProcessPropertyValues
以第一个为例,AbstractAutowireCapableBeanFactory#doCreaateBean()
方法中在为Bean填充属性时,执行类型转换操作,注解和这个差不多。
1 | // applyPropertyValues |
3.5 开发者使用
一种是使用Spring默认的,见3.4
另一种就是开发者自定义了,这里举个字符串和日期类型的例子
1 | public class StringToLocalDateConverter implements Converter<String, LocalDate> { |
1 | public class ConvertersFactoryBean implements FactoryBean<Set<?>> { |
1 |
|
4. 总结
4.1 收获
- 对适配器模式、策略模式的应用
- 对
FactoryBean
的使用
4.2 应用场景
前端向后端传递的通常是json格式字符串,需要类型转换
后端开发者的自定义类型转换器
自定义数据类型
当你的应用需要处理特定业务逻辑的自定义数据类型时,如货币、复杂的时间周期、或是特殊的业务标识符等,标准的类型转换器可能不会提供这些类型的转换支持。在这种情况下,开发者需要实现并注册自定义转换器来确保数据可以正确地从一种格式转换为另一种格式。
第三方类库模型
如果你的应用集成了第三方服务或类库,这些外部系统可能使用不同的数据类型或格式。例如,如果一个外部库返回的日期格式是字符串,而你希望在你的系统中使用
java.time.LocalDate
,你可能需要创建一个转换器来处理这种转换。数据库枚举类型处理
4.3 注意
“类型转换实际开发中开发者手动转换可能用不到,因为后端向前端传递数据传的就是字符串,没必要再转换。”这种想法不可以有!
显式进行转换可以提供更好的控制、一致性和灵活性!不然当你要对 getBean()
到的数据做额外处理时,还得再次手动转换,做不到一致性。
循环依赖
<span style="color:blue">
注意:在设计层面上,循环依赖通常是不良设计的标志。可以通过重新设计类的职责、引入中介者模式、使用事件驱动方法或将共同功能抽象到另一个类中来解决循环依赖问题。
循环依赖发生在两个或多个 Bean 互相依赖,形成一个循环链,导致 Bean 无法正常初始化。例如:
- Bean A 依赖于 Bean B
- Bean B 又依赖于 Bean A
Spring使用三级缓存来处理循环依赖,一般二级缓存就够处理循环依赖了,但是面对代理对象,二级缓存中存储的是实例化bean,实际需要的bean是代理bean。三级缓存就是为了解决代理对象的循环依赖问题。
1. 解决循环依赖的前置条件
- Bean 必须是单例范围(
singleton
)。 - 依赖注入的方式不能全是构造器注入的方式
2. 用到的类
一、
InstantiationAwareBeanPostProcessor
接口(创建代理类的类实现的接口),多了个 getEarlyBeanReference()
方法,用于提前暴露bean。
DefaultAdvisorAutoProxyCreator
类(创建代理类的类),实现了上面那个方法,
该类还是创建代理对象的类,只是多了一个关于处理提前创建好的代理对象的逻辑。
1 | // DefaultAdvisorAutoProxyCreator implements InstantiationAwareBeanPostProcessor, BeanFactoryAware |
二、
ObjectFactory
接口,getObject()
方法
唯一一个实现在 addSinglletonFactory()
方法中做参数
三、
单例池(一级缓存)
早期曝光对象(二级缓存)
早期曝光对象工厂(三级缓存)
DefaultSingletonBeanRegistry
类(默认单例注册表,bean容器及对其的基本操作都在这里)
getSingleton()
方法:
- 尝试获取完全初始化的 Bean: 在获取 Bean 的时候,首先会从一级缓存中
singletonObjects
获取。如果有完全初始化的 Bean,则直接返回。 - 尝试获取早期引用的 Bean: 如果一级缓存中没有,则从二级缓存
earlySingletonObjects
获取。如果存在早期引用,则直接返回。 - 从三级缓存创建早期引用: 如果二级缓存也没有,Spring 会从三级缓存
singletonFactories
获取对象工厂ObjectFactory
,通过它来创建早期引用,然后将创建的早期引用放入二级缓存earlySingletonObjects
,并移除三级缓存中的工厂。 - 创建 Bean: 如果以上都没有找到 Bean,Spring 会尝试实例化该 Bean,在实例化的过程中可能会递归地调用其他 Bean。如果过程中遇到了循环依赖的问题,则使用二级缓存
earlySingletonObjects
中的早期引用来解决。 - 后处理器和代理: 如果 Bean 实例化过程中使用了 Bean 后处理器,处理器可能会将 Bean 转换成代理对象。代理对象通常会放入三级缓存,以在循环依赖情况下获取代理对象。
1 | // DefaultSingletonBeanRegistry 默认单例注册表 |
四、
AbstractAutowireCapableBeanFactory
抽象类
生命周期中插入了提前曝光对象的逻辑(在 doCreateBean()
方法当中),变化的总共有三步:
第一步是在bean实例化后立马创建对象工厂
1 | // 紧挨着实例化之后执行 创建早期曝光对象工厂并添加到三级缓存中 |
第二步是填充属性中调用的 getSingleton()
,这一步在上面有
1 | applyPropertyValues # getBean # getSingleton |
第三步是添加bean到单例池中时,直接添加二级缓存中的bean单例,然后将二级缓存中的该bean移除
1 | // doCreatBean() |
1 | // DefaultSingletonBeanRegistry |
3. 简单示例&执行流程
假设类A、B相互依赖
**A.1. **A实例化后立马创建自己的早期曝光对象工厂并添加到三级缓存中
如果A被AOP代理,那么通过这个工厂获取到的就是A代理后的对象,如果A没有被AOP代理,那么这个工厂获取到的就是A实例化的对象(通过 getEarlyBeanReference()
方法实现)
**A.2. **A要填充属性B
**B.1. **B也实例化后立马创建自己的早期曝光对象工厂并添加到三级缓存中
**B.2. **B要填充属性A,调用 applyPropertyValues
# getBean
# getSingleton
,通过 getSingleton()
方法,获得属性A
**B.3. **B成功填充属性,完成B的生命周期,将二级缓存中的B存储到单例池中。
**A.3. **然后A获得完整的B,完成A的生命周期,将二级缓存中的A存储到单例池中。
至此循环依赖处理完成。
当循环依赖遇上JDK动态代理,真是酣畅淋漓的一场debug qwq
注意:JDK动态代理一切基于接口,原始类和代理类是基于同一个接口的不同子实现类,二者之间的转换要通过接口来实现,倘若二者直接相互转换,便会出现报错。
4. 不能解决的循环依赖
Spring 的三级缓存机制无法解决构造函数注入之间的循环依赖。因为在构造函数注入中,Bean 在创建期间无法暴露其早期引用,而构造器注入要求在创建对象时即完成所有依赖的注入。
下面这个链接讲的不错
面试必杀技,讲一讲Spring中的循环依赖 - 程序员DMZ - 博客园 (cnblogs.com)
Spring注入方式
实例化方式有构造器实例化和工厂方式实例化
实例化后要注入属性,主要有
- 构造器注入
- setter方法注入
- 字段注入
1. 构造器注入
1.1 优点
- 确保对象创建时依赖完整:
- 通过构造器传递依赖,确保在对象被实例化时就具备所需的所有依赖。
- 如果缺少必要的依赖项,程序会在构造阶段立即发现问题,有助于编译时错误检测。
- 增强对象的不可变性:
- 通过构造器注入,可以确保被注入的依赖在对象的生命周期内保持不变,设计上更符合不可变性的最佳实践。
- 提高了代码的稳定性和线程安全性,尤其在多线程环境中显得重要。
- 简化测试:
- 构造器注入更便于编写单元测试,因为依赖项可以通过构造器直接传递,而无需借助依赖注入框架或容器。
- 强制依赖声明:
- 构造器注入明确地表明了类的依赖关系,开发者无法忽略或遗漏必需的依赖项。
- 有助于理解类的依赖需求,提高代码的可读性。
- 易于调试和维护:
- 因为构造器注入的依赖关系在实例化时就固定下来,所以调试和追踪依赖问题变得更加直接。
- 无需容器依赖:
- 构造器注入避免了在某些情况下必须依赖于容器框架的局限性,允许更灵活地在非容器环境中使用。
1.2 注意
- 如果用构造方法注入的时候,参数过多以至于代码过于臃肿,那么此时你需要考虑这个类的设计是否合理,这个类是否参杂了太多的其他无关功能,这个类是否做到了单一职责。
- Spring 的三级缓存机制无法解决构造函数注入之间的循环依赖。因为在构造函数注入中,Bean 在创建期间无法暴露其早期引用,而构造器注入要求在创建对象时即完成所有依赖的注入。
综合来看,构造器注入是一种非常稳健的依赖注入方式,适合在对象的依赖关系明确且稳定时使用。
2. setter方法注入
适用于当依赖是可选的,或者需要在初始化后动态改变时。
优点:提供更灵活的配置方式,允许对象在创建之后改变依赖关系。
缺点:可能导致对象在完全配置前处于不一致状态。不适用于需要不可变依赖的场景。
3. 字段注入
3.1 缺点
基于字段的依赖注入是一种常见的实现方法。然而,基于字段的依赖注入有几个明显的缺陷:
可测试性受限:
使用字段注入时,依赖通常是通过框架在运行时注入的,这意味着在不启动整个Spring容器的情况下,很难对这些组件进行单元测试。虽然可以使用反射来设置这些字段,但这种方法通常比较繁琐且容易出错。违反封装原则:
字段注入通常需要将字段设置为非私有(例如,包级私有或者受保护的),这违反了面向对象设计的封装原则。封装原则推荐通过构造函数或者方法来管理依赖,这样可以保证类的行为不会被外部错误地改变。隐式依赖关系:
难以理解
新的开发人员或维护人员在阅读或审核代码时,可能不会立即意识到类依赖了哪些其他组件。他们需要深入检查每个字段的注解来确定依赖关系,这增加了理解和维护的难度。
文档和工具支持不足:
构造器或方法注入相比,字段注入可能不会被一些自动化工具或文档生成工具如Javadoc等捕捉到,因为这些工具通常关注公共接口而非内部实现。
相对地,构造器注入通过构造函数显式地声明依赖,使得依赖关系更加明显,便于理解和维护。
框架耦合性增加:
字段注入使得代码与Spring框架更加紧密地耦合在一起。例如,通过使用@Autowired
注解实现的依赖注入使得组件难以脱离Spring环境被复用或迁移到其他框架中。运行时错误的可能性:
如果依赖没有被正确注入,基于字段的注入通常会在运行时才表现出问题,而构造器注入会在应用启动时立即抛出异常,因为依赖关系是通过构造器显式传递的。不支持不可变性:
使用字段注入很难实现不可变的依赖关系,因为字段通常需要在对象创建后设置。相比之下,构造器注入支持创建完全初始化且状态不变的对象,这有助于创建线程安全的应用。
基于这些缺点,许多开发者和Spring的推荐实践趋向于使用构造器注入而不是字段注入,尽管在简单的场景下,字段注入依然是一个快速且方便的选项。
4. 总结
在实际开发中,选择使用哪种注入方式取决于具体的开发需求、代码风格和项目结构。
- 偏好构造器注入:因为它提供了明确的依赖关系,并鼓励不可变设计。
- 适时使用 Setter 注入:在某些配置类或依赖项需要在运行时灵活切换的情况下。
- 谨慎使用字段注入:通常在简单的服务类或工具类中使用,但要确保不会影响测试或代码可维护性。
扯一把 Spring 的三种注入方式,到底哪种注入方式最佳? - 掘金 (juejin.cn)
公司为什么禁止在SpringBoot项目中使用@Autowired注解 - 掘金 (juejin.cn)
懒加载
XmlBeanDefinitionReader
中添加对 lazyInit
属性的解析
beanDefinition
中加入 lazyInit
属性
DefaultListableBeanFactory
中添加对 isLazyInit
的处理
1 |
|
拦截器链
1. ProxyFactory
1 | public class ProxyFactory extends AdvisedSupport { |
2. AdvisedSupport
1 | // 代理方式(默认JDK动态代理) |
3. 基于JDK动态代理
- 获取拦截器链
- 将拦截器统一封装成ReflectiveMethodInvocation
- 执行拦截器链
1 |
|
4. 获取拦截器链
拦截器链工厂,就是将 AdvisedSupport
中所有满足条件的 advisor
都放进一个集合当中。
1 | public class DefaultAdvisorChainFactory implements AdvisorChainFactory { |
5. 将拦截器统一封装成ReflectiveMethodInvocation
完善 ReflectiveMethodInvocation
成员变量,重写 proceed()
方法,使之支持多切面
6. 执行拦截器链
使用递归的方式来使之顺序执行
1 |
|
以两个 After
和两个 before
为例
7._责任链模式
责任链模式是一种行为设计模式,它允许多个对象都有机会处理请求,将这些对象连成一条链。责任链中的每个对象都包含对下一个对象的引用,并在处理请求时要么自行处理,要么将请求传递给链中的下一个对象。
Spring AOP中,递归调用拦截器链中的每一个节点,根据拦截器的实现来决定插入逻辑的顺序。
一是深度,二是广度,知识一定要有体系。
解析xml文件的好几种方式
解析properties文件的方式
解析注解的方式