参考源

https://www.bilibili.com/video/BV1tR4y1F75R?spm_id_from=333.337.search-card.all.click

https://www.bilibili.com/video/BV12Z4y197MU?spm_id_from=333.999.0.0

《Spring源码深度解析(第2版)》

版本

本文章基于 Spring 5.3.15


Spring IOC 主要有两种实现方式:XML注解

这里分析 XML 方式。

ClassPathXmlApplicationContext("applicationContext.xml")

配置文件

首先看解析的对象:配置文件

<?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="userDao" class="cn.sail.ioc.dao.UserDao"/> </beans>

Spring 的配置文件是一个典型的 XML 文件,其中的标签名称标签属性都是约定好的,在没有自定义前只能按照约定的格式编写。

约束方式

这里先介绍 XML 文件的两种约束方式:DTDXSD

DTD

DTD(Document Type Definition)文档类型定义

一种 XML 约束模式语言,是 XML 文件的验证机制,属于 XML 文件组成的一部分

一种保证 XML 文档格式正确的有效方法,可以通过比较 XML 文档和 DTD 文件来看文档是否符合规范,元素和标签使用是否正确。

DTD 文档内容

  • 元素的定义规则
  • 元素间关系的定义规则
  • 元素可使用的属性
  • 可使用的实体或符号规则

要使用 DTD 验证模式的时候需要在 XML 文件的头部声明,例如在 Spring 中使用 DTD 声明方式

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//Sping//DTD BEAN 2.0//EN" "http://www.Spingframework.org/dtd/ Spring-beans-2.O.dtd">
<beans> </beans>

老版本的 Spring 使用 DTD,新版本的 Spring 都使用 XSD 了。

XSD

XSD(XML Schemas Definition)XML描述定义

描述 XML 文档的结构,验证 XML 文档是否符合其要求。

指定 XML 文档所允许的结构和内容,检查 XML 文档是否有效。

本身是 XML 文档,符合 XML 语法结构,可以用通用的 XML 解析器解析。

对 XML 文档进行检验,需要声明名称空间(xmlns)和文档存储位置(xsi:schemaLocation)。

<?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"> </beans>

其中 xsi:schemaLocation 指向一个网络地址

由于网络不可靠,且经常会脱网开发,Spring 会通过 resources/META-INF/spring.schemas 定义本地路径。

比如上面的配置文件映射的本地路径:

http\://www.springframework.org/schema/beans/spring-beans.xsd=org/springframework/beans/factory/xml/spring-beans.xsd

两个路径下的内容是完全一致的。

接下来开始分析 ClassPathXmlApplicationContext("applicationContext.xml")

由于 Spring 源码层级十分复杂,约定如下规则

  • 数字 类名:数字代表该类出现的顺序。
  • 类数字-数字 方法注释:数字代表该方法在类中执行的层级。

1 ClassPathXmlApplicationContext

由于其父类 AbstractApplicationContext 存在静态代码块,先进入父类的静态代码块。

2 AbstractApplicationContext

2-1 静态代码块

进入 ClassPathXmlApplicationContext 的构造方法,会先进入 AbstractApplicationContext 的静态代码块。

static {
/**
* 优先加载上下文关闭事件来防止奇怪的类加载问题
* WebLogic 8.1 在应用程序关闭的时候出现的 BUG
*/
ContextClosedEvent.class.getName();
}

这里是针对 WebLogic 8.1 的特殊处理,与主体逻辑不关,不用过于关注。

1 ClassPathXmlApplicationContext

public  ClassPathXmlApplicationContext(String configLocation) throws BeansException {
this(new String[] {configLocation}, true, null);
}
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException {
// 调用父类构造方法,进行相关的对象创建等操作,包含属性的赋值操作
super(parent);
// 设置配置文件路径
setConfigLocations(configLocations);
if (refresh) {
// 刷新
refresh();
}
}

1-1 父类构造方法

super(parent)
public AbstractXmlApplicationContext(@Nullable ApplicationContext parent) {
super(parent);
}
public AbstractRefreshableConfigApplicationContext(@Nullable ApplicationContext parent) {
super(parent);
}
public AbstractRefreshableApplicationContext(@Nullable ApplicationContext parent) {
super(parent);
}
public AbstractApplicationContext(@Nullable ApplicationContext parent) {
this();
// 设置父容器
setParent(parent);
}

