细磕Spring点滴
Spring概述
一句话的就是:Java体系的里面的另一座高山,不是在于底层多么的硬核,而是在于这个框架体系非常庞大,以至于想要一下次吃透很难。
前置说明
- GitHub上fork了一个5.1.15版本的Spring框架源码(可以参考我的:https://github.com/nimbusking/spring-framework)
- 需要具备一点Gradle的知识:Spring是基于Gradle构建
- Spring调试模块均在项目里面的:spring-nimbusk-为开头的子模块中
- 文章提供的类继承图是通过Idea导出的plantuml图,为了方便更好直观的嵌入我的博客里面,我直接拿来用了。原因是:直接截图没法灵活的修改继承树中的内容,比如加注释、关联注释什么的
很遗憾,plantuml目前不支持从底部到顶部的那种常规的继承图,而是倒过来的,即最顶层的实现不是在底部,而是在顶部,继承树从上往下看。看的时候注意区分一下。
项目编译环境
- 系统:window 11
- Gradle:5.6.4
- IDE:IntelliJ IDEA 2024.3
Spring IOC
1. 什么是 Spring IOC?
- 核心概念:IOC(控制反转)是一种设计思想,将对象的创建和依赖管理交给 Spring 容器处理,而非由开发者手动控制。
- 作用:解耦组件依赖关系,提升代码可维护性和可测试性。
- 实现方式:通过依赖注入(DI)和依赖查找实现。
2. 什么是 Bean?如何定义 Bean?
- Bean:Spring 容器管理的对象,称为 Bean。
- 定义方式:
- XML 配置:
<bean id="..." class="..."/>
- 注解:
@Component
、@Service
、@Repository
、@Controller
- Java 配置类:
@Bean
注解标记方法。
- XML 配置:
3. Bean 的作用域(Scope)有哪些?
- Singleton(默认):单例模式,容器中仅存在一个 Bean 实例。
- Prototype:每次请求都创建一个新实例。
- Request:每个 HTTP 请求创建一个实例(Web 应用)。
- Session:每个 HTTP Session 一个实例(Web 应用)。
- Application:ServletContext 生命周期内一个实例(Web 应用)。
- WebSocket:每个 WebSocket 会话一个实例。
4. 依赖注入(DI)的几种方式
- 构造器注入:通过构造函数传递依赖。
1
2
3
4
5
6public class UserService {
private final UserDao userDao;
public UserService(UserDao userDao) {
this.userDao = userDao;
}
} - Setter 注入:通过 Setter 方法注入依赖。
1
2
3<bean id="userService" class="com.example.UserService">
<property name="userDao" ref="userDao"/>
</bean> - 字段注入(不推荐):通过
@Autowired
直接注入字段。1
2
3
4public class UserService {
private UserDao userDao;
}
5. 自动装配(Autowiring)的模式
- byType:根据类型自动装配(需确保容器中只有一个该类型的 Bean)。
- byName:根据 Bean 名称匹配属性名自动装配。
- constructor:通过构造器参数类型自动装配。
- @Autowired 注解:默认按类型注入,可配合
@Qualifier
指定名称。
6. BeanFactory 和 ApplicationContext 的区别
BeanFactory | ApplicationContext |
---|---|
基础 IOC 容器,提供基本功能 | 继承 BeanFactory,扩展更多企业级功能 |
延迟加载 Bean(Lazy Loading) | 支持预加载(启动时初始化单例 Bean) |
无集成事件、AOP 等高级功能 | 支持事件发布、国际化、资源访问等 |
7. Bean 的生命周期
- 实例化:通过构造器或工厂方法创建 Bean 实例。
- 属性注入:填充 Bean 的依赖(通过 Setter 或字段注入)。
- BeanPostProcessor 前置处理:调用
postProcessBeforeInitialization
。 - 初始化:
- 实现
InitializingBean
接口的afterPropertiesSet()
。 - 自定义
init-method
方法。
- 实现
- BeanPostProcessor 后置处理:调用
postProcessAfterInitialization
。 - 使用:Bean 就绪,可被应用使用。
- 销毁:
- 实现
DisposableBean
接口的destroy()
。 - 自定义
destroy-method
方法。
- 实现
8. 如何解决循环依赖?
- 问题场景:BeanA 依赖 BeanB,BeanB 也依赖 BeanA。
- 解决方案:Spring 通过三级缓存解决单例 Bean 的循环依赖:
- 一级缓存(Singleton Objects):存放完全初始化好的 Bean。
- 二级缓存(EarlySingleton Objects):存放半成品 Bean(已实例化但未初始化)。
- 三级缓存(Singleton Factories):存放 Bean 的工厂对象,用于提前暴露 Bean 引用。
- 仅支持单例作用域的循环依赖,原型(Prototype)作用域无法解决。
9. @Autowired 和 @Resource 的区别
@Autowired | @Resource |
---|---|
Spring 框架提供的注解 | JSR-250 标准注解 |
默认按类型注入 | 默认按名称注入(可指定 name ) |
支持 required=false |
无此属性 |
需配合 @Qualifier 指定名称 |
直接通过 name 属性指定 |
10. 常见的 IOC 容器实现
- ClassPathXmlApplicationContext:从类路径加载 XML 配置文件。
- FileSystemXmlApplicationContext:从文件系统加载 XML 配置文件。
- AnnotationConfigApplicationContext:基于注解或 Java 配置类初始化容器。
11. 如何自定义 Bean 的初始化与销毁方法?
- XML 配置:
init-method
和destroy-method
属性。1
2<bean id="demoBean" class="com.example.DemoBean"
init-method="init" destroy-method="destroy"/> - 注解:
@PostConstruct
和@PreDestroy
。1
2
3
4
5
6
7public class DemoBean {
public void init() { /* 初始化逻辑 */ }
public void destroy() { /* 销毁逻辑 */ }
}
12. Spring 中使用了哪些设计模式?
- 工厂模式:
BeanFactory
管理 Bean 的创建。 - 单例模式:默认作用域的 Bean 为单例。
- 代理模式:AOP 基于动态代理实现。
- 模板方法模式:
BeanPostProcessor
的初始化回调。
高频扩展问题
- 什么是延迟初始化(Lazy Initialization)?如何配置?
- 通过
@Lazy
注解或 XML 的lazy-init="true"
实现。
- 通过
- 如何通过编程方式获取 Bean?
- 实现
ApplicationContextAware
接口或使用context.getBean()
。
- 实现
- BeanFactory 和 FactoryBean 的区别?
BeanFactory
是容器,FactoryBean
是创建复杂对象的工厂接口。
SpringBean生命周期
以下是 Spring Bean 初始化的底层实现和流程的详细总结,结合源码关键节点和核心类:
核心流程概览
Spring Bean 的初始化流程围绕 AbstractApplicationContext.refresh()
方法展开,核心步骤如下:
- Bean 实例化 → 2. 属性填充 → 3. 初始化前处理 → 4. 初始化 → 5. 初始化后处理
底层源码核心类与方法
- **
AbstractApplicationContext
**:容器刷新入口(refresh()
方法)。 - **
DefaultListableBeanFactory
**:Bean 定义注册与实例化的核心实现。 - **
AbstractAutowireCapableBeanFactory
**:负责 Bean 的创建、属性注入和初始化。createBean()
→doCreateBean()
:核心创建逻辑。initializeBean()
:初始化入口。
- **
BeanPostProcessor
**:初始化前后的扩展点。
详细流程及源码解析
1. Bean 实例化(Instantiation)
- 触发时机:容器启动时(单例 Bean 默认预加载)或首次请求时(原型 Bean)。
- 源码入口:
1
2
3
4
5
6
7
8
9
10
11
12
13// AbstractAutowireCapableBeanFactory.java
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, { Object[] args)
// 1. 实例化:通过反射或工厂方法创建 Bean 实例
BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);
Object bean = instanceWrapper.getWrappedInstance();
// 2. 提前暴露 Bean 引用(解决循环依赖)
if (earlySingletonExposure) {
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// 后续流程...
}- 实例化策略:优先使用构造器注入的解析结果,默认使用无参构造器。
- 循环依赖处理:通过三级缓存(
singletonFactories
)提前暴露半成品 Bean。
2. 属性填充(Population)
- 源码入口:
populateBean()
方法。1
2
3
4
5
6
7
8
9
10protected void populateBean(String beanName, RootBeanDefinition mbd, { BeanWrapper bw)
// 1. 按名称/类型自动装配(Autowired)
if (mbd.getResolvedAutowireMode() == AUTOWIRE_BY_NAME || AUTOWIRE_BY_TYPE) {
autowireByName(beanName, mbd, bw, newPvs);
autowireByType(beanName, mbd, bw, newPvs);
}
// 2. 应用属性值(XML/注解配置的属性)
applyPropertyValues(beanName, mbd, bw, pvs);
}- 依赖注入:通过反射(
Field.set()
或Method.invoke()
)赋值。
- 依赖注入:通过反射(
3. 初始化前处理(Before Initialization)
- 扩展点:
BeanPostProcessor.postProcessBeforeInitialization()
源码入口:applyBeanPostProcessorsBeforeInitialization()
1
2
3
4
5
6
7
8protected Object applyBeanPostProcessorsBeforeInitialization(Object bean, String beanName) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
Object current = bp.postProcessBeforeInitialization(bean, beanName);
if (current == null) return bean;
bean = current;
}
return bean;
}- 典型应用:
CommonAnnotationBeanPostProcessor
:处理@PostConstruct
注解。AutowiredAnnotationBeanPostProcessor
:处理@Autowired
注入。
- 典型应用:
4. 初始化(Initialization)
- 执行顺序(源码入口:
invokeInitMethods()
):- **
InitializingBean.afterPropertiesSet()
**:实现该接口的 Bean 调用此方法。 - **自定义
init-method
**:通过 XML 或@Bean(initMethod = "...")
指定的方法。1
2
3
4
5
6
7
8
9
10
11
12
13protected void invokeInitMethods(String beanName, Object bean, { RootBeanDefinition mbd)
// 1. 调用 InitializingBean 接口
if (bean instanceof InitializingBean) {
((InitializingBean) bean).afterPropertiesSet();
}
// 2. 调用自定义 init-method
String initMethodName = mbd.getInitMethodName();
if (StringUtils.hasLength(initMethodName)) {
Method initMethod = ClassUtils.getMethod(bean.getClass(), initMethodName);
initMethod.invoke(bean);
}
}
- **
5. 初始化后处理(After Initialization)
- 扩展点:
BeanPostProcessor.postProcessAfterInitialization()
源码入口:applyBeanPostProcessorsAfterInitialization()
1
2
3
4
5
6
7
8protected Object applyBeanPostProcessorsAfterInitialization(Object bean, String beanName) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
Object current = bp.postProcessAfterInitialization(bean, beanName);
if (current == null) return bean;
bean = current;
}
return bean;
}- 典型应用:
- AOP 代理创建:
AbstractAutoProxyCreator
在此阶段生成代理对象。 - 事务管理的代理增强。
- AOP 代理创建:
- 典型应用:
IOC相关核心类
Spring IOC 阶段核心类及其工作原理总结
1. BeanFactory
- 职责:IOC 的基础容器,负责 Bean 的实例化、配置和管理。
- 核心实现类:
DefaultListableBeanFactory
- 工作原理:
- BeanDefinition 存储:维护
BeanDefinition
的注册表(beanDefinitionMap
),存储 Bean 的元数据(类名、作用域、属性等)。 - Bean 生命周期管理:通过
getBean()
触发 Bean 的实例化、属性注入、初始化及销毁。 - 依赖解析:处理 Bean 之间的依赖关系,支持构造器注入、Setter 注入等。
- BeanDefinition 存储:维护
- 关键方法:
1
2Object getBean(String name); // 获取 Bean 实例
void registerBeanDefinition(String name, BeanDefinition bd); // 注册 Bean 定义
2. ApplicationContext
- 职责:扩展
BeanFactory
,提供企业级功能(事件发布、资源加载、国际化等)。 - 核心实现类:
ClassPathXmlApplicationContext
(XML 配置)AnnotationConfigApplicationContext
(注解配置)
- 工作原理:
- 容器初始化:在
refresh()
方法中完成 Bean 定义的加载、解析和注册。 - 扩展功能:
- 事件发布:通过
ApplicationEventPublisher
发布事件(如ContextRefreshedEvent
)。 - 资源访问:支持
ResourceLoader
加载文件、类路径资源等。 - AOP 集成:自动识别
BeanPostProcessor
实现(如 AOP 代理创建)。
- 事件发布:通过
- 容器初始化:在
- 关键流程:
1
2
3
4
5
6// AbstractApplicationContext.refresh()
refresh() {
loadBeanDefinitions(); // 加载 Bean 定义
finishBeanFactoryInitialization(); // 初始化所有单例 Bean
publishEvent(new ContextRefreshedEvent(this)); // 发布容器刷新事件
}
3. BeanDefinition
- 职责:定义 Bean 的元数据,包括类名、作用域、属性值、初始化方法等。
- 核心实现类:
RootBeanDefinition
、GenericBeanDefinition
- 关键属性:
beanClass
:Bean 的类类型。scope
:作用域(如 singleton、prototype)。propertyValues
:需要注入的属性值。initMethodName
/destroyMethodName
:初始化和销毁方法名。
- 来源:
- XML 配置解析(
XmlBeanDefinitionReader
)。 - 注解扫描(如
@Component
、@Bean
,由ClassPathBeanDefinitionScanner
处理)。
- XML 配置解析(
4. BeanPostProcessor
- 职责:在 Bean 初始化前后插入自定义逻辑(如代理生成、属性修改)。
- 核心实现类:
AutowiredAnnotationBeanPostProcessor
:处理@Autowired
注入。CommonAnnotationBeanPostProcessor
:处理@PostConstruct
、@PreDestroy
。AbstractAutoProxyCreator
:AOP 代理生成。
- 工作原理:
- 前置处理:
postProcessBeforeInitialization()
在 Bean 初始化方法前调用。 - 后置处理:
postProcessAfterInitialization()
在初始化方法后调用。
- 前置处理:
- 示例流程:
1
2
3
4
5
6
7
8// AbstractAutowireCapableBeanFactory.initializeBean()
Object bean = ...;
// 1. 执行前置处理
bean = applyBeanPostProcessorsBeforeInitialization(bean, beanName);
// 2. 执行初始化方法(如 afterPropertiesSet())
invokeInitMethods(beanName, bean);
// 3. 执行后置处理
bean = applyBeanPostProcessorsAfterInitialization(bean, beanName);
5. BeanFactoryPostProcessor
- 职责:在容器启动时修改
BeanDefinition
(如修改属性值、调整作用域)。 - 核心实现类:
PropertySourcesPlaceholderConfigurer
(解析${}
占位符)。 - 工作原理:
- 在
BeanFactory
初始化后、Bean 实例化前执行。 - 通过
postProcessBeanFactory()
方法修改 Bean 定义。
- 在
- 示例:
1
2
3
4
5
6
7public class CustomBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
BeanDefinition bd = beanFactory.getBeanDefinition("myBean");
bd.setScope(BeanDefinition.SCOPE_PROTOTYPE);
}
}
核心类协作流程
- 容器启动:
ApplicationContext
调用refresh()
,触发BeanDefinition
的加载和注册。
- Bean 定义解析:
BeanDefinitionReader
(如XmlBeanDefinitionReader
)解析配置,生成BeanDefinition
并注册到BeanFactory
。
- BeanFactoryPostProcessor 处理:
- 所有
BeanFactoryPostProcessor
修改BeanDefinition
(如占位符替换)。
- 所有
- Bean 实例化:
- 调用
getBean()
时,DefaultListableBeanFactory
根据BeanDefinition
实例化 Bean。
- 调用
- 依赖注入:
- 通过反射或
BeanPostProcessor
(如AutowiredAnnotationBeanPostProcessor
)注入属性。
- 通过反射或
- BeanPostProcessor 处理:
- 执行初始化前后的增强逻辑(如生成 AOP 代理)。
- Bean 就绪:
- Bean 被放入单例缓存池,供应用程序使用。
解决循环依赖的三级缓存机制
- 三级缓存结构(
DefaultSingletonBeanRegistry
):- 一级缓存
singletonObjects
:存储完全初始化的单例 Bean。 - 二级缓存
earlySingletonObjects
:存储提前暴露的半成品 Bean(已实例化但未初始化)。 - 三级缓存
singletonFactories
:存储ObjectFactory
,用于生成半成品 Bean 的引用。
- 一级缓存
- 流程示例(BeanA 依赖 BeanB,BeanB 依赖 BeanA):
- 步骤 1:实例化 BeanA → 将其
ObjectFactory
放入三级缓存。 - 步骤 2:注入 BeanA 依赖时发现需要 BeanB → 实例化 BeanB。
- 步骤 3:注入 BeanB 依赖时从三级缓存获取 BeanA 的早期引用 → BeanB 初始化完成。
- 步骤 4:BeanA 完成属性注入和初始化 → 移入一级缓存。
- 步骤 1:实例化 BeanA → 将其
总结
- BeanFactory:IOC 基础容器,管理 Bean 的生命周期。
- ApplicationContext:扩展容器,集成企业级功能。
- BeanDefinition:存储 Bean 的元数据。
- BeanPostProcessor:在 Bean 初始化前后插入逻辑。
- BeanFactoryPostProcessor:修改 Bean 定义。
Spring上下文生命周期(细)
SpringBean生命周期
大体分为5个大的步骤,如下图所示:
BeanDefinition定义
BeanDefinition 是 Spring Framework 中定义 Bean 的配置元信息接口,主要包含一下信息:
- Bean 的类名
- Bean 行为配置类,如作用域、自动绑定模式、生命周期回调等
- 其他 Bean 引用,又可称作合作者或者依赖
- 配置设置,比如 Bean 属性
看一下相关的类图:这里面总结来看,按照不同的使用场景可以划分为:
- 基于XML定义的bean:GenericBeanDefinition
- @Component 以及派生注解定义 Bean:ScannedGenericBeanDefinition
- 借助于 @Import 导入 Bean:AnnotatedGenericBeanDefinition
- @Bean 定义的方法:ConfigurationClassBeanDefinition
- 在 Spring BeanFactory 初始化 Bean 的前阶段,会根据 BeanDefinition 生成一个合并后的 RootBeanDefinition 对象
基于XML的Bean的解析
一图总结:
component:scan标签
通过ContextNamespaceHandler注册了很多BeanDefinitionParser类
1 | public class ContextNamespaceHandler extends NamespaceHandlerSupport { |
基于注解的Bean的解析
与XML方式基本类似,大致总结就是:
- 创建AnnotationConfigApplicationContext,来执行解析配置,这里面有俩分支,这俩基本类似,只是起始的步骤不大一样而已:
- 通过ClassPathBeanDefinitionScanner 会去扫描到包路径下所有的 .class 文件。
- 通过AnnotatedBeanDefinitionReader去读指定class及其派生的类信息,从给定的组件类中派生 bean 定义并自动刷新上下文。
- 通过 ASM(Java 字节码操作和分析框架)获取 .class 对应类的所有元信息
- 根据元信息判断是否符合条件(带有 @Component 注解或其派生注解),符合条件则根据这个类的元信息生成一个 BeanDefinition 进行注册
Bean加载过程(IOC核心)
以AnnotationConfigApplicationContext为示例,下面将梳理一下处理过程
this();
初始化准备工作,这里面主要分为三个步骤:
- 初始化核心Bean工厂:this.beanFactory = new DefaultListableBeanFactory();
- 注册内置的后置处理器:this.reader = new AnnotatedBeanDefinitionReader(this);
- 注册包扫描器:this.scanner = new ClassPathBeanDefinitionScanner(this);
具体步骤如下图所示:
关于DefaultListableBeanFactory
IOC容器的核心的Bean工厂实现
关于BeanFactoryPostProcessor
这里面特别需要提一个,就是上小节中第2步中的后置处理器,IOC框架初始化的时候大量使用:
1 | /*** |
register(componentClasses);
主要外层有个循环,调用到DefaultListableBeanFactory#registerBeanDefinition方法里注册BeanDefinition
refresh();
核心的是调用refresh方法,这个方法脉络非常清楚,但调用链非常深,让我们一个个按重点看看。
PS:下文中,重要的核心方法,会配上相关的时序图。
1 |
|
prepareRefresh()
刷新上下文环境的准备工作,记录下容器的启动时间、标记’已启动’状态、对上下文环境属性进行校验
过程比较简单,就不具体贴时序图了
obtainFreshBeanFactory()
这个过程,主要就是创建:ConfigurableListableBeanFactory:一个BeanFactory的子类实现配置接口由大多数可列出的 bean 工厂实现。除了 ConfigurableBeanFactory之外,它还提供了分析和修改 bean 定义以及预实例化单例的工具。
实际上是创建的DefaultListableBeanFactory,上文有说明。
postProcessBeanFactory(beanFactory)
做一些准备工作,为 beanFactory
进行一些准备工作,例如添加几个 BeanPostProcessor,手动注册几个特殊的 Bean,没什么特别需要标注的地方:
invokeBeanFactoryPostProcessors(beanFactory)
执行 BeanFactoryPostProcessor 处理器,包含初始化 BeanDefinitionRegistryPostProcessor 处理器。
这里面用到一个委托Delegate设计模式类:PostProcessorRegistrationDelegate,这个类用于 AbstractApplicationContext 的后处理器处理,这里面有俩核心的public方法:
先看invokeBeanFactoryPostProcessors方法,另外一个在下面介绍:
- 方法签名:
public static void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors)
具体执行步骤如下:
先进行前置判断:beanFactory instanceof BeanDefinitionRegistry
- (true) 执行当前 Spring 应用上下文和底层 BeanFactory 容器中的 BeanFactoryPostProcessor、BeanDefinitionRegistryPostProcessor 们的处理
- 先遍历当前 Spring 应用上下文中的
beanFactoryPostProcessors
,如果是 BeanDefinitionRegistryPostProcessor 类型则进行处理 - 获取底层 BeanFactory 容器中所有 BeanDefinitionRegistryPostProcessor 类型的 Bean 们,遍历处理:PriorityOrdered类型
- 获取底层 BeanFactory 容器中所有 BeanDefinitionRegistryPostProcessor 类型的 Bean 们,遍历处理:PriorityOrdered类型
- 处理上面还没处理的BeanDefinitionRegistryPostProcessor
- 调用接下来执行它们的 postProcessBeanFactory(beanFactory) 方法
- 先遍历当前 Spring 应用上下文中的
- (false) 执行当前 Spring 应用上下文中的 BeanFactoryPostProcessor 处理器的 postProcessBeanFactory(beanFactory) 方法,下面的执行就是执行postProcessBeanFactory方法。
- 获取底层 BeanFactory 容器中所有 BeanFactoryPostProcessor 类型的 Beans
- 循环判断,在实现 PriorityOrdered 的 BeanFactoryPostProcessor 之间分开用单独List存:这个步骤为了后续处理一些排序的BeanFactoryPostProcessor
- 处理PriorityOrdered 类型的 BeanFactoryPostProcessor 对象,缓存并注册初始化
- 处理Ordered 类型的 BeanFactoryPostProcessor 对象,缓存并注册初始化
- 处理nonOrdered 的 BeanFactoryPostProcessor 对象, 缓存并注册初始化
- 清除一些元数据缓存
registerBeanPostProcessors(beanFactory)
跟上面是调用的同一Delegate对象,主要处理:对 BeanPostProcessor 处理器进行初始化,并添加至 BeanFactory 中
这个里面初始化就是调用BeanFacory的getBean方法。
这个getBean处理了常见的循环依赖问题,后文中单独作一小节描述。
initMessageSource()
设置上下文的 MessageSource 对象,常见的类如国际化相关的。
initApplicationEventMulticaster()
设置上下文的 ApplicationEventMulticaster 对象,上下文事件广播器
onRefresh()
刷新上下文时再进行一些初始化工作,交由子类进行扩展
registerListeners()
将所有 ApplicationListener 监听器添加至 applicationEventMulticaster
事件广播器,如果已有事件则进行广播
finishBeanFactoryInitialization(beanFactory)
置 ConversionService 类型转换器,初始化所有还未初始化的 Bean(不是抽象、不是懒加载方式,是单例模式的)。
这里面基本上就是初始化我们自己定义的相关的Bean对象了
看一下里面的具体的调用时序:
关于这个里面创建Bean相关的重要逻辑,在文章后面的单独小节作介绍
finishRefresh()
刷新上下文的最后一步工作,会发布 ContextRefreshedEvent 上下文完成刷新事件
Bean加载之getBean初始化
这个过程相对比较复杂一点,需要处理的东西有点多:
主要涉及的是关于org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
方法的执行步骤
核心代码:
1 | protected <T> T doGetBean(final String name, final Class<T> requiredType, |
三级缓存处理之getSingleton方法
相关代码:
1 | // 处理循环依赖的getSingleton |
这里需要先解释一下三个Map的含义:
- singletonObjects(一级 Map),里面保存了所有已经初始化好的单例 Bean,也就是会保存 Spring IoC 容器中所有单例的 Spring Bean。【这个一定不能少,不然每次获取都得重复创建多费劲】;
- earlySingletonObjects(二级 Map),里面会保存从 三级 Map 获取到的正在初始化的 Bean,即早期初始化的Bean
- singletonFactories(三级 Map),里面保存了正在初始化的 Bean 对应的 ObjectFactory 实现类,调用其 getObject() 方法返回正在初始化的 Bean 对象(仅实例化还没完全初始化好)【同样,从这个属性名字就能看出,这其实是一个工厂的集合】。
一句话概况处理循环依赖的步骤就是:
例如两个 Bean 出现循环依赖,A 依赖 B,B 依赖 A;
- 当我们去依赖查找 A,在实例化后初始化前会先生成一个 ObjectFactory 对象(可获取当前正在初始化 A)保存在上面的 singletonFactories 中,初始化的过程需注入 B;
- 接下来去查找 B,初始 B 的时候又要去注入 A,又去查找 A ,由于可以通过 singletonFactories 直接拿到正在初始化的 A,那么就可以完成 B 的初始化
- 最后也完成 A 的初始化,这样就避免出现循环依赖。
这个过程,Spring处理的还是较为复杂的,如果你看不懂,参考下面这个精简版的示例来辅助理解一下:
1 |
|
实例化创建bean之doCreateBean
前文中,创建bean的过程中还有一块核心的代码,就是Bean的实例化阶段:
先看代码:
1 | // org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean(java.lang.String, org.springframework.beans.factory.support.RootBeanDefinition, java.lang.Object[]) |
一些杂项问题
BeanFactory与FactoryBean
BeanFactory是spring顶层接口,使用了简单工厂模式,负责生产Bean实例
FactoryBean是对普通Bean对象增强的一个接口,实例化这种对象的时候,是会调用到实现的重新方法getObject()方法中初始化对象的。
构造器注入和Setter注入
构造器注入:通过构造器的参数注入相关依赖对象
Setter注入:通过 Setter 方法注入依赖对象,也可以理解为字段注入
对于两种注入方式的看法(所以也可以说不推荐进行属性注入):
- 构造器注入可以避免一些尴尬的问题,比如说状态不确定性地被修改,在初始化该对象时才会注入依赖对象,一定程度上保证了 Bean 初始化后就是不变的对象,这样对于我们的程序和维护性都会带来更多的便利;
- 构造器注入不允许出现循环依赖,因为它要求被注入的对象都是成熟态,保证能够实例化,而 Setter 注入或字段注入没有这样的要求;
- 构造器注入可以保证依赖的对象能够有序的被注入,而 Setter注入或字段注入底层是通过反射机制进行注入,无法完全保证注入的顺序;
- 如果构造器注入出现比较多的依赖导致代码不够优雅,我们应该考虑自身代码的设计是否存在问题,是否需要重构代码结构。
除了上面的注入方式外,Spring 还提供了接口回调注入,通过实现 Aware 接口(例如 BeanNameAware、ApplicationContextAware)可以注入相关对象,Spring 在初始化这类 Bean 时会调用其 setXxx 方法注入对象,例如注入 beanName、ApplicationContext等。
Spring内建的Bean作用域
来源 | 说明 |
---|---|
singleton | 默认 Spring Bean 作用域,一个 BeanFactory 有且仅有一个实例 |
prototype | 原型作用域,每次依赖查找和依赖注入生成新 Bean 对象 |
request | 将 Spring Bean 存储在 ServletRequest 上下文中 |
session | 将 Spring Bean 存储在 HttpSession 中 |
application | 将 Spring Bean 存储在 ServletContext 中 |
BeanPostProcessor与BeanFactoryPostProcessor的区别
- BeanPostProcessor 提供 Spring Bean 初始化前和初始化后的生命周期回调,允许对关心的 Bean 进行扩展,甚至是替换,其相关子类也提供 Spring Bean 生命周期中其他阶段的回调。
- BeanFactoryPostProcessor 提供 Spring BeanFactory(底层 IoC 容器)的生命周期的回调,用于扩展 BeanFactory(实际为 ConfigurableListableBeanFactory),BeanFactoryPostProcessor 必须由 Spring ApplicationContext 执行,BeanFactory 无法与其直接交互。
如何基于 Extensible XML authoring 扩展 Spring XML 元素
对Spring XML扩展
- 编写 XML Schema 文件(XSD 文件):定义 XML 结构
- 自定义 NamespaceHandler 实现:定义命名空间的处理器
- 自定义 BeanDefinitionParser 实现:绑定命名空间下不同的 XML 元素与其对应的解析器
- 注册 XML 扩展(META-INF/spring.handlers 文件):命名空间与命名空间处理器的映射
- 编写 Spring Schema 资源映射文件(META-INF/spring.schemas 文件):XML Schema 文件通常定义为网络的形式,在无网的情况下无法访问,所以一般在本地的也有一个 XSD 文件,可通过编写 spring.schemas 文件,将网络形式的 XSD 文件与本地的 XSD 文件进行映射,这样会优先从本地获取对应的 XSD 文件
Mybatis 对 Spring 的集成项目中的<mybatis:scan />
标签就是这样实现的,可以参考:NamespaceHandler、MapperScannerBeanDefinitionParser、XSD 等文件
简述 Spring 事件机制原理
一个典型的观察者模式的应用
在Spring中实现,首先主要有以下几个角色:
- Spring 事件 - org.springframework.context.ApplicationEvent,实现了 java.util.EventListener 接口
- Spring 事件监听器 - org.springframework.context.ApplicationListener,实现了 java.util.EventObject 类
- Spring 事件发布器 - org.springframework.context.ApplicationEventPublisher
- Spring 事件广播器 - org.springframework.context.event.ApplicationEventMulticaster
Spring有4个内建的事件: - ContextRefreshedEvent:Spring 应用上下文就绪事件
- ContextStartedEvent:Spring 应用上下文启动事件
- ContextStoppedEvent:Spring 应用上下文停止事件
- ContextClosedEvent:Spring 应用上下文关闭事件
Spring 应用上下文就是一个 ApplicationEventPublisher 事件发布器,其内部有一个 ApplicationEventMulticaster 事件广播器(被观察者),里面保存了所有的 ApplicationListener 事件监听器(观察者)。
Spring 应用上下文发布一个事件后会通过 ApplicationEventMulticaster 事件广播器进行广播,能够处理该事件类型的 ApplicationListener 事件监听器则进行处理。
@EventListener的工作原理
@EventListener 用于标注在方法上面,该方法则可以用来处理 Spring 的相关事件。
Spring 内部有一个处理器 EventListenerMethodProcessor,它实现了 SmartInitializingSingleton 接口,在所有的 Bean(不是抽象、单例模式、不是懒加载方式)初始化后,Spring 会再次遍历所有初始化好的单例 Bean 对象时会执行该处理器对该 Bean 进行处理。在 EventListenerMethodProcessor 中会对标注了 @EventListener 注解的方法进行解析,如果符合条件则生成一个 ApplicationListener 事件监听器并注册。
Spring 提供的注解
大致分为5类:
Spring 模式注解
Spring 注解 场景说明 起始版本 @Repository 数据仓储模式注解 2.0 @Component 通用组件模式注解 2.5 @Service 服务模式注解 2.5 @Controller Web 控制器模式注解 2.5 @Configuration 配置类模式注解 3.0 装配注解
Spring 注解 场景说明 起始版本 @ImportResource 替换 XML 元素 2.5 @Import 导入 Configuration 类 2.5 @ComponentScan 扫描指定 package 下标注 Spring 模式注解的类 3.1 依赖注入注解
Spring 注解 场景说明 起始版本 @Autowired Bean 依赖注入,支持多中依赖查找方式 2.5 @Qualifier 细粒度的 @Autowired 依赖查找 2.5 @Enable 模块驱动
Spring 注解 场景说明 起始版本 @EnableWebMvc 启动整个 Web MVC 模块 3.1 @EnableTransactionManagement 启动整个事务管理模块 3.1 @EnableCaching 启动整个缓存模块 3.1 @EnableAsync 启动整个异步处理模块 3.1
@Enable 模块驱动是以 @Enable 为前缀的注解驱动编程模型。所谓“模块”是指具备相同领域的功能组件集合,组合所形成一个独立的单元。比如 Web MVC 模块、AspectJ 代理模块、Caching(缓存)模块、JMX(Java 管理扩展)模块、Async(异步处理)模块等。
这类注解底层原理就是通过 @Import 注解导入相关类(Configuration Class、 ImportSelector 接口实现、ImportBeanDefinitionRegistrar 接口实现),来实现引入某个模块或功能。
- 条件注解
Spring 注解 场景说明 起始版本 @Conditional 条件限定,引入某个 Bean 4.0 @Profile 从 Spring 4.0 开始,@Profile 基于 @Conditional 实现,限定 Bean 的 Spring 应用环境 4.0
Spring 中几种初始化方法的执行顺序
有以下几种初始化方式:
- Aware 接口:实现了 Spring 提供的相关 XxxAware 接口,例如 BeanNameAware、ApplicationContextAware,其 setXxx 方法会被回调,可以注入相关对象
- @PostConstruct 注解:该注解是 JSR-250 的标准注解,Spring 会调用该注解标注的方法
- InitializingBean 接口:实现了该接口,Spring 会调用其 afterPropertiesSet() 方法
- 自定义初始化方法:通过 init-method 指定的方法会被调用
在 Spring 初始 Bean 的过程中上面的初始化方式的执行顺序如下:
- Aware 接口的回调
- JSR-250 @PostConstruct 标注的方法的调用
- InitializingBean#afterPropertiesSet 方法的回调
- init-method 初始化方法的调用
通过 @Bean 注解定义在方法上面注入一个 Spring Bean,每次调用该方法所属的 Bean 的这个方法,得到的是同一个对象吗
不是的,@Configuration Class 在得到 CGLIB 提升后,会设置一个拦截器专门对 @Bean 方法进行拦截处理,通过依赖查找的方式从 IoC 容器中获取 Bean 对象,如果是单例 Bean,那么每次都是返回同一个对象,所以当主动调用这个方法时获取到的都是同一个 User 对象。
SpringMVC
前瞻相关补充
简单介绍一下 Spring MVC 框架
在早期 Java Web 的开发中,统一把显示层、控制层、显示层的操作全部交给 JSP 或者 Java Bean 来进行处理,存在一定的弊端,例如:JSP 和 Java Bean 之间严重耦合、开发效率低等弊端。
Spring MVC 是 Spring 体系中的一员,提供 “模型-视图-控制器”(Model-View-Controller)架构 和随时可用的组件,用于开发灵活且松散耦合的 Web 应用程序。
MVC 模式有助于分离应用程序的不同方面,如输入逻辑,业务逻辑和 UI 逻辑,同时在所有这些元素之间提供松散耦合。
Spring MVC 有什么优点
- 使用真的非常方便,无论是添加 HTTP 请求方法映射的方法,还是不同数据格式的响应。
- 提供拦截器机制,可以方便的对请求进行拦截处理。
- 提供异常机制,可以方便的对异常做统一处理。
- 可以任意使用各种视图技术,而不仅仅局限于 JSP ,例如 Freemarker、Thymeleaf 等等。
描述一下 Spring MVC 的工作流程
Spring MVC 也是基于 Servlet 来处理请求的,主要通过 DispatcherServlet 这个 Servlet 来处理请求,处理过程需要通过九大组件来完成,先看到下面这个流程图:
大体的步骤如下:
- 用户的浏览器发送一个请求,这个请求经过互联网到达了我们的服务器。Servlet 容器(如常见的Tomcat)首先接待了这个请求,并将该请求委托给
DispatcherServlet
进行处理。 - DispatcherServlet 将该请求传给了处理器映射组件
HandlerMapping
,并获取到适合该请求的HandlerExecutionChain
拦截器和处理器对象。 - 在获取到处理器后,DispatcherServlet 还不能直接调用处理器的逻辑,需要进行对处理器进行适配。处理器适配成功后,DispatcherServlet 通过处理器适配器
HandlerAdapter
调用处理器的逻辑,并获取返回值ModelAndView
对象。 - 之后,DispatcherServlet 需要根据 ModelAndView 解析视图。解析视图的工作由
ViewResolver
完成,若能解析成功,ViewResolver 会返回相应的View
视图对象。 - 在获取到具体的
View
对象后,最后一步要做的事情就是由View
渲染视图,并将渲染结果返回给用户。
总结就是:客户端发起请求后,最终会交由 DispatcherServlet 来处理,它会通过你的 URI 找到对应的方法,从请求中解析参数,然后通过反射机制调用该方法,将方法的执行结果设置到响应中,如果存在对应的 View 对象,则进行页面渲染,实际上就是将请求转发到指定的 URL
Spring MVC 的核心组件
九大组件(按使用顺序排序的):
组件 | 说明 |
---|---|
DispatcherServlet | Spring MVC 的核心组件,是请求的入口,负责协调各个组件工作 |
MultipartResolver | 内容类型( Content-Type )为 multipart/* 的请求的解析器,例如解析处理文件上传的请求,便于获取参数信息以及上传的文件 |
HandlerMapping | 请求的处理器匹配器,负责为请求找到合适的 HandlerExecutionChain 处理器执行链,包含处理器(handler)和拦截器们(interceptors) |
HandlerAdapter | 处理器的适配器。因为处理器 handler 的类型是 Object 类型,需要有一个调用者来实现 handler 是怎么被执行。Spring 中的处理器的实现多变,比如用户处理器可以实现 Controller 接口、HttpRequestHandler 接口,也可以用 @RequestMapping 注解将方法作为一个处理器等,这就导致 Spring MVC 无法直接执行这个处理器。所以这里需要一个处理器适配器,由它去执行处理器 |
HandlerExceptionResolver | 处理器异常解析器,将处理器( handler )执行时发生的异常,解析( 转换 )成对应的 ModelAndView 结果 |
RequestToViewNameTranslator | 视图名称转换器,用于解析出请求的默认视图名 |
LocaleResolver | 本地化(国际化)解析器,提供国际化支持 |
ThemeResolver | 主题解析器,提供可设置应用整体样式风格的支持 |
ViewResolver | 视图解析器,根据视图名和国际化,获得最终的视图 View 对象 |
FlashMapManager | FlashMap 管理器,负责重定向时,保存参数至临时存储(默认 Session) |
一些关于SpringMVC的注解
@Controller 注解
@Controller
注解标记一个类为 Spring Web MVC 控制器 Controller。Spring MVC 会将扫描到该注解的类,然后扫描这个类下面带有 @RequestMapping 注解的方法,根据注解信息,为这个方法生成一个对应的处理器对象,在上面的 HandlerMapping 和 HandlerAdapter组件中讲到过。
当然,除了添加 @Controller 注解这种方式以外,你还可以实现 Spring MVC 提供的 Controller 或者 HttpRequestHandler 接口,对应的实现类也会被作为一个处理器对象。
@RequestMapping 注解
@RequestMapping 注解,配置处理器的 HTTP 请求方法,URI等信息,这样才能将请求和方法进行映射。这个注解可以作用于类上面,也可以作用于方法上面,在类上面一般是配置这个控制器的 URI 前缀。方法上的一般具体指定的路径。
@RestController 和 @Controller 的区别
@RestController 注解,在 @Controller 基础上,增加了 @ResponseBody 注解,更加适合目前前后端分离的架构下,提供 Restful API ,返回例如 JSON 数据格式。当然,返回什么样的数据格式,根据客户端的 ACCEPT 请求头来决定。
@RequestMapping 和 @GetMapping 的区别
- @RequestMapping:可注解在类和方法上;@GetMapping 仅可注册在方法上
- @RequestMapping:可进行 GET、POST、PUT、DELETE 等请求方法;
- @GetMapping 是 @RequestMapping 的 GET 请求方法的特例,目的是为了提高清晰度。
@RequestParam 和 @PathVariable的区别
两个注解都用于方法参数,获取参数值的方式不同:@RequestParam 注解的参数从请求携带的参数中获取,而 @PathVariable 注解从请求的 URI 中获取
返回 JSON 格式使用什么注解
可以使用 @ResponseBody 注解,或者使用包含 @ResponseBody 注解的 @RestController 注解。
当然,还是需要配合相应的支持 JSON 格式化的 HttpMessageConverter
实现类。例如,Spring MVC 默认使用 MappingJackson2HttpMessageConverter
WebApplicationContext
WebApplicationContext 是实现 ApplicationContext 接口的子类,专门为 WEB 应用准备的
- 它允许从相对于 Web 根目录的路径中加载配置文件,完成初始化 Spring MVC 组件的工作。
- 从 WebApplicationContext 中,可以获取 ServletContext 引用,整个 Web 应用上下文对象将作为属性放置在 ServletContext 中,以便 Web 应用环境可以访问 Spring 上下文。
Spring MVC 拦截器
Spring MVC 拦截器有三个增强处理的地方:
- 前置处理:在执行方法前执行,全部成功执行才会往下执行方法
- 后置处理:在成功执行方法后执行,倒序
- 已完成处理:不管方法是否成功执行都会执行,不过只会执行前置处理成功的拦截器,倒序
可以通过拦截器进行权限检验,参数校验,记录日志等操作Spring MVC 的拦截器和 Filter 过滤器的区别
- 功能相同:拦截器和 Filter 都能实现相应的功能,谁也不比谁强
- 容器不同:拦截器构建在 Spring MVC 体系中;Filter 构建在 Servlet 容器之上
- 使用便利性不同:拦截器提供了三个方法,分别在不同的时机执行;过滤器仅提供一个方法,当然也能实现拦截器的执行时机的效果,就是麻烦一些
一般拓展性好的框架,都会提供相应的拦截器或过滤器机制,方便的开发人员做一些拓展
SpringMVC核心相关类
SpringMVC 是一个基于请求-响应模型的 Web 框架,其核心类协同工作以处理 HTTP 请求并生成响应。以下是其核心类的工作原理总结:
- 1.
DispatcherServlet
(前端控制器)- 作用:SpringMVC 的入口,统一接收所有 HTTP 请求,并协调各组件完成请求处理。
- 工作原理:
- 初始化时加载
WebApplicationContext
(如HandlerMapping
、HandlerAdapter
等组件)。 - 收到请求后,按顺序调用
HandlerMapping
、HandlerAdapter
、ViewResolver
等组件。 - 处理异常时,通过
HandlerExceptionResolver
统一处理。
- 初始化时加载
- 2.
HandlerMapping
(处理器映射器)- 作用:根据请求的 URL 和配置规则(如
@RequestMapping
),找到对应的处理器(HandlerMethod
)。 - 常见实现:
RequestMappingHandlerMapping
:处理基于注解@RequestMapping
的方法。SimpleUrlHandlerMapping
:基于 XML 或 Java 配置的 URL 映射。
- 流程:
- 解析请求 URL,匹配到对应的
HandlerMethod
(如 Controller 中的方法)。
- 解析请求 URL,匹配到对应的
- 作用:根据请求的 URL 和配置规则(如
- 3.
HandlerAdapter
(处理器适配器)- 作用:调用具体的处理器方法,处理请求参数绑定、方法执行和返回值处理。
- 常见实现:
RequestMappingHandlerAdapter
:适配@Controller
注解的处理器方法。HttpRequestHandlerAdapter
:处理HttpRequestHandler
接口的实现。
- 流程:
- 通过
HandlerMethodArgumentResolver
解析方法参数(如@RequestParam
、@RequestBody
)。 - 执行目标方法(如
Controller.method()
)。 - 通过
HandlerMethodReturnValueHandler
处理返回值(如@ResponseBody
、视图名)。
- 通过
- 4.
HandlerMethodArgumentResolver
(参数解析器)- 作用:将 HTTP 请求的参数(如 URL 参数、请求体、Session 等)绑定到方法的参数上。
- 常见实现:
RequestParamMethodArgumentResolver
:处理@RequestParam
。RequestBodyArgumentResolver
:处理@RequestBody
。ModelAttributeMethodProcessor
:处理@ModelAttribute
。
- 5.
HandlerMethodReturnValueHandler
(返回值处理器)- 作用:处理 Controller 方法的返回值(如 JSON、视图名、
ModelAndView
)。 - 常见实现:
RequestResponseBodyMethodProcessor
:处理@ResponseBody
注解,返回 JSON/XML。ViewNameMethodReturnValueHandler
:处理字符串类型的视图名。ModelAndViewMethodReturnValueHandler
:处理ModelAndView
对象。
- 作用:处理 Controller 方法的返回值(如 JSON、视图名、
- 6.
ViewResolver
(视图解析器)- 作用:将逻辑视图名解析为具体的
View
对象(如 JSP、Thymeleaf 模板)。 - 常见实现:
InternalResourceViewResolver
:解析 JSP 页面。ThymeleafViewResolver
:解析 Thymeleaf 模板。
- 流程:
- 根据视图名找到对应的视图模板文件(如
/WEB-INF/views/{viewName}.jsp
)。
- 根据视图名找到对应的视图模板文件(如
- 作用:将逻辑视图名解析为具体的
- 7.
View
(视图)- 作用:渲染最终的响应内容(HTML、JSON 等)。
- 常见实现:
JstlView
:渲染 JSP 页面。MappingJackson2JsonView
:渲染 JSON 数据。
- 8.
ModelAndViewContainer
(模型与视图容器)- 作用:在请求处理过程中临时存储模型数据(
Model
)和视图信息(View
)。 - 关键方法:
addAttribute()
:添加模型数据。setViewName()
:设置逻辑视图名。
- 作用:在请求处理过程中临时存储模型数据(
- 9.
HandlerExceptionResolver
(异常解析器)- 作用:统一处理 Controller 抛出的异常,返回错误页面或 JSON。
- 常见实现:
ExceptionHandlerExceptionResolver
:处理@ExceptionHandler
注解的方法。ResponseStatusExceptionResolver
:处理@ResponseStatus
注解的异常。DefaultHandlerExceptionResolver
:处理 SpringMVC 内置异常(如 404、500)。
- 10.
CorsInterceptor
(跨域拦截器)- 作用:处理跨域请求(CORS)。
- 流程:
- 在
DispatcherServlet
处理请求前,通过拦截器添加跨域响应头(如Access-Control-Allow-Origin
)。
- 在
整体流程
- 请求入口:
DispatcherServlet
接收请求。 - 处理器映射:
HandlerMapping
找到对应的HandlerMethod
。 - 处理器适配:
HandlerAdapter
执行方法,解析参数并处理返回值。 - 视图解析:
ViewResolver
将逻辑视图名转换为View
对象。 - 响应渲染:
View
渲染模型数据并生成最终响应。 - 异常处理:若过程中发生异常,由
HandlerExceptionResolver
统一处理。
web.xml的生命之旅
Servlet3.0之前
回忆一下那时候,在Java Configuration技术出现之前,还是通过xml文件的方式配置,正如下面一个示例的web.xml配置开始:
1 |
|
在项目里面,我们通过手动继承HttpServlet
类来实现Servlet功能,与之对应的可能还有过滤器的实现:
1 | public class HelloWorldServlet extends HttpServlet { |
就像这样,配置Tomcat运行相关之后,启动通过浏览器指定端口:http://xx:8080/hello 就可以访问到后台返回的“Hello World”了。
Servlet 3.0
关于Servlet 3.0技术特性,这里就不提了,毕竟上古的东西。要知道两点,在Servlet3.0中引入了对注解的支持同时,对ServletContext
的管理也得到了增强,具体就是ServletContext 为动态配置 Servlet 增加了如下方法:
- ServletRegistration.Dynamic addServlet(String servletName,Class<? extends Servlet> servletClass)
- ServletRegistration.Dynamic addServlet(String servletName, Servlet servlet)
- ServletRegistration.Dynamic addServlet(String servletName, String className)
- T createServlet(Class clazz)
- ServletRegistration getServletRegistration(String servletName)
- Map<string,? extends servletregistration> getServletRegistrations()
其中前三个方法的作用是相同的,只是参数类型不同而已;
通过 createServlet() 方法创建的 Servlet,通常需要做一些自定义的配置,然后使用 addServlet() 方法来将其动态注册为一个可以用于服务的 Servlet。
两个 getServletRegistration() 方法主要用于动态为 Servlet 增加映射信息,这等价于在 web.xml( 抑或 web-fragment.xml) 中使用 标签为存在的 Servlet 增加映射信息。
以上 ServletContext 新增的方法要么是在 ServletContextListener
的 contexInitialized 方法中调用,要么是在 ServletContainerInitializer
的 onStartup()
方法中调用。
ServletContainerInitializer 也是 Servlet 3.0 新增的一个接口,容器在启动时使用 JAR 服务 API(JAR Service API) 来发现 ServletContainerInitializer 的实现类,并且容器将 WEB-INF/lib 目录下 JAR 包中的类都交给该类的 onStartup() 方法处理,我们通常需要在该实现类上使用 @HandlesTypes 注解来指定希望被处理的类,过滤掉不希望给 onStartup() 处理的类。
有了上面这个动态添加的机制,我们就可以像下面这样来动态添加一个Servlet服务了:
1 | public class CustomServletContainerInitializer implements ServletContainerInitializer { |
这里还有个问题,正如前文所说,可以通过ServletContainerInitializer
来扫描并添加我们实现的代码,但是怎么让容器发现我们的实现类呢?
这里就需要借助SPI的机制来辅助我们的web容器来发现我们的实现类,具体就是,通过在项目 ClassPath 路径下创建 META-INF/services/javax.servlet.ServletContainerInitializer
文件来做到的,内容如下:
1 | cc.nimbusk.webapp.CustomServletContainerInitializer |
这样一来就可以转起来了,通过ServletContainerInitializer和SPI机制就可以和web.xml说再见了。
Servlet 3.0与Spring整合
说到这里,我们已经可以构建一个动态拓展的Servlet服务了,但是我们回想使用Spring(具体指SpringMVC)这么多年来,似乎已经忘记为啥,不知不觉从以前的web.xml的项目就不用这个xml文件了!
这里,我们很有必要回顾一下,为什么整合到spring之后,不用配置这个文件了。
通常,我们使用springmvc构建javaweb程序的时候,肯定需要依赖一个spring子工程,spring-web,我们看spring-web子工程源码的时候,可以发现,在其classpath下有这么一个文件:
1 | META-INF/services/javax.servlet.ServletContainerInitializer |
这个文件里面有个一行配置:
1 | org.springframework.web.SpringServletContainerInitializer |
如下图所示:
我们来看看,这个类的实现
1 | // 删除了类和方法注释,其实这两段注释,非常详细的解释了这个类以及这个onStartUp方法具体是要干嘛的。 |
这里有几个点需要解释一下:
- @HandlesTypes注解是Servlet3.0中引入的一个新注解,这个注解用来标记表示当前ServletContainerInitializer的实现类,能处理的类型。
- <1> 示我们由于 Servlet 厂商实现的差异,onStartup 方法会加载我们本不想处理的 Class 对象,所以进行了特判。
- Spring 与我们上述提供的 Demo 不同,并没有在 SpringServletContainerInitializer 中直接对 Servlet 和 Filter 进行注册,而是委托给了一个陌生的类
WebApplicationInitializer
,这个类便是 Spring 用来初始化 Web 环境的委托者类。
WebApplicationInitializer
先来看看这个类的继承图:
这里面出现了一个身影:AbstractDispatcherServletInitializer
,这个类如果不熟悉,那大名鼎鼎的DispatcherServlet
你一定不陌生。
而AbstractDispatcherServletInitializer
便是无 web.xml
前提下,创建 DispatcherServlet
的关键,相关代码如下:
1 | // org.springframework.web.servlet.support.AbstractDispatcherServletInitializer |
至此传统的SpringMVC如何跟我们过去的web.xml进行解耦的过程,大致弄清楚了。
但是,创建之旅还需要提前介绍一下SpringBoot是如何加载Sevlet的,毕竟目前而言,很少有项目使用原生的SpringMVC来构建java-web程序的了。
SpringBoot与Servlet创建
先铺垫一下,前文中的传统加载方式,在springboot中的加载流程基本上没有关系,这是一个全新的加载流程。
两种加载方式
- Servlet3.0 注解 +@ServletComponentScan
SpringBoot 依旧兼容 Servlet 3.0 一系列以 @Web* 开头的注解:@WebServlet,@WebFilter,@WebListener
例如:1
2
3
4
5
public class HelloWorldServlet extends HttpServlet{}
public class HelloWorldFilter implements Filter {}
在启动类上面添加 @ServletComponentScan
注解去扫描到这些注解
1 |
|
这种方式相对来说比较简介直观,其中 org.springframework.boot.web.servlet.@ServletComponentScan
注解通过 @Import(ServletComponentScanRegistrar.class)
方式,它会将扫描到的 @WebServlet、@WebFilter、@WebListener
的注解对应的类,最终封装成 FilterRegistrationBean、ServletRegistrationBean、ServletListenerRegistrationBean
对象,注册到 Spring 容器中。
也就是说,和注册方式二:RegistrationBean统一了
- RegistrationBean
例如:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class WebConfig {
public ServletRegistrationBean<HelloWorldServlet> helloWorldServlet() {
ServletRegistrationBean<HelloWorldServlet> servlet = new ServletRegistrationBean<>();
servlet.addUrlMappings("/hello");
servlet.setServlet(new HelloWorldServlet());
return servlet;
}
public FilterRegistrationBean<HelloWorldFilter> helloWorldFilter() {
FilterRegistrationBean<HelloWorldFilter> filter = new FilterRegistrationBean<>();
filter.addUrlPatterns("/hello/*");
filter.setFilter(new HelloWorldFilter());
return filter;
}
}
ServletRegistrationBean
和 FilterRegistrationBean
都继成 RegistrationBean,它是 SpringBoot 中广泛应用的一个注册类,负责把 Servlet,Filter,Listener 给容器化,使它们被 Spring 托管,并且完成自身对 Web 容器的注册。
SpringBoot加载 Servlet 的流程
当使用内嵌的 Tomcat 时,你在 SpringServletContainerInitializer
上面打断点,会发现根本不会进入该类的内部,因为 SpringBoot 完全走了另一套初始化流程,而是进入了 org.springframework.boot.web.embedded.tomcat.TomcatStarter
这个类
SpringBoot在取舍原始java -jar
模式运行的基础上,构建了一个新的加载器:ServletContextInitializer
,和 ServletContainerInitializer 不一样:
- 前者 ServletContextInitializer 是 org.springframework.boot.web.servlet.ServletContextInitializer
- 后者 ServletContainerInitializer 是 javax.servlet.ServletContainerInitializer,前文提到的 RegistrationBean 就实现了 ServletContextInitializer 接口
TomcatStarter 中 org.springframework.boot.context.embedded.ServletContextInitializer[] initializers 属性,是 SpringBoot 初始化 Servlet,Filter,Listener 的关键,代码如下:
1 | class TomcatStarter implements ServletContainerInitializer { |
这里面加载的时候,内嵌容器是通过它在内嵌容器中的实现类:org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext
,来加载 Filter、Servlet 和 Listener 这部分的代码,总结来看,这部分流程就是:
- ServletWebServerApplicationContext 的 onRefresh() 方法触发配置了一个匿名的 ServletContextInitializer
- 这个匿名的 ServletContextInitializer 的 onStartup 方法会去容器中搜索到了所有的 RegisterBean 并按照顺序加载到 ServletContext 中
- 这个匿名的 ServletContextInitializer 最终传递给 TomcatStarter,由 TomcatStarter 的 onStartup 方法去触发 ServletContextInitializer 的 onStartup 方法,最终完成装配
至此前面大致铺垫了相关知识之后,我们开始正式剖析SpringMVC内部的核心要点:
WebApplicationContext容器初始化
先来看一段经典的xml配置的spring-mvc.xml文件:
1 |
|
其中:
- 【1】 处,配置了
org.springframework.web.context.ContextLoaderListener
对象,它实现了 Servlet 的javax.servlet.ServletContextListener
接口,能够监听ServletContext
对象的生命周期,也就是监听 Web 应用的生命周期,当 Servlet 容器启动或者销毁时,会触发相应的 ServletContextEvent 事件,ContextLoaderListener 监听到启动事件,则会初始化一个Root Spring WebApplicationContext 容器,监听到销毁事件,则会销毁该容器 - 【2】 处,配置了
org.springframework.web.servlet.DispatcherServlet
对象,它继承了 javax.servlet.http.HttpServlet 抽象类,也就是一个 Servlet。Spring MVC 的核心类,处理请求,会初始化一个属于它的 Spring WebApplicationContext 容器,并且这个容器是以 【1】 处的 Root 容器作为父容器
Root WebApplicationContext 容器
在前文中的web.xml中可以看到,Root WebApplicationContext 容器的初始化,通过 ContextLoaderListener
来实现。在 Servlet 容器启动时,例如 Tomcat、Jetty 启动后,则会被 ContextLoaderListener 监听到,从而调用 contextInitialized(ServletContextEvent event) 方法,初始化 Root WebApplicationContext 容器。
ContextLoaderListener监听器
我们先看看该监听器的继承图:
org.springframework.web.context.ContextLoaderListener 类,实现 javax.servlet.ServletContextListener 接口,继承 ContextLoader 类,实现 Servlet 容器启动和关闭时,分别初始化和销毁 WebApplicationContext 容器,代码如下:
1 | public class ContextLoaderListener extends ContextLoader implements ServletContextListener { |
我们接着顺着上面代码往里面看看,上述代码的super(context),这个在类ContextLoader中:
1 | // org.springframework.web.context.ContextLoader |
这里有一段静态代码块,加载了一个 ContextLoader.properties
的文件,我们可以打开文件看看:
1 | ## 注意下面的原英文注释 |
这个配置意味着,如果我们没有在 <context-param />
标签中指定 WebApplicationContext,则默认使用 XmlWebApplicationContext
类,我们在使用 Spring 的过程中一般情况下不会主动指定。
ContextLoader剩下的其余构造方法
1 | public class ContextLoader { |
其中:
- 在前文的 web.xml 文件中可以看到定义的
contextConfigLocation
参数为 spring-mybatis.xml 配置文件路径 - currentContextPerThread:用于保存当前 ClassLoader 类加载器与 WebApplicationContext 对象的映射关系
- currentContext:如果当前线程的类加载器就是 ContextLoader 类所在的类加载器,则该属性用于保存 WebApplicationContext 对象
- context:WebApplicationContext 实例对象
initWebApplicationContext(ServletContext servletContext) 方法,初始化 WebApplicationContext 对象,代码如下:
1 | public WebApplicationContext initWebApplicationContext(ServletContext servletContext) { |
配合上文中的中文注释,其中有一块需要额外说明:
第3步,如果context为空,则调用**createWebApplicationContext(ServletContext sc)**方法,初始化一个 Root WebApplicationContext 对象,方法如下:
1
2
3
4
5
6
7
8
9
10
11
12protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
// <1> 获得 context 的类(默认情况是从 ContextLoader.properties 配置文件读取的,为 XmlWebApplicationContext)
Class<?> contextClass = determineContextClass(sc);
// <2> 判断 context 的类,是否符合 ConfigurableWebApplicationContext 的类型
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
}
// <3> 创建 context 的类的对象
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}第4步,如果是
ConfigurableWebApplicationContext
的子类,并且未刷新,则进行配置和刷新:- 如果未刷新(激活),默认情况下,是符合这个条件的,所以会往下执行
- 如果无父容器,则进行加载和设置。默认情况下,loadParentContext(ServletContext servletContext) 方法返回一个空对象,也就是没有父容器了
- 调用**configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc)**方法,配置context对象,并进行刷新
configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) 方法,配置 ConfigurableWebApplicationContext 对象,并进行刷新,方法如下:
1 | protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) { |
其中:
如果 wac 使用了默认编号,则重新设置 id 属性。默认情况下,我们不会对 wac 设置编号,所以会执行进去。而实际上,id 的生成规则,也分成使用 contextId 在
标签中由用户配置,和自动生成两种情况。😈 默认情况下,会走第二种情况。 设置 wac 的 ServletContext 属性
【关键】设置 context 的配置文件地址。例如我们在前文中的 web.xml 中所看到的
1
2
3
4
5<!-- Spring 和 MyBatis 的配置文件 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mybatis.xml</param-value>
</context-param>对 wac 进行定制化处理,暂时忽略
【关键】触发 wac 的刷新事件,执行初始化。此处,就会进行一些的 Spring 容器的初始化工作,涉及到 Spring IOC 相关内容。这个过程就是调用我们在IOC篇章中,看的最多的:org.springframework.context.support.AbstractApplicationContext#refresh方法。至此,SpringMVC和SpringIOC相关打通了。
Servlet WebApplicationContext 容器
咱们接着顺着web.xml的内容,往下看看:
在web.xml中,我们已经看到,除了会初始化一个 Root WebApplicationContext 容器外,还会往 Servlet 容器的 ServletContext 上下文中注入一个 DispatcherServlet 对象,初始化该对象的过程也会初始化一个 Servlet WebApplicationContext 容器。
先看看DispatcherServlet继承图:
可以看到 DispatcherServlet 是一个 Servlet 对象,在注入至 Servlet 容器会调用其 init 方法,完成一些初始化工作
HttpServletBean ,负责将 ServletConfig 设置到当前 Servlet 对象中,它的 Java doc:
1
2
3
4
5
6/**
* Simple extension of {@link javax.servlet.http.HttpServlet} which treats
* its config parameters ({@code init-param} entries within the
* {@code servlet} tag in {@code web.xml}) as bean properties.
*/FrameworkServlet ,负责初始化 Spring Servlet WebApplicationContext 容器,同时该类覆写了 doGet、doPost 等方法,并将所有类型的请求委托给 doService 方法去处理,doService 是一个抽象方法,需要子类实现,它的 Java doc:
1
2
3
4
5/**
* Base servlet for Spring's web framework. Provides integration with
* a Spring application context, in a JavaBean-based overall solution.
*/DispatcherServlet ,负责初始化 Spring MVC 的各个组件,以及处理客户端的请求,协调各个组件工作,它的 Java doc:
1
2
3
4
5
6/**
* Central dispatcher for HTTP request handlers/controllers, e.g. for web UI controllers
* or HTTP-based remote service exporters. Dispatches to registered handlers for processing
* a web request, providing convenient mapping and exception handling facilities.
*/
每个组件都各司其职,下面分别来看看:
HttpServletBean
org.springframework.web.servlet.HttpServletBean 抽象类,实现 EnvironmentCapable、EnvironmentAware 接口,继承 HttpServlet 抽象类,负责将 ServletConfig 集成到 Spring 中
1 | public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware { |
我们重点看一下这个类中的init方法:
1 |
|
其中:
- 解析 Servlet 配置的
<init-param />
标签,封装成 PropertyValues的pvs对象。其中,ServletConfigPropertyValues 是 HttpServletBean 的私有静态类,继承 MutablePropertyValues 类。 - 如果存在
<init-param />
初始化参数:- 将当前的这个 Servlet 对象,转化成一个 BeanWrapper 对象。从而能够以 Spring 的方式来将 pvs 注入到该 BeanWrapper 对象中。简单来说,BeanWrapper 是 Spring 提供的一个用来操作 Java Bean 属性的工具,使用它可以直接修改一个对象的属性。
- 注册自定义属性编辑器,一旦碰到 Resource 类型的属性,将会使用 ResourceEditor 进行解析
- 调用initBeanWrapper(BeanWrapper bw)方法,可初始化当前这个 Servlet 对象,空实现,留给子类覆盖,目前好像还没有子类实现
- 遍历 pvs 中的属性值,注入到该 BeanWrapper 对象中,也就是设置到当前 Servlet 对象中,例如 FrameworkServlet 中的 contextConfigLocation 属性则会设置为上面的 classpath:spring-mvc.xml 值了。
- 【关键】调用initServletBean()方法,空实现,交由子类去实现,完成自定义初始化逻辑,顺着下来,我们看看 FrameworkServlet#initServletBean() 方法
FrameworkServlet
org.springframework.web.servlet.FrameworkServlet 抽象类,实现 ApplicationContextAware 接口,继承 HttpServletBean 抽象类,负责初始化 Spring Servlet WebApplicationContext 容器
构造方法
1 | public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware { |
- contextClass 属性:创建的 WebApplicationContext 类型,默认为 XmlWebApplicationContext.class,在 Root WebApplicationContext 容器的创建过程中也是它
- contextConfigLocation 属性:配置文件的地址,例如:classpath:spring-mvc.xml
- webApplicationContext 属性:WebApplicationContext 对象,Servlet WebApplicationContext 容器,有四种创建方式
- 通过上面的构造方法
- 实现了 ApplicationContextAware 接口,通过 Spring 注入,也就是 setApplicationContext(ApplicationContext applicationContext) 方法
- 通过 findWebApplicationContext() 方法,下文见
- 通过 createWebApplicationContext(WebApplicationContext parent) 方法,下文见
initServletBean方法
initServletBean() 方法,重写父类的方法,在 HttpServletBean 的 init() 方法的最后一步会调用,进一步初始化当前 Servlet 对象,当前主要是初始化Servlet WebApplicationContext 容器。
流程不是特别复杂,就忽略相关代码的展示了。
initWebApplicationContext方法
initWebApplicationContext() 方法【核心】,初始化 Servlet WebApplicationContext 对象,方法如下
1 | protected WebApplicationContext initWebApplicationContext() { |
其中:
- 调用 WebApplicationContextUtils#getWebApplicationContext((ServletContext sc) 方法,从 ServletContext 中获得 Root WebApplicationContext 对象,可以回到ContextLoader#initWebApplicationContext方法中的第 5 步,你会觉得很熟悉
- 获得 WebApplicationContext wac 对象,有三种情况
- 如果构造方法已经传入 webApplicationContext 属性,则直接引用给 wac,也就是上面构造方法中提到的第 1、2 种创建方式
如果 wac 是 ConfigurableWebApplicationContext 类型,并且未刷新(未激活),则调用 configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) 方法,进行配置和刷新,下文见
如果父容器为空,则设置为上面第 1 步获取到的 Root WebApplicationContext 对象。 - 调用 findWebApplicationContext()方法,从 ServletContext 获取对应的 WebApplicationContext 对象,也就是上面构造方法中提到的第 3 种创建方式 知道有这种方式,一般不会这么做的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
protected WebApplicationContext findWebApplicationContext() {
String attrName = getContextAttribute();
// 需要配置了 contextAttribute 属性下,才会去查找,一般我们不会去配置
if (attrName == null) {
return null;
}
// 从 ServletContext 中,获得属性名对应的 WebApplicationContext 对象
WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(getServletContext(), attrName);
// 如果不存在,则抛出 IllegalStateException 异常
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: initializer not registered?");
}
return wac;
} - 调用createWebApplicationContext(@Nullable WebApplicationContext parent)方法,创建一个 WebApplicationContext 对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25protected WebApplicationContext createWebApplicationContext( { ApplicationContext parent)
// <a> 获得 context 的类,XmlWebApplicationContext.class
Class<?> contextClass = getContextClass();
// 如果非 ConfigurableWebApplicationContext 类型,抛出 ApplicationContextException 异常
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException(
"Fatal initialization error in servlet with name '" + getServletName() +
"': custom WebApplicationContext class [" + contextClass.getName() +
"] is not of type ConfigurableWebApplicationContext");
}
// <b> 创建 context 类的对象
ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
// <c> 设置 environment、parent、configLocation 属性
wac.setEnvironment(getEnvironment());
wac.setParent(parent);
String configLocation = getContextConfigLocation();
if (configLocation != null) {
wac.setConfigLocation(configLocation);
}
// <d> 配置和初始化 wac
configureAndRefreshWebApplicationContext(wac);
return wac;
}
- 如果构造方法已经传入 webApplicationContext 属性,则直接引用给 wac,也就是上面构造方法中提到的第 1、2 种创建方式
- 如果未触发刷新事件,则调用 onRefresh(ApplicationContext context) 方法,主动触发刷新事件,该方法为空实现,交由子类 DispatcherServlet 去实现
- 将 context 设置到 ServletContext 中
configureAndRefreshWebApplicationContext方法
1 | protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) { |
这里面关键的就是:触发 wac 的刷新事件,执行初始化。此处,就会进行一些的 Spring 容器的初始化工作,涉及到 Spring IOC 相关内容。
onRefresh方法
onRefresh(ApplicationContext context) 方法,当 Servlet WebApplicationContext 刷新完成后,触发 Spring MVC 组件的初始化,方法如下
1 | /** |
这是一个空方法,具体实现交付给其子类DispatcherServlet中实现了:
1 | // DispatcherServlet.java |
初始化九个组件,这里只是先提一下,在后续的文档中会进行分析
onRefresh方法的触发有两种方式:
- 方式一:如果refreshEventReceived 为 false,也就是未接收到刷新事件(防止重复初始化相关组件),则在 initWebApplicationContext 方法中直接调用
- 方式二:通过在 configureAndRefreshWebApplicationContext 方法中,触发 wac 的刷新事件
方式二为什么可以刷新呢?是因为:
先看到 configureAndRefreshWebApplicationContext 方法的第 3 步,添加了一个 SourceFilteringListener 监听器,如下
1 | // <3> 添加监听器 SourceFilteringListener 到 wac 中 |
监听到相关事件后,会委派给 ContextRefreshListener 进行处理,它是 FrameworkServlet 的私有内部类,来看看它又是怎么处理的,代码如下:
1 | private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> { |
直接将该事件委派给了 FrameworkServlet 的 onApplicationEvent 方法,如下:
1 | public void onApplicationEvent(ContextRefreshedEvent event) { |
就这样,通过一个事件驱动的方式完成了刷新。
至此我们就了解到了传统SpringMVC初始化相关的大部分内容,关于注解的内容,后面结合SpringBoot我们再一起来看。
DispatcherServlet
再次回顾一下前文中的关于一个http请求流的执行流程:
https://nimbusk.cc/post/f5eb228d.html#default-10
Spring MVC 对各个组件的职责划分的比较清晰。
DispatcherServlet 负责协调,其他组件则各自做分内之事,互不干扰。
经过这样的职责划分,代码会便于维护。同时对于源码阅读者来说,也会很友好。可以降低理解源码的难度,使大家能够快速理清主逻辑。这一点值得我们学习。
静态代码块
1 | private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties"; |
这部分会从 DispatcherServlet.properties
文件中加载默认的组件实现类,将相关配置加载到 defaultStrategies 中,文件如下:
1 | # Default implementation classes for DispatcherServlet's strategy interfaces. |
构造方法
1 | /** MultipartResolver used by this servlet. multipart 数据(文件)处理器 */ |
值得注意的是,构造器里面默认初始化设置了一个true值:setDispatchOptionsRequest(true)
这个值呢,初始的时候默认是false的,主要作用:设置当前 Servlet 是否应将 HTTP OPTIONS 请求分派给 doService 该方法,即能否处理Options请求
从 4.3版本 DispatcherServlet 开始,由于FrameworkServlet内置了对 OPTIONS 的支持,因此默认将此属性设置为 “true”。
onRefresh方法
前文已经看过了,初始化每个组件的东西,在下一个小节中单独介绍
doService方法
doService(HttpServletRequest request, HttpServletResponse response)方法,DispatcherServlet 的处理请求的入口方法,代码如下:
1 | protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception { |
其中:
- 调用logRequest(HttpServletRequest request) 方法,如果日志级别为 DEBUG,则打印请求日志
- 保存当前请求中相关属性的一个快照,作为异步处理的属性值,防止被修改,暂时忽略
- 设置 Spring 框架中的常用对象到 request 属性中,例如 webApplicationContext、localeResolver、themeResolver
- FlashMap 的相关配置:先简单的知道,这个是一个Session相关的缓存Map,在处理请求重定向的时候会用到。
- 【重点】调用 doDispatch(HttpServletRequest request, HttpServletResponse response) 方法,执行请求的分发
- 异步处理相关,暂时忽略
doDispatch方法 【核心】
开始之前,再次唠叨回顾一下前文中的SpringMVC请求分发图:
接着我们来看一下这个方法的核心代码:
1 | protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { |
我们来看一下每个步骤的功能:
- 获得 WebAsyncManager 异步处理器,TODO 暂时忽略
- 【文件】 调用 checkMultipart(HttpServletRequest request) 方法,检测请求是否为上传请求,如果是则通过 multipartResolver 组件将其封装成 MultipartHttpServletRequest 对象,便于获取参数信息以及文件
- 【处理器匹配器】 调用 getHandler(HttpServletRequest request) 方法,通过 HandlerMapping 组件获得请求对应的 HandlerExecutionChain 处理器执行链,包含 HandlerMethod 处理器和 HandlerInterceptor 拦截器们。如果获取不到对应的执行链,则根据配置抛出异常或返回 404 错误。
- 【处理器适配器】 调用 getHandlerAdapter(Object handler) 方法,获得当前处理器对应的 HandlerAdapter 适配器对象
- 处理有 Last-Modified 请求头的场景,TODO 暂时忽略
- 【拦截器】 调用 HandlerExecutionChain 执行链的 applyPreHandle(HttpServletRequest request, HttpServletResponse response) 方法,拦截器的前置处理;如果出现拦截器前置处理失败,则会调用拦截器的已完成处理方法(倒序)
- 【重点】 调用 HandlerAdapter 适配器的 handle(HttpServletRequest request, HttpServletResponse response, Object handler) 方法,真正的执行处理器,也就是执行对应的方法(例如我们定义的 Controller 中的方法),并返回视图
- 如果是异步,则直接 return,注意,还是会执行 finally 中的代码
- 调用 applyDefaultViewName(HttpServletRequest request, ModelAndView mv) 方法,ModelAndView 不为空,但是没有视图,则设置默认视图名称,使用到了 viewNameTranslator 视图名称转换器组件
- 【拦截器】 调用 HandlerExecutionChain 执行链的 applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) 方法,拦截器的后置处理(倒序)
- 记录异常,注意,此处仅仅记录,不会抛出异常,而是统一交给 <11> 处理
- 【处理执行结果】 调用 processDispatchResult() 方法,处理正常和异常的请求调用结果,包含页面渲染
- 【拦截器】 如果上一步发生了异常,则调用triggerAfterCompletion()方法,即调用 HandlerInterceptor 执行链的triggerAfterCompletion()方法,拦截器已完成处理(倒序)
- finally 代码块,异步处理,暂时忽略,如果是涉及到文件的请求,则清理相关资源
我们来总结一下:
九大组件
为了节省本文篇幅,这部分内容独立到单独文章中:
https://nimbusk.cc/post/b18da5cf.html
SpringAOP
为了节省本文篇幅,这部分内容独立到单独文章中:
https://nimbusk.cc/post/a46a5ca9.html
Spring事务
为了节省本文篇幅,这部分内容独立到单独文章中:
https://nimbusk.cc/post/18702fe9.html
SpringBoot
为了节省本文篇幅,这部分内容独立到单独文章中:
https://nimbusk.cc/post/f52b8ac8.html
SpringCloud
为了节省本文篇幅,这部分内容独立到单独文章中:
https://nimbusk.cc/post/3b599cdb.html