深入理解spring启动流程
spring可以说是所有java程序员都绕不开的一款优秀的开源框架,稳坐java开发领域的头把交椅。
一、入口程序
简单写一个测试类,从ClassPathXmlApplicationContext开始debug,一点点去剖析spring的源码。
public class Test {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("test.xml");
Student bean = context.getBean(Student.class);
context.close();
}
}
复制代码
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 http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="person" class="com.mashibing.Person" >
<property name="id" value="1"></property>
<property name="name" value="zhangsan"></property>
</bean>
<bean class="com.mashibing.MyBeanFactoryPostProcessor"></bean>
</beans>
复制代码
二、ClassPathXmlApplicationContext构造方法
阅读spring的源码,看到调用父类的方法,一定要点进去具体去看一下,有一些细节还是很有必要了解到的。不然在后续阅读过程中,会发现某个属性已经被赋值了或者某个方法不知道在什么时候被加载过了,所以一定要多点一下去看看。
点进去可以看到一直都是不停的调用父类的方法,直到AbstractApplicationContext中
此时通过一个参数为parent的构造器进来调用自身的无参构造,先不去关注getResourcePatternResolver()方法。
先看一下当前类有那些需要当期关注的属性值
AbstractApplicationContext 具体属性值
- 日志相关logger
protected final Log logger = LogFactory.getLog(getClass());
复制代码
- 创建上下文唯一标识(容器ID)
private String id = ObjectUtils.identityToString(this);
复制代码
- 显示名称
private String displayName = ObjectUtils.identityToString(this);
复制代码
- 增强或者修改Bean定义信息的集合
private final List<BeanFactoryPostProcessor> beanFactoryPostProcessors = new ArrayList<>();
复制代码
- 定义当前是否活跃标记位,初始化为false
private final AtomicBoolean active = new AtomicBoolean();
复制代码
- 定义当前是否是关闭状态标记位,初始化为false
private final AtomicBoolean closed = new AtomicBoolean();
复制代码
- 刷新或销毁容器时的同步器,一把锁。刷新也就是创建,在创建或者销毁的过程中是不能中断的,所以会加锁,后续会看到,目前只是定义。
private final Object startupShutdownMonitor = new Object();
复制代码
其他的一些变量和属性暂时先不关注。
getResourcePatternResolver方法
无参构造中调用该方法,刚刚先去看了一些比较重要的属性值和字段。现在回过头看getResourcePatternResolver方法。
继续跟进去发现创建了一个类,这个类的主要作用就是用来解析xml配置文件。实际上就是创建了一个资源解析器。
通过类图可以看到,顶层是ResourceLoader。资源加载器。
资源包含xml,yaml等配置文件。
setParent方法
首先可以明确一点,在当前入参parent是null。那么问题来了,既然是null为什么还有set呢?或者是parent又指的是什么?
- 首先说明在当前测试中并没有父子容器的概念,所以parent为null。
- springMVC中是存在父子容器的,未来可以再去看看。该方法比较简单,如果parent不为空并且父类环境为可配置环境时会发生环境合并。
AbstractXmlApplicationContext
继续debug会执行一些变量的初始化和定义,执行到以下代码时需要留意
首先可以看一下类名,当前类名为AbstractXmlApplicationContext,xml都很熟悉。
xml是一种文件格式,也是有规范标准,分别是dtd,xsd。
这个属性值的标志位就是检查xml配置是否符合标准,在程序运行前对xml进行校验,避免在程序运行过程中报错。
setConfigLocation方法
刚刚看完了super,现在继续debug往下走
看到这个方法,见名知意,就是把传递进来的各种类型的配置文件设置到某个变量中,方便在后续过程中使用。
SpringBoot中配置文件的后缀,如:dev,test,prod等也是基于该方法实现。
这里需要注意下resolvePath这个方法,该方法可以达到在定义配置文件的时候使用表达式,如:
srping-${userName}.xml
复制代码
有一个前提是表达式中的变量需要存在才可以获取到,不然会抛异常。
继续跟着往下走
- getEnvironment方法
-
从spring启动到现在还没有获取过任何环境变量,此方法内部会有一个createEnvironment方法。
-
createEnvironment方法创建了一个StandardEnvironment对象。
-
该类没有提供构造方法,通过默认的无参构造创建,难道只是创建了一个空的对象,那必然不是。
public class StandardEnvironment extends AbstractEnvironment{ //具体看源码 } 复制代码
-
由上述代码可以看到有一个父类AbstractEnvironment,可以看一下AbstractEnvironment的构造方法
public AbstractEnvironment() { customizePropertySources(this.propertySources); } 复制代码
可以看到调用了一个customizePropertySources方法。
protected void customizePropertySources(MutablePropertySources propertySources) { } 复制代码
以上并不是伪代码。而是AbstractEnvironment是一个抽象类,此方法是一个模板方法,具体实现交由子类完成。
AbstractEnvironment的属性值
-
** 设置忽略的属性值**
public static final String IGNORE_GETENV_PROPERTY_NAME = "spring.getenv.ignore"; 复制代码
-
指定活跃的配置文件
public static final String ACTIVE_PROFILES_PROPERTY_NAME = "spring.profiles.active"; 复制代码
-
默认使用的配置文件
public static final String ACTIVE_PROFILES_PROPERTY_NAME = "spring.profiles.active"; 复制代码
-
-
回过头再来看子类的实现,也就是刚刚提到的StandardEnvironment这个类中的实现。
StandardEnvironment 有两个属性值
-
系统环境
public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment"; 复制代码
-
系统属性
public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties"; 复制代码
-
具体的get就是分别把当前系统和环境变量设置进来。
可以看到具体值已经赋值进来了。
- resolveRequiredPlaceholders
-
调用createPlaceholderHelper创建一个PropertyPlaceholderHelper
public String resolvePlaceholders(String text) { if (this.nonStrictHelper == null) { this.nonStrictHelper = createPlaceholderHelper(true); } return doResolvePlaceholders(text, this.nonStrictHelper); } 复制代码
-
createPlaceholderHelper方法
private PropertyPlaceholderHelper createPlaceholderHelper(boolean ignoreUnresolvablePlaceholders) { return new PropertyPlaceholderHelper(this.placeholderPrefix, this.placeholderSuffix, this.valueSeparator, ignoreUnresolvablePlaceholders); } 复制代码
-
占位符前缀: ${
private String placeholderPrefix = SystemPropertyUtils.PLACEHOLDER_PREFIX; 复制代码
-
占位符后缀: }
private String placeholderSuffix = SystemPropertyUtils.PLACEHOLDER_SUFFIX; 复制代码
-
分割符: :(冒号)
private String valueSeparator = SystemPropertyUtils.VALUE_SEPARATOR; 复制代码
根据以上三个占位符创建出PropertyPlaceholderHelper
-
-
doResolvePlaceholders方法
-
parseStringValue 这个方法才是真正替换占位符的方法
-
parseStringValue方法
该方法比较长,具体可以看源码。可以分为两部分理解
-
寻找到需要替换的字符位置
-
替换字符 **spring考虑用户可能会有 ****${aaa{bbb}}**这种吊炸天的配置文件占位符,所以有一个递归调用。
从这里进去开始替换占位符,具体逻辑如下
这个逻辑很简单,就是从上文提到的StandardEnvironment中加载的一些属性中找到对应的值进行替换,上文已经提到加载进来的都是kv形式存在的。根据key找到对应value进行替换。
-
-
至此完成了占位符的替换,得到了真正的配置文件名称和路径。
-
总结
通过上述解析占位符的过程可以举一反三,比如${jsbc-url}等经常见的配置也是如此替换的,再多推断一些,一些框架整合spring的时候可以继承StandardEnvironment或者AbstractEnvironment重写customizePropertySources方法将一些需要替换的value值加载到进来,在使用的使用就可以通过以上方法进行占位符的替换操作。
-
-
至此ClassPathXmlApplicationContext构造就分析了完了配置文件路径的加载以及一些属性的初始化等操作。但此时才仅仅是一个开始,后续的refresh方法才是spring的核心方法。
下回再来仔细过一下refresh方法。
作者:武四三二一
链接:https://juejin.cn/post/7089082990784938020
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。