前面几章,大致讲了Spring
的IOC
容器的大致过程和原理,以及重要的容器和beanFactory
的继承关系,为后续这些细节挖掘提供一点理解基础。掌握总体脉络是必要的,接下来的每一章都是从总体脉络中,
去研究之前没看的一些重要细节。
本章就是主要从Spring
容器的启动开始,查看一下Spring
容器是怎么启动的,调用了父类的构造方法有没有干了什么。😄
直接从创建容器为切入点进去:
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = context.getBean(User.class);
进去之后会调用到这个方法:
可以看到这里是分了三步:
1、调用父类构造方法
2、设置配置文件地址
3、刷新容器
public ClassPathXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {
//调用父类构造方法,其实没做啥,就是如果有父容器(默认啥空),设置父容器和合并父容器的environment到当前容器
super(parent);
//设置配置文件地址:如果有用了$、#{}表达式,会解析到这些占位符,拿environment里面到属性去替换返回
setConfigLocations(configLocations);
if (refresh) {
//刷新容器,是Spring解析配置,加载Bean的入口。
// 用了模板方法设计模型:规定了容器中的一系列步骤
refresh();
}
}
1. super(parent)-调用父类构造方法
其实这个方法点进去,会调用到一系列父类的super方法,但是最终只是调用到了 AbstractApplicationContext
的构造方法(其实每个父类里面对应的属性都可以看一看,有些都是直接初始化默认的)
/**
* Create a new AbstractApplicationContext with the given parent context.
* @param parent the parent context
*/
public AbstractApplicationContext(@Nullable ApplicationContext parent) {
//会初始化resourcePatternResolver属性为PathMatchingResourcePatternResolver
//就是路径资源解析器,比如写的"classpath:*",会默认去加载classpath下的资源
this();
//设置父容器。并会copy父容器的environment属性合并到当前容器中
setParent(parent);
}
1.1 this()
接下来调用自己的this方法
public AbstractApplicationContext() {
//设置资源解析器
this.resourcePatternResolver = getResourcePatternResolver();
}
就是设置了自己的resourcePatternResolver
资源解析器
1.1.1 getResourcePatternResolver()
这个代码没啥,就是创建了一个默认的资源解析处理器 PathMatchingResourcePatternResolver
protected ResourcePatternResolver getResourcePatternResolver() {
return new PathMatchingResourcePatternResolver(this);
}
其实这个对象的功能就是把你传进来的字符串的路径,解析加载到具体的文件,返回Spring
能识别的Resource
对象
ok,this
方法走完了应该就继续走之前的setParent(parent)
方法
1.2 setParent(parent)
其实这里目前就是走不进去的,默认的parent父容器我们这里没使用,所以是空的,并不会走if
的逻辑
但是代码也挺简单,其实就是设置了parent属性,合并父容器的Environment
到当前容器的Environment
中
public void setParent(@Nullable ApplicationContext parent) {
this.parent = parent;
//如歌有父容器,则合并父容器的Environment的元素到当前容器中
//合并PropertySource(也就是key和value)
//合并激活activeProfiles文件列表
//合并默认文件列表defaultProfiles
if (parent != null) {
Environment parentEnvironment = parent.getEnvironment();
if (parentEnvironment instanceof ConfigurableEnvironment) {
getEnvironment().merge((ConfigurableEnvironment) parentEnvironment);
}
}
}
当然,可以假设我们设置了parent
属性。
会先调用到getEnvironment
方法,获取环境对象,如果没有的话,会创建一个默认的
1.2.1 getEnvironment
@Override
public ConfigurableEnvironment getEnvironment() {
if (this.environment == null) {
this.environment = createEnvironment();
}
return this.environment;
}
默认是空的,会跑到createEnvironment
方法
1.2.1.1 createEnvironment()
protected ConfigurableEnvironment createEnvironment() {
return new StandardEnvironment();
}
会初始化一个StandardEnvironment
类型的对象,我们可以关注他的构造方法,其实并没有内容,但是会默认调用他的父类AbstractEnvironment
构造器的方法
public AbstractEnvironment() {
//这里会默认加载属性属性变量和环境信息
this(new MutablePropertySources());
}
1.2.1.1.1 new MutablePropertySources()
其实这个对象就是使用了迭代器的设计模式,里面用 propertySourceList
数组存储不同类型的PropertySource
那么PropertySource
是干嘛的呢??
//存放Environment对象里的每个属性,一个PropertySource对象里面存有不同的Properties对象
//Properties对象就是有key和value的键值对象
//比如name=systemProperties -> 系统属性Properties对象
//比如name=systemEnv -> 系统环境变量Properties对象
public abstract class PropertySource<T> {
protected final Log logger = LogFactory.getLog(getClass());
protected final String name;
protected final T source;
}
这里摘取了他的属性。
其实name
只是一个类型而已,比如Environment
包括了systemProperties
(系统属性)和systemEnv
(系统环境变量)两种。对应就是不同的name
的属性存储器
source
属性一般都是Java中的Properties
对象,这个对象大家应该都熟悉吧(就跟map
差不多,有key
和value
,一般用于读取properties
文件使用)
看一下下面的图就知道了,Environment
在Spring中算是非常重要的对象了,所以必须了解
好了,知道了创建了这个默认的对象即可。
接下来就是调用AbstractEnvironment
的this
方法进去了。
AbstractEnvironment(MutablePropertySources)
protected AbstractEnvironment(MutablePropertySources propertySources) {
this.propertySources = propertySources;
//创建属性解析器PropertySourcesPropertyResolver
this.propertyResolver = createPropertyResolver(propertySources);
//调用子类的方法,加载系统的环境变量和系统属性到environment中
customizePropertySources(propertySources);
}
可以看到这里就是设置了Environment内部的propertySources
对象(存储属性的容器),
设置了propertyResolver
属性解析器,类型为PropertySourcesPropertyResolver
还把刚刚那个propertySources
设置进去了,这个解析器在后面会用到(在设置配置文件路径时会解析,后面会聊到!)
接下来非常重要的方法就是customizePropertySources
方法了,其实在当前类AbstractEnvironment
中是空方法,是子类 StandardEnvironment
实现的。(这里是不是很熟悉的味道,又是模版方法设计模式,AbstractEnvironment
规定了步骤,调用了当前类的空方法,子类会去覆盖这个空方法)😄
ok,我们进来了子类StandardEnvironment
的customizePropertySources
方法
其实可以看到这里就是写了两句代码,分别就是去读取系统属性和系统环境变量的值,加载到Environment
中
public class StandardEnvironment extends AbstractEnvironment {
/** System environment property source name: {@value}. */
public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";
/** JVM system properties property source name: {@value}. */
public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";
@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
//添加系统属性和系统环境变量,封装了一个个propertySource对象,添加到Environment的propertySources属性列表中
propertySources.addLast(
//系统属性
new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
propertySources.addLast(
//系统环境变量
new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}
}
我们可以看其中一个方法getSystemEnvironment
,就是调用了jdk的System.getenv()
方法,去获取到你本机的系统环境变量的值,然后最后设置到propertySources
-> Environment
中
@Override
@SuppressWarnings({"rawtypes", "unchecked"})
public Map<String, Object> getSystemEnvironment() {
if (suppressGetenvAccess()) {
return Collections.emptyMap();
}
try {
//jdk提供的方法,获取系统的环境变量
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;
}
}
};
}
}
解析完的Environment的里面的值大概是这样:
到这里,应该是理解Environment对象了吧。😄
okk,✋🏻回到之前的调用getEnvironment
的地方,咱们已经看完这个方法啦!也就是标题1.2处
接下里有了Environment
对象,就会进行父子容器的Environment
的合并啦!
1.2.2 Environment.merge()-父子容器的Environment合并
这里的代码就非常简单了,主要就是合并父容器的Environment
的属性到当前子容器中
public void merge(ConfigurableEnvironment parent) {
//合并PropertySource,也就是具体存在的属性键值对
for (PropertySource<?> ps : parent.getPropertySources()) {
if (!this.propertySources.contains(ps.getName())) {
this.propertySources.addLast(ps);
}
}
//合并活跃的profile - 一般SpringBoot中多开发环境都会设置profile
String[] parentActiveProfiles = parent.getActiveProfiles();
if (!ObjectUtils.isEmpty(parentActiveProfiles)) {
synchronized (this.activeProfiles) {
Collections.addAll(this.activeProfiles, parentActiveProfiles);
}
}
//合并默认的profile
String[] parentDefaultProfiles = parent.getDefaultProfiles();
if (!ObjectUtils.isEmpty(parentDefaultProfiles)) {
synchronized (this.defaultProfiles) {
this.defaultProfiles.remove(RESERVED_DEFAULT_PROFILE_NAME);
Collections.addAll(this.defaultProfiles, parentDefaultProfiles);
}
}
}
ok,到这里标题1,调用父类构造的方法到这里就结束了,接下来继续探索setConfigLocations
干了什么。
2. setConfigLocations-设置配置文件路径
/**
* 设置配置文件地址,并且会将文件路径格式化成标准格式
* 比如applicationContext-${profile}.xml, profile存在在Environment。
* 假设我的Environment中有 profile = "dev",
* 那么applicationContext-${profile}.xml会被替换成 applicationContext-dev.xml
* Set the config locations for this application context.
* <p>If not set, the implementation may use a default as appropriate.
*/
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;
}
}
这里关键的方法是会调用到resolvePath
方法并返回这些字符串路径
点进去,有没有感觉到很惊喜,为什么用了getEnvironment
去调用的呢?
其实之前的getEnvironment
并没有执行到,因为我们没有设置父类parent,到这里才是第一次初始化这个Environment
对象然后调用它的resolveRequiredPlaceholders
方法去解析路径
(这里关Environment
什么事呢?其实我们可以动态地写我们的配置文件,配置文件会去读取占位符,判断在Environment
是否存在这些属性,并完成替换)
protected String resolvePath(String path) {
//这里的获取getEnvironment,会默认创建StandardEnvironment对象。
//并用这个Environment对象解析路径
return getEnvironment().resolveRequiredPlaceholders(path);
}
写个示例就清楚咯!
2.1. 示例
我的电脑中存在HOME这个环境变量
接下来修改我的配置文件名称:
修改完之后发现,配置文件路径确定给解析到了。
了解这个功能即可。平时很少这么使用
ok,解析完配置,接下来就是最核心的方法了,调用refresh
容器刷新方法
3. refresh-容器刷新方法
这个方法是IOC的核心方法,只要掌握这个方法中的每一个方法,其实就基本掌握了Spring的IOC的整个流程。
后面将会分为很多章节去解释每个方法。
/**
* 容器刷新方法,是Spring最核心到方法。
* 规定了容器刷新到流程:比如prepareRefresh 前置刷新准备、
* obtainFreshBeanFactory 创建beanfactory去解析配置文、加载beandefinition、
* prepareBeanFactory 预设置beanfactory、
* invokeBeanFactoryPostProcessors 执行beanfactoryPostProcessor
* registerBeanPostProcessors 注册各种beanPostProcesser后置处理器
* initMessageSource 国际化调用
* initApplicationEventMulticaster 初始化事件多播器
* onRefresh 刷新方法,给其他子容器调用,目前这个容器没干啥
* registerListeners 注册时间监听器
* finishBeanFactoryInitialization 初始化所有非懒加载的bean对象到容器中
* finishRefresh 容器完成刷新: 主要会发布一些事件
*
* @throws BeansException
* @throws IllegalStateException
*/
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
// Prepare this context for refreshing.
//容器刷新的前置准备
//设置启动时间,激活状态为true,关闭状态false
//初始化environment
//初始化监听器列表
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
//创建beanFactory对象,并且扫描配置文件,加载beanDeifination,注册到容器中
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
//BeanFactory的预准备处理,设置beanFactory的属性,比如添加各种beanPostProcessor
//设置environment为bean对象并添加到容器中,后面可以直接@autowrie注入这些对象
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
//子类去实现的回调方法,当前容器没做什么工作,是个空方法
postProcessBeanFactory(beanFactory);
StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
// Invoke factory processors registered as beans in the context.
//加载并处理beanFactoryPostProcessor
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
//注册BeanPostProcessor对象到容器中
registerBeanPostProcessors(beanFactory);
beanPostProcess.end();
// Initialize message source for this context.
//初始化消息源,国际化使用
initMessageSource();
// Initialize event multicaster for this context.
//初始化事件多播器对象,并注册到容器中
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
//刷新,又是spring为了扩展,做的一个空实现,让子类可以覆盖这个方法做增强功能
onRefresh();
// Check for listener beans and register them.
//注册监听器到容器中,如果容器中的earlyApplicationEvents列表中有事件列表
//就会先发送这些事件。比如可以在前面的onRefresh方法中设置
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
//最最重要的方法,根据之前加载好的beandefinition,实例化bean到容器中,
//涉及到三级缓存、bean的生命周期、属性赋值等等
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
//完成刷新,会发送事件。
//检查earlyApplicationEvents事件列表中有没有新增的未发送的事件,有就发送
// 在执行applicationEventMulticaster事件列表中的所有事件
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
contextRefresh.end();
}
}
}