上一篇跟蹤了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例項,我們仍然不能就這樣直接使用。下一篇接著跟蹤。