2 AbstractApplicationContext

this()
public AbstractApplicationContext() {
// 创建资源模式处理器
this.resourcePatternResolver = getResourcePatternResolver();
}

2-2 创建资源模式处理器

protected ResourcePatternResolver getResourcePatternResolver() {
// 创建一个资源模式解析器,用来解析 XML 配置文件
return new PathMatchingResourcePatternResolver(this);
}

3 PathMatchingResourcePatternResolver

PathMatchingResourcePatternResolver(this)
public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader) {
Assert.notNull(resourceLoader, "ResourceLoader must not be null");
this.resourceLoader = resourceLoader;
}

2 AbstractApplicationContext

2-1 设置父容器

setParent(parent)
public void setParent(@Nullable ApplicationContext parent) {
this.parent = parent;
if (parent != null) {
Environment parentEnvironment = parent.getEnvironment();
if (parentEnvironment instanceof ConfigurableEnvironment) {
getEnvironment().merge((ConfigurableEnvironment) parentEnvironment);
}
}
}

if (parent != null) 由于 parent 没有传,执行结束。

1 ClassPathXmlApplicationContext

1-1 设置配置文件路径

setConfigLocations(configLocations)

由于该类没有定义该方法,调用其父类 AbstractRefreshableConfigApplicationContext 的该方法。

4 AbstractRefreshableConfigApplicationContext

public void setConfigLocations(@Nullable String... locations) {
if (locations != null) {
Assert.noNullElements(locations, "Config locations must not be null");
this.configLocations = new String[locations.length];
for (int i = 0; i < locations.length; i++) {
// 解析给定路径
this.configLocations[i] = resolvePath(locations[i]).trim();
}
}
else {
this.configLocations = null;
}
}

4-1 解析给定路径

resolvePath(locations[i])
protected String resolvePath(String path) {
// 获取环境信息
// 解析所需的占位符
return getEnvironment().resolveRequiredPlaceholders(path);
}

4-2 获取环境信息

getEnvironment()
public ConfigurableEnvironment getEnvironment() {
if (this.environment == null) {
// 创建环境对象
this.environment = createEnvironment();
}
return this.environment;
}

4-3 创建环境对象

createEnvironment()
protected ConfigurableEnvironment createEnvironment() {
return new StandardEnvironment();
}

5 StandardEnvironment

public StandardEnvironment() {
}

由于继承关系,再调用 AbstractEnvironment 的构造方法。

6 AbstractEnvironment

public AbstractEnvironment() {
this(new MutablePropertySources());
}
protected AbstractEnvironment(MutablePropertySources propertySources) {
this.propertySources = propertySources;
// 创建属性解析器
this.propertyResolver = createPropertyResolver(propertySources);
// 定制化属性资源
customizePropertySources(propertySources);
}

6-1 创建属性解析器

createPropertyResolver(propertySources)
protected ConfigurablePropertyResolver createPropertyResolver(MutablePropertySources propertySources) {
return new PropertySourcesPropertyResolver(propertySources);
}

7 PropertySourcesPropertyResolver

public PropertySourcesPropertyResolver(@Nullable PropertySources propertySources) {
this.propertySources = propertySources;
}

6 AbstractEnvironment

6-1 定制化属性资源

customizePropertySources(propertySources)

由于子类重写了该方法,会调用子类方法。

5 StandardEnvironment

