前面幾章,大致講了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文件列表//合并默認文件列表defaultProfilesif (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;//創建屬性解析器PropertySourcesPropertyResolverthis.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";@Overrideprotected 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@Nullableprotected 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.//加載并處理beanFactoryPostProcessorinvokeBeanFactoryPostProcessors(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();}}
}