IOC容器初始化分為三個步驟,分别是:
1、Resource定位,即BeanDefinition的資源定位。
2、BeanDefinition的載入
3、向IOC容器注冊BeanDefinition
下面我們來詳細展開這三部分的内容
一、Resource定位
以ApplicationContext的具體實作FileSystemXmlApplicationContext來介紹Resource定位的過程:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZuBnLxkjN4MTN4UjMtMjMwEDN3IzNxMjMyAzNxAjMtkzNxEzM48CXyAzNxAjMvwVO3ETMzgzLcd2bsJ2Lc12bj5ycn9Gbi52YuUTMwIzcldWYtl2Lc9CX6MHc0RHaiojIsJye.png)
IOC容器初始化類比為用木桶來裝水,Resource的定位過程就是尋找水的過程。
它是由ResourceLoader通過統一的Resource接口來完成的,Resource對各種形式的BeanDefinition的使用都提供了統一的接口。
Resource接口有許多實作類,針對不同的BeanDefinition,如:
在檔案系統中的Bean定義資訊可以使用FileSystemResource來進行抽象。
在類路徑中的Bean定義資訊可以使用ClassPathResource來進行抽象。
我們來看下FileSystemXmlApplicationContext其中的一個構造函數:
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
這裡有個refresh()方法,這個方法非常重要,可以說是IOC容器啟動的入口,該方法的實作在AbstractApplicationContext中。
在AbstractApplicationContext的refresh方法中,最終對調用到AbstractRefreshableApplicationContext的refreshBeanFactory,在這裡會建立一個基礎的IOC容器供ApplicationContext使用,這個基礎的BeanFactory就是DefaultListableBeanFactory。
protected final void refreshBeanFactory() throws BeansException {
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
再根據loadBeanDefinitions方法一直往下查找,最終會找到DefaultResourceLoader類的getResource方法:
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
if (location.startsWith(CLASSPATH_URL_PREFIX)) {
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
}
else {
try {
// Try to parse the location as a URL...
URL url = new URL(location);
return new UrlResource(url);
}
catch (MalformedURLException ex) {
// No URL -> resolve as resource path.
return getResourceByPath(location);
}
}
}
這裡的getResourceByPath方法最終被子類重寫,該場景下重寫該方法的類就是FileSystemXmlApplicationContext
protected Resource getResourceByPath(String path) {
if (path != null && path.startsWith("/")) {
path = path.substring(1);
}
return new FileSystemResource(path);
}
這樣我們從FileSystemXmlApplicationContext的構造函數中的refresh方法開始,最終到getResourceByPath結束,完成了資源定位的解析。但是這個隻是容器啟動中的一部分,還有載入和解析也是糅合在一起的,隻是為了思路清晰,故意對這些地方視而不見。
二、BeanDefinition的載入和解析
在第一部分資源的定位中,我們是以getResourceByPath()結束的,接下來我們要通過該方法傳回的Resource對象來進行BeanDefinition的載入了。現在我們已經知道水在哪裡了,就可以現在打水和裝水之旅了。
這個過程就是将定義的BeanDefinition在IOC容器中轉化成一個Spring内部表示的資料結構的過程。我們緊接着前面的源碼往下梳理:
AbstractRefreshableApplicationContext的方法refreshBeanFactory中調用loadBeanDefinitions(),往下梳理找到類AbstractXmlApplicationContext,源碼片段如下;
1 protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
2 // Create a new XmlBeanDefinitionReader for the given BeanFactory.
3 XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
4
5 // Configure the bean definition reader with this context's
6 // resource loading environment.
7 beanDefinitionReader.setResourceLoader(this);
8 beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
9
10 // Allow a subclass to provide custom initialization of the reader,
11 // then proceed with actually loading the bean definitions.
12 initBeanDefinitionReader(beanDefinitionReader);
13 loadBeanDefinitions(beanDefinitionReader);
14 }
其中第7行是為讀取器設定ResourceLoader,為後面的資源讀取做準備;第13行就是啟動Bean定義資訊載入的開始。
1 protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
2 Resource[] configResources = getConfigResources();
3 if (configResources != null) {
4 reader.loadBeanDefinitions(configResources);
5 }
6 String[] configLocations = getConfigLocations();
7 if (configLocations != null) {
8 reader.loadBeanDefinitions(configLocations);
9 }
10 }
第2行是以Resource的方式擷取配置檔案的資源位置,而第6行是以String的形式擷取配置檔案的位置。
reader.loadBeanDefinitions()的實作見XmlBeanDefinitionReader的父類AbstractBeanDefinitionReader
1 public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
2 Assert.notNull(locations, "Location array must not be null");
3 int counter = 0;
4 for (String location : locations) {
5 counter += loadBeanDefinitions(location);
6 }
7 return counter;
8 }
這裡如果Resource為空,則停止BeanDefinition的載入。在啟動BeanDefinition的過程中,會周遊整個Resource集合所包含的BeanDefinition的資訊。
這裡面調用的loadBeanDefinitions()方法具體實作并不在AbstractBeanDefinitionReader,而是在子類裡:
1 public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
2 Assert.notNull(encodedResource, "EncodedResource must not be null");
3 if (logger.isInfoEnabled()) {
4 logger.info("Loading XML bean definitions from " + encodedResource.getResource());
5 }
6
7 Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
8 if (currentResources == null) {
9 currentResources = new HashSet<EncodedResource>(4);
10 this.resourcesCurrentlyBeingLoaded.set(currentResources);
11 }
12 if (!currentResources.add(encodedResource)) {
13 throw new BeanDefinitionStoreException(
14 "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
15 }
16 try {
17 InputStream inputStream = encodedResource.getResource().getInputStream();
18 try {
19 InputSource inputSource = new InputSource(inputStream);
20 if (encodedResource.getEncoding() != null) {
21 inputSource.setEncoding(encodedResource.getEncoding());
22 }
23 return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
24 }
25 finally {
26 inputStream.close();
27 }
28 }
在這裡可以拿到代表XML檔案的Resource,讀取器在打開I/O流後就可以得到XML的檔案對象,接下來的事情就是按照Spring的Bean定義規則對這個XML檔案做解析并封裝了。而這個解析的過程是在類BeanDefinitionParserDelegate
在調用doLoadBeanDefinitions過程中,代碼會走到類XmlBeanDefinitionReader中的doLoadBeanDefinitions的方法
1 protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
2 throws BeanDefinitionStoreException {
3 try {
4 int validationMode = getValidationModeForResource(resource);
5 Document doc = this.documentLoader.loadDocument(
6 inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware());
7 return registerBeanDefinitions(doc, resource);
8 }
這裡第5行就是Document對象生成的入口,具體實作在DefaultDocumentLoader中。
第7行就是BeanDefinition解析的入口,我們接着往下走,最終的解析過程在類BeanDefinitionParserDelegate中,源碼如下:
1 public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {
2 String id = ele.getAttribute(ID_ATTRIBUTE);
3 String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
4
5 List<String> aliases = new ArrayList<String>();
6 if (StringUtils.hasLength(nameAttr)) {
7 String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, BEAN_NAME_DELIMITERS);
8 aliases.addAll(Arrays.asList(nameArr));
9 }
10
11 String beanName = id;
12 if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
13 beanName = aliases.remove(0);
14 if (logger.isDebugEnabled()) {
15 logger.debug("No XML 'id' specified - using '" + beanName +
16 "' as bean name and " + aliases + " as aliases");
17 }
18 }
19
20 if (containingBean == null) {
21 checkNameUniqueness(beanName, aliases, ele);
22 }
23
24 AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
25 if (beanDefinition != null) {
26 if (!StringUtils.hasText(beanName)) {
27 try {
28 if (containingBean != null) {
29 beanName = BeanDefinitionReaderUtils.generateBeanName(
30 beanDefinition, this.readerContext.getRegistry(), true);
31 }
32 else {
33 beanName = this.readerContext.generateBeanName(beanDefinition);
34 // Register an alias for the plain bean class name, if still possible,
35 // if the generator returned the class name plus a suffix.
36 // This is expected for Spring 1.2/2.0 backwards compatibility.
37 String beanClassName = beanDefinition.getBeanClassName();
38 if (beanClassName != null &&
39 beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
40 !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
41 aliases.add(beanClassName);
42 }
43 }
44 if (logger.isDebugEnabled()) {
45 logger.debug("Neither XML 'id' nor 'name' specified - " +
46 "using generated bean name [" + beanName + "]");
47 }
48 }
49 catch (Exception ex) {
50 error(ex.getMessage(), ele);
51 return null;
52 }
53 }
54 String[] aliasesArray = StringUtils.toStringArray(aliases);
55 return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
56 }
57
58 return null;
59 }
其中 第24行會觸發對Bean元素的詳細解析,解析完成後會傳回一個AbstractBeanDefinition對象,可以看下這個對象的屬性,基本上都是Spring配置檔案中的常用屬性。
解析方法的具體内容如下:
1 public AbstractBeanDefinition parseBeanDefinitionElement(
2 Element ele, String beanName, BeanDefinition containingBean) {
3
4 this.parseState.push(new BeanEntry(beanName));
5
6 String className = null;
7 if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
8 className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
9 }
10
11 try {
12 String parent = null;
13 if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
14 parent = ele.getAttribute(PARENT_ATTRIBUTE);
15 }
16 AbstractBeanDefinition bd = createBeanDefinition(className, parent);
17
18 parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
19 bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
20
21 parseMetaElements(ele, bd);
22 parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
23 parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
24
25 parseConstructorArgElements(ele, bd);
26 parsePropertyElements(ele, bd);
27 parseQualifierElements(ele, bd);
28
29 bd.setResource(this.readerContext.getResource());
30 bd.setSource(extractSource(ele));
31
32 return bd;
33 }
其中第18行-27行,都是對Spring配置檔案中不同<Bean>節點的解析,可以每個都深入去研究下。最終傳回一個AbstractBeanDefinition對象。
到這裡,XML檔案中定義的BeanDefinition就被整個載入到IOC容器中,建立了對應的資料結構,可以看成是POJO在容器中的抽象。但是這個時候容器中還隻是有一些靜态的配置資訊,完成了管理Bean對象的資料準備工作,但是容器還沒有完全發揮作用。
三、BeanDefinition注冊過程
這個源碼中方法調用過程比較簡單,我們直接來看注冊的核心代碼:
1 public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
2 throws BeanDefinitionStoreException {
3
4 Assert.hasText(beanName, "Bean name must not be empty");
5 Assert.notNull(beanDefinition, "BeanDefinition must not be null");
6
7 if (beanDefinition instanceof AbstractBeanDefinition) {
8 try {
9 ((AbstractBeanDefinition) beanDefinition).validate();
10 }
11 catch (BeanDefinitionValidationException ex) {
12 throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
13 "Validation of bean definition failed", ex);
14 }
15 }
16
17 synchronized (this.beanDefinitionMap) {
18 Object oldBeanDefinition = this.beanDefinitionMap.get(beanName);
19 if (oldBeanDefinition != null) {
20 if (!this.allowBeanDefinitionOverriding) {
21 throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
22 "Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
23 "': There is already [" + oldBeanDefinition + "] bound.");
24 }
25 else {
26 if (this.logger.isInfoEnabled()) {
27 this.logger.info("Overriding bean definition for bean '" + beanName +
28 "': replacing [" + oldBeanDefinition + "] with [" + beanDefinition + "]");
29 }
30 }
31 }
32 else {
33 this.beanDefinitionNames.add(beanName);
34 this.frozenBeanDefinitionNames = null;
35 }
36 this.beanDefinitionMap.put(beanName, beanDefinition);
37
38 resetBeanDefinition(beanName);
39 }
40 }
最關鍵的一步就是第36行,所謂的注冊就是将解析得到的BeanDefinition設定到hashMap中去。至此就完成了整個IOC容器初始化的工作,這樣一來就可以在beanDefinition裡檢索和使用已經配置好的Bean了。ICO容器的作用就是維護和處理裡面的Bean。
最後,我們來回答下這個問題,spring 容器中bean是什麼時候被初始化的?
通過前面的分析,我們知道bean的執行個體化是伴随着容器的初始化而同時進行的(但是也有特例),并且預設情況下是單例的。