protected void customizePropertySources(MutablePropertySources propertySources) {
// 获取系统属性
propertySources.addLast(new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
// 获取系统环境
propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}

5-1 获取系统属性

getSystemProperties()
public Map<String, Object> getSystemProperties() {
try {
return (Map) System.getProperties();
} catch (AccessControlException ex) {
return (Map) new ReadOnlySystemAttributesMap() {
@Override
@Nullable
protected String getSystemAttribute(String attributeName) {
try {
return System.getProperty(attributeName);
} catch (AccessControlException ex) {
if (logger.isInfoEnabled()) {
logger.info("Caught AccessControlException when accessing system property '" + attributeName + "'; its value will be returned [null]. Reason: " + ex.getMessage());
}
return null;
}
}
};
}
}

5-1 获取系统环境

getSystemEnvironment()
public Map<String, Object> getSystemEnvironment() {
if (suppressGetenvAccess()) {
return Collections.emptyMap();
}
try {
return (Map) System.getenv();
} catch (AccessControlException ex) {
return (Map) new ReadOnlySystemAttributesMap() {
@Override
@Nullable
protected String getSystemAttribute(String attributeName) {
try {
return System.getenv(attributeName);
} catch (AccessControlException ex) {
if (logger.isInfoEnabled()) {
logger.info("Caught AccessControlException when accessing system environment variable '" + attributeName + "'; its value will be returned [null]. Reason: " + ex.getMessage());
}
return null;
}
}
};
}
}

4 AbstractRefreshableConfigApplicationContext

4-2 解析所需的占位符

resolveRequiredPlaceholders(path)

由于前面的 getEnvironment() 返回值为 ConfigurableEnvironment 接口,AbstractEnvironment 实现了该接口,所以跳转。

6 AbstractEnvironment

public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
// 解析所需的占位符
return this.propertyResolver.resolveRequiredPlaceholders(text);
}

由于 this.propertyResolver 的对象为 ConfigurablePropertyResolver,其由 AbstractPropertyResolver 实现,所以跳转。

7 AbstractPropertyResolver

public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
if (this.strictHelper == null) {
// 创建占位符
this.strictHelper = createPlaceholderHelper(false);
}
// 解决占位符
return doResolvePlaceholders(text, this.strictHelper);
}

7-1 创建占位符

createPlaceholderHelper(false)
private PropertyPlaceholderHelper createPlaceholderHelper(boolean ignoreUnresolvablePlaceholders) {
return new PropertyPlaceholderHelper(this.placeholderPrefix, this.placeholderSuffix, this.valueSeparator, ignoreUnresolvablePlaceholders);
}

8 PropertyPlaceholderHelper

public PropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuffix, @Nullable String valueSeparator, boolean ignoreUnresolvablePlaceholders) {
Assert.notNull(placeholderPrefix, "'placeholderPrefix' must not be null");
Assert.notNull(placeholderSuffix, "'placeholderSuffix' must not be null");
this.placeholderPrefix = placeholderPrefix;
this.placeholderSuffix = placeholderSuffix;
String simplePrefixForSuffix = wellKnownSimplePrefixes.get(this.placeholderSuffix);
if (simplePrefixForSuffix != null && this.placeholderPrefix.endsWith(simplePrefixForSuffix)) {
this.simplePrefix = simplePrefixForSuffix;
} else {
this.simplePrefix = this.placeholderPrefix;
}
this.valueSeparator = valueSeparator;
this.ignoreUnresolvablePlaceholders = ignoreUnresolvablePlaceholders;
}

7 AbstractPropertyResolver

7-1 解决占位符

doResolvePlaceholders(text, this.strictHelper)
private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
// 替换占位符
return helper.replacePlaceholders(text, this::getPropertyAsRawString);
}

7-2 替换占位符

replacePlaceholders(text, this::getPropertyAsRawString)
public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
Assert.notNull(value, "'value' must not be null");
// 解析字符串值
return parseStringValue(value, placeholderResolver, null);
}

7-3 解析字符串值

protected String parseStringValue(String value, PlaceholderResolver placeholderResolver, @Nullable Set<String> visitedPlaceholders) {
// 获取前缀符所在位置
int startIndex = value.indexOf(this.placeholderPrefix);
if (startIndex == -1) {
return value;
}
StringBuilder result = new StringBuilder(value);
while (startIndex != -1) {
// 获取后缀符所在位置
int endIndex = findPlaceholderEndIndex(result, startIndex);
if (endIndex != -1) {
// 获取前缀符和后缀符中间的值
String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
String originalPlaceholder = placeholder;
if (visitedPlaceholders == null) {
visitedPlaceholders = new HashSet<>(4);
}
if (!visitedPlaceholders.add(originalPlaceholder)) {
throw new IllegalArgumentException("Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
}
// 递归调用,解析占位符键中包含的占位符
placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
// 获取完全解析后的值
String propVal = placeholderResolver.resolvePlaceholder(placeholder);
if (propVal == null && this.valueSeparator != null) {
int separatorIndex = placeholder.indexOf(this.valueSeparator);
if (separatorIndex != -1) {
String actualPlaceholder = placeholder.substring(0, separatorIndex);
String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
if (propVal == null) {
propVal = defaultValue;
}
}
}
if (propVal != null) {
// 递归调用,处理全部的占位符
propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
if (logger.isTraceEnabled()) {
logger.trace("Resolved placeholder '" + placeholder + "'");
}
startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length());
}
else if (this.ignoreUnresolvablePlaceholders) {
// 继续处理剩余的值
startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
}
else {
throw new IllegalArgumentException("Could not resolve placeholder '" + placeholder + "'" + " in value \"" + value + "\"");
}
visitedPlaceholders.remove(originalPlaceholder);
}
else {
startIndex = -1;
}
}
return result.toString();
}
if (startIndex == -1) {
return value;
}

