spring原始碼解析之IOC容器(二)------載入和註冊

蝸牛揹著馬發表於2019-06-24

  上一篇跟蹤了IOC容器對配置檔案的定位,現在我們繼續跟蹤程式碼,看看IOC容器是怎麼載入和註冊配置檔案中的資訊的。開始之前,首先我們先來了解一下IOC容器所使用的資料結構-------BeanDefinition,它是一個上層介面,有很多實現類,分別對應不同的資料載體。我們平時開發的時候,也會定義很多pojo類,來作為獲取資料的載體。最常見的就是,從資料庫中獲取資料之後,使用一個定義的pojo來裝載,然後我們就可以在程式中使用這個pojo類來編寫各種業務邏輯。同樣,IOC容器首先會讀取配置的XML中各個節點,即各個標籤元素,然後根據不同的標籤元素,使用不同的資料結構來裝載該元素中的各種屬性的值。比如我們最熟悉的<bean>標籤,就是使用AbstractBeanDefinition這個資料結構,接下來的分析中我們可以看到。

  先回到上篇資源的定位那裡,程式碼如下:

 1 public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
 2         ResourceLoader resourceLoader = getResourceLoader();
 3         if (resourceLoader == null) {
 4             throw new BeanDefinitionStoreException(
 5                     "Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
 6         }
 7 
 8         if (resourceLoader instanceof ResourcePatternResolver) {
 9             // Resource pattern matching available.
10             try {
11                 Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
12                 int loadCount = loadBeanDefinitions(resources);
13                 if (actualResources != null) {
14                     for (Resource resource : resources) {
15                         actualResources.add(resource);
16                     }
17                 }
18                 if (logger.isDebugEnabled()) {
19                     logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
20                 }
21                 return loadCount;
22             }
23             catch (IOException ex) {
24                 throw new BeanDefinitionStoreException(
25                         "Could not resolve bean definition resource pattern [" + location + "]", ex);
26             }
27         }
28         else {
29             // 定位到資源之後,封裝成一個resource物件
30             Resource resource = resourceLoader.getResource(location);
31             int loadCount = loadBeanDefinitions(resource);
32             if (actualResources != null) {
33                 actualResources.add(resource);
34             }
35             if (logger.isDebugEnabled()) {
36                 logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
37             }
38             return loadCount;
39         }
40     }

  進入loadBeanDefinitions(resource)方法,正式開始載入原始碼的跟蹤:

1         @Override
2     public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
3         return loadBeanDefinitions(new EncodedResource(resource));
4     }    
 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);
 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         }
29         catch (IOException ex) {
30             throw new BeanDefinitionStoreException(
31                     "IOException parsing XML document from " + encodedResource.getResource(), ex);
32         }
33         finally {
34             currentResources.remove(encodedResource);
35             if (currentResources.isEmpty()) {
36                 this.resourcesCurrentlyBeingLoaded.remove();
37             }
38         }
39     }

  進入doLoadBeanDefinitions(inputSource, encodedResource.getResource())方法:

 1 protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
 2             throws BeanDefinitionStoreException {
 3         try {
 4             Document doc = doLoadDocument(inputSource, resource);
 5             return registerBeanDefinitions(doc, resource);
 6         }
 7         catch (BeanDefinitionStoreException ex) {
 8             throw ex;
 9         }
10         catch (SAXParseException ex) {
11             throw new XmlBeanDefinitionStoreException(resource.getDescription(),
12                     "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
13         }
14         catch (SAXException ex) {
15             throw new XmlBeanDefinitionStoreException(resource.getDescription(),
16                     "XML document from " + resource + " is invalid", ex);
17         }
18         catch (ParserConfigurationException ex) {
19             throw new BeanDefinitionStoreException(resource.getDescription(),
20                     "Parser configuration exception parsing XML from " + resource, ex);
21         }
22         catch (IOException ex) {
23             throw new BeanDefinitionStoreException(resource.getDescription(),
24                     "IOException parsing XML document from " + resource, ex);
25         }
26         catch (Throwable ex) {
27             throw new BeanDefinitionStoreException(resource.getDescription(),
28                     "Unexpected exception parsing XML document from " + resource, ex);
29         }
30     }

  繼續進入registerBeanDefinitions(doc, resource)方法:

1 public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
2         //此時documentReader已經是DefaultBeanDefinitionDocumentReader類了
3         BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
4         int countBefore = getRegistry().getBeanDefinitionCount();
5         documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
6         //返回當前註冊的beanDefinition的個數
7         return getRegistry().getBeanDefinitionCount() - countBefore;
8     }

  進入registerBeanDefinitions(doc, createReaderContext(resource))方法:

