spring源碼閱讀(2)-- 容器啟動之加載BeanDefinition
在《spring源碼閱讀(1)-- 容器啟動之資源定位》一文中,閱讀了spring是怎麽根據用戶指定的配置加載資源,當加載完資源,接下來便是把從資源中加載BeanDefinition。
BeanDefinition作為spring其中一個組件,spring是這樣描述BeanDefinition的:BeanDefinition描述了一個bean實例,它具有屬性值,構造函數參數值以及具體實現提供的更多信息。個人的理解是BeanDefinition保存了一個bean實例的所有元數據,下面列舉一些常用的BeanDefinition屬性,更多屬性可以通過查看spring-beans.xsd了解
name:bean實例的別買,一個bean實例可以擁有多個別名
class:bean實例的class,如果作為一個父bean可以為空
parent:父bean的名稱
scope:聲明bean實例是單例還是原型的,默認單例
lazy-init:是否延遲加載,當是一個單例bean是,默認值是false
init-method:設置完屬性時調用的初始化方法
destroy-method:在bean工廠關閉時調用
項目沿用《spring源碼閱讀(1)-- 容器啟動之資源定位》一文的,這裏就不貼工程相關的配置文件,重點貼一下spring的配置文件
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation=" 5 http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd 6 "> 7 8 <bean id="springtest"class="com.zksite.spring.test.SpringBeanTest" /> 9 10 </beans>
通過閱讀上文,BeanDefinition的加載是由BeanDefinitionReader組件負責,而具體的實現是XmlBeanDefinitionReader。BeanDefinition的加載是由BeanDefinitionReader從Resuouce中去完成的,下面是XmlBeanDefinitionReader.loadBeanDefinitions(EncodedResource encodedResource)方法
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 } 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 }View Code
方法裏首先判斷一下是否循環加載,然後通過資源創建InputSource(spring解析xml是通過sax去解析的),然後調用doLoadBeanDefinitions()去解析xml和加載BeanDefinition。下面是doLoadBeanDefinitions代碼
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 }
doLoadDocument方法通過配置指定的DocumentLoader和創建XmlBeanDefinitionReader時指定的EntityResolver(這裏的實現是ResourceEntityResolver)去加載documen。sax在解析文檔時,由於指定了EntityResolver,所以在校驗xml文檔時會調用ResourceEntityResolver.resolveEntity()方法去加載dtd或xsd
1 public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException { 2 InputSource source = super.resolveEntity(publicId, systemId); 3 if (source == null && systemId != null) { 4 String resourcePath = null; 5 try { 6 String decodedSystemId = URLDecoder.decode(systemId, "UTF-8"); 7 String givenUrl = new URL(decodedSystemId).toString(); 8 String systemRootUrl = new File("").toURI().toURL().toString(); 9 // Try relative to resource base if currently in system root. 10 if (givenUrl.startsWith(systemRootUrl)) { 11 resourcePath = givenUrl.substring(systemRootUrl.length()); 12 } 13 } 14 catch (Exception ex) { 15 // Typically a MalformedURLException or AccessControlException. 16 if (logger.isDebugEnabled()) { 17 logger.debug("Could not resolve XML entity [" + systemId + "] against system root URL", ex); 18 } 19 // No URL (or no resolvable URL) -> try relative to resource base. 20 resourcePath = systemId; 21 } 22 if (resourcePath != null) { 23 if (logger.isTraceEnabled()) { 24 logger.trace("Trying to locate XML entity [" + systemId + "] as resource [" + resourcePath + "]"); 25 } 26 Resource resource = this.resourceLoader.getResource(resourcePath); 27 source = new InputSource(resource.getInputStream()); 28 source.setPublicId(publicId); 29 source.setSystemId(systemId); 30 if (logger.isDebugEnabled()) { 31 logger.debug("Found XML entity [" + systemId + "]: " + resource); 32 } 33 } 34 } 35 return source; 36 }
方法裏首先調用了父類提供的resolveEntity方法去加載,而父類是通過判斷加載的是dtd或xsd然後使用持有的EntityResolver去加載。現在配置文件指定的http://www.springframework.org/schema/beans/spring-beans.xsd,xsd是由實例schemaResolver.resolveEntity去加載(schemaResolver實例的創建發生在XmlBeanDefinitionReader設置EntityResolver時)
1 public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException { 2 if (systemId != null) { 3 if (systemId.endsWith(DTD_SUFFIX)) { 4 return this.dtdResolver.resolveEntity(publicId, systemId); 5 } 6 else if (systemId.endsWith(XSD_SUFFIX)) { 7 return this.schemaResolver.resolveEntity(publicId, systemId); 8 } 9 } 10 return null; 11 }
schemaResolver是PluggableSchemaResolver的實例,進入PluggableSchemaResolver的resolveEntity方法
1 public InputSource resolveEntity(String publicId, String systemId) throws IOException { 2 if (logger.isTraceEnabled()) { 3 logger.trace("Trying to resolve XML entity with public id [" + publicId + 4 "] and system id [" + systemId + "]"); 5 } 6 7 if (systemId != null) { 8 String resourceLocation = getSchemaMappings().get(systemId); 9 if (resourceLocation != null) { 10 Resource resource = new ClassPathResource(resourceLocation, this.classLoader); 11 try { 12 InputSource source = new InputSource(resource.getInputStream()); 13 source.setPublicId(publicId); 14 source.setSystemId(systemId); 15 if (logger.isDebugEnabled()) { 16 logger.debug("Found XML schema [" + systemId + "] in classpath: " + resourceLocation); 17 } 18 return source; 19 } 20 catch (FileNotFoundException ex) { 21 if (logger.isDebugEnabled()) { 22 logger.debug("Couldn‘t find XML schema [" + systemId + "]: " + resource, ex); 23 } 24 } 25 } 26 } 27 return null; 28 }View Code
方法裏首先調用getSchemaMappings()方法獲取所有schema,然後再從裏面獲取指定systemId的schema,如果找到則返回一個設置好systemId的InputSource。getSchemaMappings()方法裏面主要做的事情是,通過指定的ClassLoader查找出所有META-INF下的spring.schemas文件(當需要擴展spring的配置文件時,需要編寫自定義的schema),然後再存到一個Map裏面,key為命名空間,value為schema文件的路徑。
當加載完documen和校驗通過後,接下來的便是加載BeanDefinition,進入XmlBeanDefinitionReader.registerBeanDefinitions方法
1 public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { 2 BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); 3 int countBefore = getRegistry().getBeanDefinitionCount(); 4 documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); 5 return getRegistry().getBeanDefinitionCount() - countBefore; 6 }
方法裏首先創建一個BeanDefinitionDocumentReader(又一個新家夥,spring的抽象能力真的非常棒),BeanDefinitionDocumentReader的主要職責是負責解析dom文檔並根據dom文檔創建BeanDefinition然後註冊到BeanDefinition註冊中心(只有當標簽是默認命名空間的,也就是http://www.springframework.org/schema/beans,當是擴展的標簽時,需要自行實現BeanDefinitionParser進行解析),這裏的實現為DefaultBeanDefinitionDocumentReader,DefaultBeanDefinitionDocumentReader.doRegisterBeanDefinitions實現了BeanDefinition的加載和註冊,方法裏首先根據parentDelegate(主要目的是用來傳播默認設置)創建一個BeanDefinitionParserDelegate,然後判斷是否設置了profile,如果當前的配置沒有被激活,則會跳過解析,跳過的不是整個配置文件,有關profile的使用可以《詳解Spring中的Profile》。doRegisterBeanDefinitions源碼如下:
1 protected void doRegisterBeanDefinitions(Element root) { 2 BeanDefinitionParserDelegate parent = this.delegate; 3 this.delegate = createDelegate(getReaderContext(), root, parent); 4 5 if (this.delegate.isDefaultNamespace(root)) { 6 String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE); 7 if (StringUtils.hasText(profileSpec)) { 8 String[] specifiedProfiles = StringUtils.tokenizeToStringArray( 9 profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS); 10 if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) { 11 if (logger.isInfoEnabled()) { 12 logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec + 13 "] not matching: " + getReaderContext().getResource()); 14 } 15 return; 16 } 17 } 18 } 19 20 preProcessXml(root); 21 parseBeanDefinitions(root, this.delegate); 22 postProcessXml(root); 23 24 this.delegate = parent; 25 }
當配置文件沒有跳過時,執行解析documen文檔操作,doRegisterBeanDefinitions方法裏的preProcessXml和postProcessXml是預留的擴展點,DefaultBeanDefinitionDocumentReader裏的實現為空,所以直接進入parseBeanDefinitions方法,方法裏獲取所有的子節點,然後循環遍歷解析。
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 }
如果是默認命名空間的標簽,直接進入parseDefaultElement,方法裏根據標簽名字,進行不同的處理,如果是“import”將加載一個資源,然後執行上面的流程;如果是“alias”,向BeanDefinitionRegistry註冊別名;如果是“bean”執行BeanDefinition的註冊;如果是“beans”遞歸調用doRegisterBeanDefinitions方法。parseDefaultElement源碼如下:
1 private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) { 2 if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { 3 importBeanDefinitionResource(ele); 4 } 5 else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { 6 processAliasRegistration(ele); 7 } 8 else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) { 9 processBeanDefinition(ele, delegate); 10 } 11 else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { 12 // recurse 13 doRegisterBeanDefinitions(ele); 14 } 15 }
當解析的標簽是“bean”時,將會使用BeanDefinitionParserDelegate.parseBeanDefinitionElement去解析bean標簽。進入BeanDefinitionParserDelegate.parseBeanDefinitionElement方法
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, MULTI_VALUE_ATTRIBUTE_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 }View Code
方法裏首先獲取bean標簽的id和name屬性,如果配置了id,那麽beanNama就是id。然後判斷是否配置了多個name,如果有將解析為別名。然後調用parseBeanDefinitionElement方法創建BeanDefinition,parseBeanDefinitionElement方法會根據標簽的屬性和子節點內容去設置BeanDefinition(關於BeanDefinition的屬性有什麽作用這裏先跳過),至此,已經成功解析完整個bean標簽並且創建了BeanDefinition,然後返回給上層調用者即parseBeanDefinitionElement方法。parseBeanDefinitionElement方法做最後的處理,判斷用戶是否設置了名稱,如果沒有生成一個。當這些操作都完成時,回到DefaultBeanDefinitionDocumentReader.processBeanDefinition方法進行進一步裝飾BeanDefinition然後向給定的BeanDefinitionRegistry註冊。
如果解析的命名空間不是默認的,spring會怎麽處理呢?現在來更改一下配置文件
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:context="http://www.springframework.org/schema/context" 4 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 5 xsi:schemaLocation=" 6 http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd 7 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd 8 "> 9 10 <bean class="com.zksite.spring.test.SpringBeanTest" /> 11 <context:component-scan base-package="com"></context:component-scan> 12 </beans>
配置文件新添加了命名空間:http://www.springframework.org/schema/context並配置了schemaLocation http://www.springframework.org/schema/context/spring-context.xsd。這裏先介紹一下spring命名空間擴展機制,spring為了方便用戶擴展,提供了NamespaceHandler接口,如果用戶需要擴展spring的配置文件只需要做以下處理:
1.編寫xml schema文件
2.編寫spring.schemas文件,用於獲取xml schema文件路徑
3.編寫spring.handlers文件,用戶獲取自定義標簽解析器
4.實現NamespaceHandler接口,通常建議繼承NamespaceHandlerSupport,實現init方法
spring在解析到用戶自定義的標簽時,通過調用BeanDefinitionParserDelegate.parseCustomElement進行處理,方法裏會通過持有的NamespaceHandlerResolver獲取用戶配置的NamespaceHandler,然後調用NamespaceHandler.parse方法去解析。NamespaceHandlerResolver是怎麽獲取到指定的NamespaceHandler的呢?進入DefaultNamespaceHandlerResolver的resolve方法
1 public NamespaceHandler resolve(String namespaceUri) { 2 Map<String, Object> handlerMappings = getHandlerMappings(); 3 Object handlerOrClassName = handlerMappings.get(namespaceUri); 4 if (handlerOrClassName == null) { 5 return null; 6 } 7 else if (handlerOrClassName instanceof NamespaceHandler) { 8 return (NamespaceHandler) handlerOrClassName; 9 } 10 else { 11 String className = (String) handlerOrClassName; 12 try { 13 Class<?> handlerClass = ClassUtils.forName(className, this.classLoader); 14 if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) { 15 throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri + 16 "] does not implement the [" + NamespaceHandler.class.getName() + "] interface"); 17 } 18 NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass); 19 namespaceHandler.init(); 20 handlerMappings.put(namespaceUri, namespaceHandler); 21 return namespaceHandler; 22 } 23 catch (ClassNotFoundException ex) { 24 throw new FatalBeanException("NamespaceHandler class [" + className + "] for namespace [" + 25 namespaceUri + "] not found", ex); 26 } 27 catch (LinkageError err) { 28 throw new FatalBeanException("Invalid NamespaceHandler class [" + className + "] for namespace [" + 29 namespaceUri + "]: problem with handler class file or dependent class", err); 30 } 31 } 32 }
首先調用getHandlerMappings()方法,getHandlerMappings方法會根據指定的classLoader找出META-INF下面的所有spring.handlers,然後再把spring.handlers裏面的內容存放到一個Map裏,key存放命名空間,value存放NamespaceHandler。獲取到NamespaceHandler時,先判斷一下是否已經初始化了,如果沒有,通過反射初始化,然後調用NamespaceHandler.init()方法。當找到指定的NamespaceHandler之後返回給BeanDefinitionParserDelegate.parseCustomElement,方法裏再調用獲取回來的NamespaceHandler.parse方法去解析自定義標簽。
至此,spring已經通過資源加載了BeanDefinition,接下裏的便是向註冊中心註冊BeanDefinition。進入BeanDefinitionReaderUtils.registerBeanDefinition方法
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 }
方法裏使用傳入的BeanDefinitionRegistry進行註冊,BeanDefinitionRegistry是通過一個Map將BeanDefinition存起來。
最後總結一下註冊BeanDefinition用到了哪些組件
BeanDefinitionReader:負責從配置文件加載BeanDefinition
BeanDefinitionDocumentReader:負責解析document加載BeanDefinition並註冊
BeanDefinitionParserDelegate:負責解析標簽並根據標簽內容構建BeanDefinition
BeanDefinitionRegistry:負責BeanDefinition的註冊
當註冊完BeanDefinition,接下來便是創建bean
spring源碼閱讀(2)-- 容器啟動之加載BeanDefinition