由于配置文件名称没有前缀符,执行结束。

1 ClassPathXmlApplicationContext

1-1 刷新

2 AbstractApplicationContext

refresh()
public void refresh() throws BeansException, IllegalStateException {
// 同步监视器
synchronized (this.startupShutdownMonitor) {
StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh"); /*
1 准备刷新的上下文环境。例如对系统属性或者环境变量进行准备及验证
设置容器的启动时间
设置关闭状态为 false
设置活跃状态为 true
获取 Environment 对象,并加载当前系统的属性值到 Environment 对象中并进行验证
准备监听器和事件的集合对象,默认为空的集合
*/
prepareRefresh(); /*
2 初始化 BeanFactory,并进行 XML 文件读取
创建容器对象:DefaultListableBeanFactory
加载 XML 配置文件的属性值到当前工厂中,最重要的就是 BeanDefinition
*/
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); /*
3 对 BeanFactory 进行各种功能填充
比如 @Qualifier 与 @Autowired 就是在这一步骤中增加的支持
*/
prepareBeanFactory(beanFactory); try {
/*
4 定义 Bean 工厂的增强器,子类覆盖方法做额外的处理(此处我们自己一般不做任何扩展工作,但是可以查看 web 中的代码是有具体实现的)
*/
postProcessBeanFactory(beanFactory); StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process"); /*
5 执行 Bean 工厂的增强器,激活各种 beanFactory 处理器
*/
invokeBeanFactoryPostProcessors(beanFactory); /*
6 注册 Bean 增强器。注册拦截 Bean 创建的 Bean 处理器,这里只是注册,真正的调用是在 getBean 时候
*/
registerBeanPostProcessors(beanFactory);
beanPostProcess.end(); /*
7 为上下文初始化 message 源,即不同语言的消息体,国际化处理
*/
initMessageSource(); /*
8 初始化应用消息广播器,并放入 "applicationEventMulticaster" bean 中
*/
initApplicationEventMulticaster(); /*
9 特定刷新。初始化其他的 bean,留给子类扩展
*/
onRefresh(); /*
10 注册监听器。在所有注册的 bean 中查找 listen bean,注册到消息广播器中
*/
registerListeners(); /*
11 初始化剩下的单实例(非懒加载的)
*/
finishBeanFactoryInitialization(beanFactory); /*
12 完成刷新过程,通知生命周期处理器 lifecycleProcessor 刷新过程,同时发出 ContextRefreshEvent 通知别人
*/
finishRefresh();
} catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " + "cancelling refresh attempt: " + ex);
}
// 为防止bean资源占用,在异常处理中,销毁已经在前面过程中生成的单件bean
destroyBeans();
// 重置active标志
cancelRefresh(ex);
throw ex;
} finally {
/*
13 清空缓存
*/
resetCommonCaches();
contextRefresh.end();
}
}
}

AbstractApplicationContext 中的 refresh() 是整个 IOC 的核心。

后续会对其中的 13 个主要方法做详细解析。