1 public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
2         this.readerContext = readerContext;
3         logger.debug("Loading bean definitions");
4         Element root = doc.getDocumentElement();
5         doRegisterBeanDefinitions(root);
6     }

  進入doRegisterBeanDefinitions(root)方法:

 1 protected void doRegisterBeanDefinitions(Element root) {
 2         // Any nested <beans> elements will cause recursion in this method. In
 3         // order to propagate and preserve <beans> default-* attributes correctly,
 4         // keep track of the current (parent) delegate, which may be null. Create
 5         // the new (child) delegate with a reference to the parent for fallback purposes,
 6         // then ultimately reset this.delegate back to its original (parent) reference.
 7         // this behavior emulates a stack of delegates without actually necessitating one.
 8         BeanDefinitionParserDelegate parent = this.delegate;
 9         this.delegate = createDelegate(getReaderContext(), root, parent);
10 
11         if (this.delegate.isDefaultNamespace(root)) {
12             //profile屬性平時使用非常少,該屬性可以用於配置資料庫的切換(常用),使用時,需要在web.xml中配置context-parm
13             //<context-parm>
14             //    <parm-name>Spring.profiles.active</parm-name>
15             //    <parm-value>dev(在applicationContext.xml中配置的profile屬性的beans的profile屬性值)</parm-name>
16             //</context-parm>
17             //在applicationContext.xml中的配置
18             //<beans profile="dev">    </beans>
19             //<beans profile="produce">   </beans>
20             String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
21             if (StringUtils.hasText(profileSpec)) {
22                 String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
23                         profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
24                 if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
25                     if (logger.isInfoEnabled()) {
26                         logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +
27                                 "] not matching: " + getReaderContext().getResource());
28                     }
29                     return;
30                 }
31             }
32         }
33 
34         preProcessXml(root);
35         parseBeanDefinitions(root, this.delegate);
36         postProcessXml(root);
37 
38         this.delegate = parent;
39     }

  這裡也用到了模板方法,preProcessXml(root)和postProcessXml(root)這兩個方法都是空實現,是留給客戶來實現自己的邏輯的。重點研究一下parseBeanDefinitions(root, this.delegate)方法:

 1 protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
 2         if (delegate.isDefaultNamespace(root)) {
 3             NodeList nl = root.getChildNodes();
 4             for (int i = 0; i < nl.getLength(); i++) {
 5                 Node node = nl.item(i);
 6                 if (node instanceof Element) {
 7                     Element ele = (Element) node;
 8                     if (delegate.isDefaultNamespace(ele)) {
 9                         parseDefaultElement(ele, delegate);
10                     }
11                     else {
12                         delegate.parseCustomElement(ele);
13                     }
14                 }
15             }
16         }
17         else {
18             delegate.parseCustomElement(root);
19         }
20     }

  parseCustomElement(root)方法不需要怎麼研究,我們平時幾乎不會用到自定義的標籤,所以只跟蹤parseDefaultElement(ele, delegate)裡面的程式碼:

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
    //import標籤
        if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
            importBeanDefinitionResource(ele);
        }
    //alias標籤
        else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
            processAliasRegistration(ele);
        }
   //bean標籤
        else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
            processBeanDefinition(ele, delegate);
        }
  //beans標籤
        else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
            // recurse
            doRegisterBeanDefinitions(ele);
        }
    }

  可以看到,對於不同的標籤,spring採用不同的策略進行處理,重點跟蹤一下處理bean標籤的方法processBeanDefinition(ele, delegate):

 1 protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
 2         //委託給delegate去進行各種標籤的解析,parseBeanDefinitionElement方法中包含了各種標籤元素的解析,
 3         //並將解析好的內容封裝成BeanDefinitionHolder物件
 4         BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
 5         if (bdHolder != null) {
 6             bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
 7             try {
 8                 // Register the final decorated instance.
 9                 BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
10             }
11             catch (BeanDefinitionStoreException ex) {
12                 getReaderContext().error("Failed to register bean definition with name '" +
13                         bdHolder.getBeanName() + "'", ele, ex);
14             }
15             // Send registration event.
16             getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
17         }
18     }

  在這個方法中,delegate.parseBeanDefinitionElement(ele)是解析bean元素中各種屬性的方法,registerBeanDefinition(bdHolder, getReaderContext().getRegistry())是將封裝好的資料進行儲存的方法。先看一下解析的方法:

 1 public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {
 2         //獲取bean標籤的id屬性的值
 3         String id = ele.getAttribute(ID_ATTRIBUTE);
 4         //獲取bean標籤上name屬性的值
 5         String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
 6 
 7         List<String> aliases = new ArrayList<String>();
 8         if (StringUtils.hasLength(nameAttr)) {
 9             //將name的值進行分割,並將它們當作別名存到aliases中
10             String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
11             aliases.addAll(Arrays.asList(nameArr));
12         }
13 
14         String beanName = id;
15         //如果bean標籤的id沒有值,但是name屬性有值,則將name屬性的第一個值當作id的值,並從aliases中將第一個別名移除掉
16         if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
17             beanName = aliases.remove(0);
18             if (logger.isDebugEnabled()) {
19                 logger.debug("No XML 'id' specified - using '" + beanName +
20                         "' as bean name and " + aliases + " as aliases");
21             }
22         }
23 
24         if (containingBean == null) {
25             //檢查bean的唯一性
26             checkNameUniqueness(beanName, aliases, ele);
27         }
28 
29         //這裡已經是將XML中bean元素中的所有屬性都封裝到beanDefinition物件中了
30         AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
31         if (beanDefinition != null) {
32             if (!StringUtils.hasText(beanName)) {
33                 try {
34                     if (containingBean != null) {
35                         beanName = BeanDefinitionReaderUtils.generateBeanName(
36                                 beanDefinition, this.readerContext.getRegistry(), true);
37                     }
38                     else {
39                         beanName = this.readerContext.generateBeanName(beanDefinition);
40                         // Register an alias for the plain bean class name, if still possible,
41                         // if the generator returned the class name plus a suffix.
42                         // This is expected for Spring 1.2/2.0 backwards compatibility.
43                         String beanClassName = beanDefinition.getBeanClassName();
44                         if (beanClassName != null &&
45                                 beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
46                                 !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
47                             aliases.add(beanClassName);
48                         }
49                     }
50                     if (logger.isDebugEnabled()) {
51                         logger.debug("Neither XML 'id' nor 'name' specified - " +
52                                 "using generated bean name [" + beanName + "]");
53                     }
54                 }
55                 catch (Exception ex) {
56                     error(ex.getMessage(), ele);
57                     return null;
58                 }
59             }
60             String[] aliasesArray = StringUtils.toStringArray(aliases);
61             //最後將封裝好的beanDefinition、它的id、以及它的別名一起封裝成BeanDefinitionHolder物件返回
62             return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
63         }
64 
65         return null;
66     }

  我們可以得到如下資訊:

  1、獲取bean標籤的id屬性和name屬性的值;

  2、name屬性是可以都有多個值的,以逗號或者分號分割;

  3、如果id沒有賦值,則取name的第一個值作為id的值。所以,我們一般都會給id賦值,這樣效率高一些;

  4、檢查以這個id標識的bean是不是唯一的;

  5、進行其他屬性的解析,並最終封裝測AbstractBeanDefinition物件,也就是我們前文中提到的資料結構;

  6、最後封裝成BeanDefinitionHolder物件之後返回。

  進入parseBeanDefinitionElement(ele, beanName, containingBean)方法,看一下其他元素的解析過程:

 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         }