Spring源码 04 IOC XML方式的更多相关文章

  1. Spring源码 05 IOC 注解方式

    参考源 https://www.bilibili.com/video/BV1tR4y1F75R?spm_id_from=333.337.search-card.all.click https://ww ...

  2. spring源码浅析——IOC

    =========================================== 原文链接: spring源码浅析--IOC   转载请注明出处! ======================= ...

  3. Spring源码解析-ioc容器的设计

    Spring源码解析-ioc容器的设计 1 IoC容器系列的设计:BeanFactory和ApplicatioContext 在Spring容器中,主要分为两个主要的容器系列,一个是实现BeanFac ...

  4. spring源码分析---IOC(1)

    我们都知道spring有2个最重要的概念,IOC(控制反转)和AOP(依赖注入).今天我就分享一下spring源码的IOC. IOC的定义:直观的来说,就是由spring来负责控制对象的生命周期和对象 ...

  5. Spring源码 06 IOC refresh方法1

    参考源 https://www.bilibili.com/video/BV1tR4y1F75R?spm_id_from=333.337.search-card.all.click https://ww ...

  6. Spring源码 03 IOC原理

    参考源 https://www.bilibili.com/video/BV1tR4y1F75R?spm_id_from=333.337.search-card.all.click https://ww ...

  7. spring源码学习五 - xml格式配置,如何解析

    spring在注入bean的时候,可以通过bean.xml来配置,在xml文件中配置bean的属性,然后spring在refresh的时候,会去解析xml配置文件,这篇笔记,主要来记录.xml配置文件 ...

  8. Spring源码 07 IOC refresh方法2

    参考源 https://www.bilibili.com/video/BV1tR4y1F75R?spm_id_from=333.337.search-card.all.click https://ww ...

  9. 从零开始学spring源码之ioc预热:bean的拓展和beanProcessor注册

    上篇聊完了bean的解析,说起来做的事情很简单,把xml文件里面配置的标签全部解析到spring容器里面,但是spring做的时候,花了那么大代价去做,后面看看到底值不值得呢. 接下来看看prepar ...

随机推荐

  1. 20212115 实验三 《python程序设计》实验报告

    实验报告 20212115<python程序设计>实验三报告 课程:<Python程序设计>班级: 2121姓名: 朱时鸿学号:20212115实验教师:王志强老师实验日期:2 ...

  2. jeecgboot-vue3笔记(三)弹窗的使用

    需求描述 点击按钮,弹窗窗体(子组件),确定后在子组件中完成业务逻辑处理(例如添加记录),然后回调父组件刷新以显示最近记录. 实现步骤 子组件 子组件定义BasicModal <BasicMod ...

  3. SpringCloud微服务实战——搭建企业级开发框架(四十一):扩展JustAuth+SpringSecurity+Vue实现多租户系统微信扫码、钉钉扫码等第三方登录

      前面我们详细介绍了SSO.OAuth2的定义和实现原理,也举例说明了如何在微服务框架中使用spring-security-oauth2实现单点登录授权服务器和单点登录客户端.目前很多平台都提供了单 ...

  4. 基于MybatisPlus代码生成器(2.0新版本)

    一.模块简介 1.功能亮点 实时读取库表结构元数据信息,比如表名.字段名.字段类型.注释等,选中修改后的表,点击一键生成,代码成即可提现出表结构的变化. 单表快速转化restful风格的API接口并对 ...

  5. 30.Mysql主从复制、读写分离

    Mysql主从复制.读写分离 目录 Mysql主从复制.读写分离 读写分离 读写分离概述 为什么要读写分离 什么时候要读写分离 主从复制与读写分离 mysql支持的复制类型 主从复制的工作过程 初始环 ...

  6. 29.MySQL高级SQL语句

    MySQL高级SQL语句 目录 MySQL高级SQL语句 创建两个表 SELECT DISTINCT WHERE AND OR IN BETWEEN 通配符 LIKE ORDER BY 函数 数学函数 ...

  7. .Net Core 企业微信更新模版卡片消息

    1.搭建回调服务器 可参考:https://www.cnblogs.com/zspwf/p/16381643.html进行搭建 2.编写代码 2.1接口定义 应用可以发送模板卡片消息,发送之后可再通过 ...

  8. Javaweb-pom文件

    pom.xml是maven的核心配置文件 <?xml version="1.0" encoding="UTF-8"?> <!--maven版本 ...

  9. python基础知识-day9(库学习)

    1.os学习 1 print(os.name) #获取操作系统 2 print(os.path.exists("D:\soft\python")) #判断路径是否存在 3 prin ...

  10. 论文阅读 Exploring Temporal Information for Dynamic Network Embedding

    10 Exploring Temporal Information for Dynamic Network Embedding 5 link:https://scholar.google.com.sg ...