34         catch (ClassNotFoundException ex) {
35             error("Bean class [" + className + "] not found", ele, ex);
36         }
37         catch (NoClassDefFoundError err) {
38             error("Class that bean class [" + className + "] depends on not found", ele, err);
39         }
40         catch (Throwable ex) {
41             error("Unexpected failure during bean definition parsing", ele, ex);
42         }
43         finally {
44             this.parseState.pop();
45         }
46 
47         return null;
48     }

  解析封裝成BeanDefinitionHolder物件之後,就可以進行註冊了,先回到之前的processBeanDefinition(ele, delegate):

 1 protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
 2         //委託給delegate去進行各種標籤的解析,parseBeanDefinitionElement方法中包含了各種標籤元素的解析,
 3         //並將解析好的內容封裝成BeanDefinitionHolder物件
 4         BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
 5         if (bdHolder != null) {
 6             bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
 7             try {
 8                 // Register the final decorated instance.
 9                 BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
10             }
11             catch (BeanDefinitionStoreException ex) {
12                 getReaderContext().error("Failed to register bean definition with name '" +
13                         bdHolder.getBeanName() + "'", ele, ex);
14             }
15             // Send registration event.
16             getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
17         }
18     }

  現在進入BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry())方法進行分析:

 1 public static void registerBeanDefinition(
 2             BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
 3             throws BeanDefinitionStoreException {
 4 
 5         // Register bean definition under primary name.
 6         String beanName = definitionHolder.getBeanName();
 7         registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
 8 
 9         // Register aliases for bean name, if any.
10         String[] aliases = definitionHolder.getAliases();
11         if (aliases != null) {
12             for (String alias : aliases) {
13                 registry.registerAlias(beanName, alias);
14             }
15         }
16     }

  這裡的beanName就是之前封裝好的bean的id。這個方法中分別以id和別名作為key來註冊bean,其實就是儲存在map中。

  進入registerBeanDefinition(beanName, definitionHolder.getBeanDefinition()),在其子類DefaultListableBeanFactory中有實現:

 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         BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
18         if (existingDefinition != null) {
19             if (!isAllowBeanDefinitionOverriding()) {
20                 throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
21                         "Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
22                         "': There is already [" + existingDefinition + "] bound.");
23             }
24             else if (existingDefinition.getRole() < beanDefinition.getRole()) {
25                 // e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
26                 if (logger.isWarnEnabled()) {
27                     logger.warn("Overriding user-defined bean definition for bean '" + beanName +
28                             "' with a framework-generated bean definition: replacing [" +
29                             existingDefinition + "] with [" + beanDefinition + "]");
30                 }
31             }
32             else if (!beanDefinition.equals(existingDefinition)) {
33                 if (logger.isInfoEnabled()) {
34                     logger.info("Overriding bean definition for bean '" + beanName +
35                             "' with a different definition: replacing [" + existingDefinition +
36                             "] with [" + beanDefinition + "]");
37                 }
38             }
39             else {
40                 if (logger.isDebugEnabled()) {
41                     logger.debug("Overriding bean definition for bean '" + beanName +
42                             "' with an equivalent definition: replacing [" + existingDefinition +
43                             "] with [" + beanDefinition + "]");
44                 }
45             }
46             this.beanDefinitionMap.put(beanName, beanDefinition);
47         }
48         else {
49             if (hasBeanCreationStarted()) {
50                 // Cannot modify startup-time collection elements anymore (for stable iteration)
51                 synchronized (this.beanDefinitionMap) {
52                     this.beanDefinitionMap.put(beanName, beanDefinition);
53                     List<String> updatedDefinitions = new ArrayList<String>(this.beanDefinitionNames.size() + 1);
54                     updatedDefinitions.addAll(this.beanDefinitionNames);
55                     updatedDefinitions.add(beanName);
56                     this.beanDefinitionNames = updatedDefinitions;
57                     if (this.manualSingletonNames.contains(beanName)) {
58                         Set<String> updatedSingletons = new LinkedHashSet<String>(this.manualSingletonNames);
59                         updatedSingletons.remove(beanName);
60                         this.manualSingletonNames = updatedSingletons;
61                     }
62                 }
63             }
64             else {
65                 // Still in startup registration phase
66                 this.beanDefinitionMap.put(beanName, beanDefinition);
67                 this.beanDefinitionNames.add(beanName);
68                 this.manualSingletonNames.remove(beanName);
69             }
70             this.frozenBeanDefinitionNames = null;
71         }
72 
73         if (existingDefinition != null || containsSingleton(beanName)) {
74             resetBeanDefinition(beanName);
75         }
76     }

  我們可以看到:這個beanDefinitionMap就是用來儲存解析好的bean的,以id作為key。至此,就將所有的bean標籤解析好之後封裝成BeanDefinition註冊到了IOC容器中。但是,到目前為止,IOC容器並沒有為我們將這些解析好的資料生成一個一個bean例項,我們仍然不能就這樣直接使用。下一篇接著跟蹤。

相關文章