1 前言
眾所周知,Spring可以幫我們管理我們需要的bean,在我們需要用到這些bean的時候,可以很方便的獲取到它,然后進行一系列的操作,比如,我們定義一個bean MyTestBean
public class MyTestBean { private String testStr = "testStr"; public String getTestStr() { return testStr; } public void setTestStr(String testStr) { this.testStr = testStr; }
然后xml配置一下
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd"> <bean id = "myTestBean" class="bean.MyTestBean"/> </beans>
撰寫一下測驗代碼,測驗一下,就會看到測驗通過的結果,
public class BeanFactoryTest { @Test public void testSimpleLoad() { BeanFactory bf = new XmlBeanFactory(new ClassPathResource("beanFactoryTest.xml")); MyTestBean bean = (MyTestBean)bf.getBean("myTestBean"); assert "testStr".equals(bean.getTestStr()); } }
直接使用BeanFactory作為容器對于Spring來說不常見,這里只是用來測驗,以便可以更快更好地分析Spring內部原理,其涉及到的一些組件,貫穿整個Spring容器當中,對于我們了解其他Spring容器也有很大的幫助,限于篇幅,這里只介紹該容器創建的部分,即對于new XmlBeanFactory(new ClassPathResource(“beanFactoryTest.xml”)),Spring都干了些什么,
2 Spring容器創建原理
2.1 整體實作流程
首先我們大致了解一下Spring容器創建的整體程序,
整體時序圖
可以看到,該Spring容器創建大致分為以下幾部分:
- 資源的封裝,以Resource封裝組態檔
- 加載BeanDefinition
- 決議組態檔,獲取Document
- 決議及注冊BeanDefinition
- 標簽的決議,分為默認標簽和自定義標簽的決議
下面我們就以這樣的順序對各個部分從代碼實作上進行具體分析,
2.2 核心類介紹
在進行具體創建邏輯之前,我們先對Spring容器創建的核心類進行介紹,以便我們更好地掌握它的實作程序,
2.2.1 DefaultListableBeanFactory
XmlBeanFactory繼承自DefaultListableBeanFactory,而DefaultListableBeanFactory是整個bean加載的核心部分,是Spring注冊及加載bean的默認實作,XmlBeanFactory與DefaultListableBeanFactory的不同之處在于,XmlBeanFactory使用了自定義的XML讀取器XmlBeanDefinitionReader,實作了個性化的BeanDefinitionReader讀取,
容器加載相關類圖
2.2.2 XmlBeanDefinitionReader
XmlBeanDefinitionReader用于資源檔案讀取、決議及Bean注冊,讀取Xml組態檔的流程大致為,首先使用ResourceLoader將資源檔案路徑轉換為對應的Resource檔案,然后將Resource檔案轉換為Document檔案,最后對Document及Element進行決議,
組態檔讀取相關類圖
2.2.3 BeanDefinition
在Spring中,BeanDefinition是組態檔元素標簽在容器中的內部表示形式,包含了元素的所有資訊,Spring將組態檔中的轉換為BeanDefinition,并將這些BeanDefinition注冊到BeanDefinitionRegistry中,BeanDefinitionRegistry以map形式保存,后續操作直接從BeanDefinitionRegistry中讀取配置資訊,
BeanDefinition及其實作類
2.3 組態檔的封裝
在Java中不同的資源都要抽象成URL,然后使用不同型別的URLStreamHandler處理不同的URL表示的資源,但是,Spring對其內部使用到的資源實作了自己的抽象結構:Resource介面封裝底層資源,主要原因有3點:
- URL沒有默認定義相對Classpath或ServletContext等資源的handler
- URL沒有提供基本的方法,例如檢查當前資源是否存在是否可讀等
- 自定義URL handler需要了解URL實作機制
對不同來源的資源檔案都有相應的Resource實作:檔案(FileSystemResource)、Classpath資源(ClassPathResource)、URL資源(UrlResource)、InputStream資源(InputStreamResource)、Byte陣列(ByteArrayResource)等,
資源檔案處理相關類圖
2.4 加載BeanDefinition
下面我們就從代碼層次看看整個容器究竟是怎么實作的,觀察測驗代碼,我們可以將XmlBeanFactory的構造方法作為切入點進行分析,
public XmlBeanFactory(Resource resource) throws BeansException { this(resource, null); } public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException { super(parentBeanFactory); // 加載BeanDefinition this.reader.loadBeanDefinitions(resource); }
主要做了兩件事,一是呼叫父類的構造方法,二是加載BeanDefinition,
首先我們先進入父類構造方法,最終進到AbstractAutowireCapableBeanFactory構造方法中,
public AbstractAutowireCapableBeanFactory() { super(); // 忽略BeanNameAware、BeanFactoryAware和BeanClassLoaderAware介面的自動裝配功能 ignoreDependencyInterface(BeanNameAware.class); ignoreDependencyInterface(BeanFactoryAware.class); ignoreDependencyInterface(BeanClassLoaderAware.class); }
主要是ignoreDependencyInterface方法,它的主要功能是忽略給定介面的自動裝配功能,實作上很簡單,就是把這些Class加入到ignoredDependencyInterfaces集合中,ignoredDependencyInterfaces是Set<class<?>>型別,
再看加載Bean的方法,執行的是XmlBeanDefinitionReader類的loadBeanDefinitions方法,進入方法,可以看到主要就是做了兩件事,一是構造InputSource,這個類全路徑名是org.xml.sax.InputSource,這步的目的就是通過SAX讀取XML檔案事先準備一下InputSource物件,而真正的加載Bean的邏輯在doLoadBeanDefinitions方法中,
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException { InputStream inputStream = encodedResource.getResource().getInputStream(); // 構建InputSource,用于決議XML InputSource inputSource = new InputSource(inputStream); if (encodedResource.getEncoding() != null) { inputSource.setEncoding(encodedResource.getEncoding()); } // 實際加載BeanDefinition的執行邏輯 return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); }
doLoadBeanDefinitions方法首先加載XML檔案,得到Document物件,然后根據Document物件注冊Bean,我們首先看下得到Document物件的程序,
2.5 獲取Document
獲取Document,首先通過getValidationModeForResource獲取XML驗證模式,然后決議得到Document物件,
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception { // 首先獲取XML驗證模式,然后SAX方式決議得到Document return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler, getValidationModeForResource(resource), isNamespaceAware()); }
常用的XML驗證模式有兩種:DTD和XSD,實際使用哪種驗證模式在getValidationModeForResource中進行了決議,這個方法判斷是DTD驗證還是XSD驗證,僅僅是判斷一下XML是否包含DOCTYPE字串,
而決議得到Document的方法很簡單,就是通過SAX決議XML檔案的套路,這里不再贅述,
2.6 決議及注冊BeanDefinition
當把檔案轉換為Document后,接下來的提取及注冊bean就是我們的重頭戲了,呼叫了以下方法,
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); int countBefore = getRegistry().getBeanDefinitionCount(); // 注冊BeanDefinition documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); return getRegistry().getBeanDefinitionCount() - countBefore; }
在這個方法中很好地應用了單一職責原則,將邏輯處理委托給單一的類進行處理,而這個邏輯處理類就是BeanDefinitionDocumentReader,BeanDefinitionDocumentReader是一個介面,實體化的作業在createBeanDefinitionDocumentReader()中完成,真正的型別是DefaultBeanDefinitionDocumentReader,而它的registerBeanDefinitions方法很簡單,僅僅是先根據Document獲取了root,實際注冊在doRegisterBeanDefinitions方法中,
protected void doRegisterBeanDefinitions(Element root) { if (this.delegate.isDefaultNamespace(root)) { String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE); if (StringUtils.hasText(profileSpec)) { String[] specifiedProfiles = StringUtils.tokenizeToStringArray( profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS); // 如果環境變數不包含指定profile,則流程結束 if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) { return; } } } preProcessXml(root); // 決議BeanDefinition parseBeanDefinitions(root, this.delegate); postProcessXml(root); }
注冊程序首先對profile進行處理,如果是環境變數定義的則進行處理,否則不進行處理,然后就是決議bean,這里呼叫了preProcessXml(root)和postProcessXml(root)兩個方法,但是發現這兩個方法是空方法,這里應用了模板方法模式,如果繼承自DefaultBeanDefinitionDocumentReader的子類需要在Bean決議前后做一些處理的話,只需要重寫這兩個方法即可,
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { if (delegate.isDefaultNamespace(root)) { // 根元素是默認命名空間 NodeList nl = root.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (node instanceof Element) { Element ele = (Element) node; if (delegate.isDefaultNamespace(ele)) { // 如果元素是默認命名空間,則按默認方式決議元素 parseDefaultElement(ele, delegate); } else { // 如果這個元素是自定義命名空間,則按自定義方式決議元素 delegate.parseCustomElement(ele); } } } } else { // 如果根元素是自定義命名空間,則按自定義方式決議元素 delegate.parseCustomElement(root); } }
而決議bean需要判斷元素是否是默認命名空間,如果是則呼叫parseDefaultElement(ele, delegate)方法,不是則呼叫delegate.parseCustomElement(ele)方法,判斷是否是默認命名空間,呼叫isDefaultNamespace方法,元素或者節點的命名空間與Spring中固定的命名空間http://www.springframework.org/schema/beans 進行對比,一致則認為是默認的,否則就認為是自定義的,
2.7 默認標簽的決議
默認標簽的決議邏輯一目了然,分別對4種不同標簽(import、alias、bean和beans)做了不同的處理,
2.7.1 bean標簽的決議及注冊
對bean標簽的決議是通過processBeanDefinition(ele, delegate)方法進行的,大致邏輯總結如下:
- 首先委托BeanDefinitionDelegate類的parseBeanDefinitionElement方法進行元素決議,回傳BeanDefinitionHolder型別的實體bdHolder,經過這個方法后,bdHolder實體已經包含我們組態檔中配置的各種屬性了,例如class、name、id、alias等屬性,
- 如果bdHolder不為空,若存在默認標簽的子節點下再有自定義屬性,還需要再次對自定義標簽進行決議,
- 決議后,對bdHolder進行注冊,注冊操作委托給了BeanDefinitionReaderUtils的registerBeanDefinition方法,
- 發出回應事件,通知相關監聽器,這個bean已經加載完了,
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { // 決議元素資訊,用bdHolder封裝 BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); if (bdHolder != null) { // 如果標簽下有自定義屬性,則對自定義屬性進行決議 bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); // 對bdHolder進行注冊 BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); // 發送注冊事件給監聽器. getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder)); } }
1)元素決議及資訊提取
首先我們從元素決議及資訊提取開始:delegate.parseBeanDefinitionElement(ele),進入BeanDefinitionParserDelegate類parseBeanDefinitionElement方法,主要完成如下內容:
- 提取元素的id和name屬性
- 決議其他所有屬性,封裝到GenericBeanDefinition型別的實體中,對應this.parseBeanDefinitionElement(ele, beanName, containingBean)方法
- 如果bean沒有指定beanName,則使用默認規則為此Bean生成beanName
- 將獲取到的資訊封裝到BeanDefinitionHolder實體中
beanName取值策略是,首先取id,如果沒有指定id則取name[0](因為name可以指定多個),如果name也沒有指定,則采取自動生成方式生成,
最終bean元素的所有屬性和子元素資訊都保存到GenericBeanDefinition中了,至此就完成了XML檔案到GenericBeanDefinition的轉換,
2)默認標簽中自定義標簽元素的決議
如果這個bean使用的是默認的標簽配置,但是其中的子元素卻使用了自定義配置,這時這部分內容就起作用了,入口是delegate.decorateBeanDefinitionIfRequired(ele, bdHolder)方法,它分別對元素的所有屬性和子元素進行了decorateIfRequired方法的呼叫,decorateIfRequired方法會判斷,如果是自定義節點,則找出自定義型別所對應的NamespaceHandler并進行進一步決議,
public BeanDefinitionHolder decorateIfRequired( Node node, BeanDefinitionHolder originalDef, BeanDefinition containingBd) { String namespaceUri = getNamespaceURI(node); if (!isDefaultNamespace(namespaceUri)) { // 如果元素是自定義元素,則根據命名空間找到對應的命名空間處理器 NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri); if (handler != null) { // 自定義命名空間處理器處理bdHolder return handler.decorate(node, originalDef, new ParserContext(this.readerContext, this, containingBd)); } } return originalDef; }
3)BeanDefinition的注冊
BeanDefinition注冊分為通過beanName注冊BeanDefinition和注冊別名兩部分,進入BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, this.getReaderContext().getRegistry())方法內部,如下
public static void registerBeanDefinition( BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException { String beanName = definitionHolder.getBeanName(); // 通過beanName注冊BeanDefinition registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition()); String[] aliases = definitionHolder.getAliases(); if (aliases != null) { for (String alias : aliases) { // 注冊別名 registry.registerAlias(beanName, alias); } } }
通過beanName注冊BeanDefinition主要進行了4個步驟:
- 對AbstractBeanDefinition的校驗,主要是對于AbstractBeanDefinition的methodOverrides屬性的
- 對于beanName已經注冊的情況的處理,如果已經設定了不允許bean的覆寫,會拋出例外,否則進行覆寫
- 加入Map快取,beanName為key,BeanDefinition為value
- 清除之前留下的對應beanName的快取
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) { if (beanDefinition instanceof AbstractBeanDefinition) { // 對AbstractBeanDefinition的校驗 ((AbstractBeanDefinition) beanDefinition).validate(); } BeanDefinition oldBeanDefinition = this.beanDefinitionMap.get(beanName); if (oldBeanDefinition != null) { if (!isAllowBeanDefinitionOverriding()) { // 如果beanName已經注冊,并且設定了不允許bean覆寫,會拋出例外 throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName, "Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName + "': There is already [" + oldBeanDefinition + "] bound."); } // 將beanDefinition存到Map,beanName為key this.beanDefinitionMap.put(beanName, beanDefinition); } else { if (hasBeanCreationStarted()) { synchronized (this.beanDefinitionMap) { // 將beanDefinition存到Map,beanName為key this.beanDefinitionMap.put(beanName, beanDefinition); } } else { // 將beanDefinition存到Map,beanName為key this.beanDefinitionMap.put(beanName, beanDefinition); } } if (oldBeanDefinition != null || containsSingleton(beanName)) { // 清除之前留下的對應beanName的快取 resetBeanDefinition(beanName); } }
注冊別名的原理相對簡單,分為4個步驟:
- alias和beanName相同情況處理,此時會洗掉原有的alias
- alias覆寫處理,若aliasName已經使用了并已經指向了另一個beanName,且設定了別名不能覆寫,則會拋出例外
- alias回圈檢查,如果出現了別名回圈的情況,則拋出例外
- 注冊alias
public void registerAlias(String name, String alias) { if (alias.equals(name)) { this.aliasMap.remove(alias); } else { String registeredName = (String)this.aliasMap.get(alias); if (registeredName != null) { if (registeredName.equals(name)) { return; } // 若aliasName已經使用了并已經指向了另一個beanName,且設定了別名不能覆寫,則會拋出例外 if (!this.allowAliasOverriding()) { throw new IllegalStateException("Cannot register alias '" + alias + "' for name '" + name + "': It is already registered for name '" + registeredName + "'."); } } // alias回圈檢查 this.checkForAliasCircle(name, alias); // alias注冊 this.aliasMap.put(alias, name); } }
4)通知監聽器決議及注冊完成
通過代碼this.getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder))完成此功能,這里的實作只是為了擴展,當需要對注冊BeanDefinition事件進行監聽時可以通過注冊監聽器的方式并將處理邏輯寫入監聽器中,目前Spring沒有對此事件進行任何處理,
2.7.2 alias標簽的決議
對alias標簽的決議是通過processAliasRegistration(ele)方法處理的,
protected void processAliasRegistration(Element ele) { String name = ele.getAttribute(NAME_ATTRIBUTE); String alias = ele.getAttribute(ALIAS_ATTRIBUTE); boolean valid = true; if (!StringUtils.hasText(name)) { valid = false; } if (!StringUtils.hasText(alias)) { valid = false; } if (valid) { // alias標簽決議 getReaderContext().getRegistry().registerAlias(name, alias); // 通知監聽器 getReaderContext().fireAliasRegistered(name, alias, extractSource(ele)); } }
進入方法內部可以看到,this.getReaderContext().getRegistry().registerAlias(name, alias)方法實作了alias標簽的決議,而這個方法實際就是前面注冊別名的那個方法,this.getReaderContext().fireAliasRegistered(name, alias, this.extractSource(ele))這個方法用于在別名注冊后通知監聽器做相應的處理,這里的實作只是為了擴展,目前Spring沒有對此事件進行任何處理,
2.7.3 import標簽的決議
對import標簽的決議,Spring大致分為以下步驟:
- 獲取import標簽的resource屬性配置的路徑
- 決議路徑中的系統屬性,格式如“${user.dir}”,對應方法this.getReaderContext().getEnvironment().resolveRequiredPlaceholders(location)
- 判斷resource屬性配置的路徑是絕對路徑還是相對路徑
- 如果是絕對路徑,則呼叫bean的決議程序進行決議
- 如果是相對路徑則計算出絕對路徑后進行決議
- 通知監聽器,決議完成
不管是絕對路徑下import標簽的決議還是相對路徑下import標簽的決議,通過跟蹤代碼發現,最后都會調到XmlBeanDefinitionReader類的loadBeanDefinitions方法,而這個方法在加載bean部分已經了解了,
2.7.4 嵌入式beans標簽的決議
該標簽決議呼叫的是DefaultBeanDefinitionDocumentReader的doRegisterBeanDefinitions方法,而這個方法已經在決議及注冊BeanDefinitions部分了解了,嵌入式beans標簽和非嵌入式beans標簽的決議程序其實是一樣的,
2.8 自定義標簽的決議
自定義標簽非常有用,我們熟知的標簽就是采用自定義標簽的原理實作的,下面來探究一下自定義標簽的決議原理,
前面提到過決議自定義標簽的入口,查看以下具體實作:
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) { String namespaceUri = this.getNamespaceURI(ele); NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri); if (handler == null) { return null; } else { return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd)); } }
這里傳入的containingBd為null,可以看到自定義標簽決議的思路特別簡單,無非是根據標簽元素獲取對應的命名空間,根據命名空間決議對應的處理器,然后根據用戶自定義的處理器進行決議,
2.8.1 決議自定義標簽處理器
通過元素可以獲取它的命名空間,有了命名空間就可以進行NamespaceHandler提取了,在readerContext初始化的時候其屬性namespaceHandlerResolver被初始化為DefaultNamespaceHandlerResolver的實體,所以呼叫resolve方法實際呼叫的是DefaultNamespaceHandlerResolver的方法,
public NamespaceHandler resolve(String namespaceUri) { // 獲取命名空間到處理器的映射關系 Map<String, Object> handlerMappings = getHandlerMappings(); Object handlerOrClassName = handlerMappings.get(namespaceUri); if (handlerOrClassName == null) { return null; } else if (handlerOrClassName instanceof NamespaceHandler) { return (NamespaceHandler) handlerOrClassName; } else { String className = (String) handlerOrClassName; Class<?> handlerClass = ClassUtils.forName(className, this.classLoader); if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) { throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri + "] does not implement the [" + NamespaceHandler.class.getName() + "] interface"); } // 實體化命名空間處理器 NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass); namespaceHandler.init(); handlerMappings.put(namespaceUri, namespaceHandler); return namespaceHandler; } } private Map<String, Object> getHandlerMappings() { if (this.handlerMappings == null) { synchronized (this) { if (this.handlerMappings == null) { // 加載組態檔META-INF/spring.handlers Properties mappings = PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader); Map<String, Object> handlerMappings = new ConcurrentHashMap<String, Object>(mappings.size()); CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings); this.handlerMappings = handlerMappings; } } } return this.handlerMappings; }
可以看到自定義標簽處理器的決議流程,首先通過this.getHandlerMappings()方法決議組態檔獲取命名空間到處理器的映射關系,Map保存,然后實體化該命名空間處理器,呼叫init()初始化方法,而定義的this.handlerMappingsLocation變數在呼叫構造方法的時候代碼寫死了,是META-INF/spring.handlers,這個組態檔是用戶自己去撰寫的,定義命名空間到處理器類的映射,命名空間處理器類的實作也是需要用戶去實作,用戶可以繼承NamespaceHandlerSupport抽象類實作一下init()抽象方法,
2.8.2 標簽解析
我們已經得到了由哪個標簽處理器進行處理,接下來標簽決議由handler.parse(ele, new ParserContext(this.readerContext, this, containingBd))去實作,
public BeanDefinition parse(Element element, ParserContext parserContext) { // 獲取元素決議器,進行決議 return findParserForElement(element, parserContext).parse(element, parserContext); } private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) { // 根據節點名稱獲取parser String localName = parserContext.getDelegate().getLocalName(element); BeanDefinitionParser parser = this.parsers.get(localName); return parser; }
在父類NamespaceHandlerSupport中可以看到,決議功能首先找到決議器,然后進行決議,查找決議器首先獲取節點名稱,然后通過Map parsers獲取對應節點的決議器,而這個Map的賦值一般在用戶實作的命名空間處理器init()方法中呼叫,
而自定義標簽的決議任務由parse方法完成,可以看到,首先通過parseInternal方法將標簽元素轉換成了BeanDefinition,然后決議id和name屬性并用BeanDefinitionHolder封裝元素資訊,接著進行BeanDefinition的注冊,最后通知監聽器,
public final BeanDefinition parse(Element element, ParserContext parserContext) { // 決議元素 AbstractBeanDefinition definition = this.parseInternal(element, parserContext); if (definition != null && !parserContext.isNested()) { // 決議id String id = this.resolveId(element, definition, parserContext); String[] aliases = null; if (this.shouldParseNameAsAliases()) { // 決議name String name = element.getAttribute("name"); if (StringUtils.hasLength(name)) { aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name)); } } // holder封裝元素資訊 BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases); // 注冊 this.registerBeanDefinition(holder, parserContext.getRegistry()); if (this.shouldFireEvents()) { BeanComponentDefinition componentDefinition = new BeanComponentDefinition(holder); this.postProcessComponentDefinition(componentDefinition); // 通知監聽器 parserContext.registerComponent(componentDefinition); } } return definition; }
對于后面三步前面都介紹過了,只需看下parseInternal方法邏輯,
protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) { BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(); // 決議parentName String parentName = this.getParentName(element); if (parentName != null) { builder.getRawBeanDefinition().setParentName(parentName); } // 決議beanClass Class<?> beanClass = this.getBeanClass(element); if (beanClass != null) { builder.getRawBeanDefinition().setBeanClass(beanClass); } else { String beanClassName = this.getBeanClassName(element); if (beanClassName != null) { builder.getRawBeanDefinition().setBeanClassName(beanClassName); } } // 決議source builder.getRawBeanDefinition().setSource(parserContext.extractSource(element)); if (parserContext.isNested()) { builder.setScope(parserContext.getContainingBeanDefinition().getScope()); } // 決議lazyInit if (parserContext.isDefaultLazyInit()) { builder.setLazyInit(true); } this.doParse(element, parserContext, builder); return builder.getBeanDefinition(); } protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { this.doParse(element, builder); } protected void doParse(Element element, BeanDefinitionBuilder builder) { }
可以看到,parseInternal方法實際就是先決議parentName、beanClass、source、scope和lazyInit,以BeanDefinition封裝,然后呼叫doParse方法,這個方法也是用戶自定義決議器需要實作的方法,最后回傳這個BeanDefinition,
3 總結
至此,我們一步一步地分析了Spring容器創建基本原理的所有內容,沒想到短短一句new XmlBeanFactory(new ClassPathResource(“beanFactoryTest.xml”)),Spring做了這么多事:組態檔的封裝、Document獲取和決議及注冊BeanDefinition等,決議及注冊BeanDefinition對默認標簽和自定義標簽進行不同的處理,對于默認標簽,又分別對bean標簽、alias標簽、import標簽和beans標簽進行不同的處理,其中bean標簽決議邏輯最為復雜也最為基礎,而剩下的那幾個標簽又復用了bean標簽處理的部分邏輯,
我們從中不僅可以學到Spring容器創建的基本原理,還可以學到許多編碼規范及技巧,了解到好的代碼是什么樣子的,比如其中應用到的單一職責原則、模板方法模式等等,而且還可以發現,它的代碼邏輯很清晰,往往通過它的方法名稱就知道這個方法的功能,并且每個方法也不會特別長,增加了代碼的可讀性和可維護性,并且代碼封裝性很好,很多復雜的功能代碼都可以復用,
在我們之后的開發作業中需要不斷學習好的編碼技巧及規范,應用到日常開發作業當中,最終形成我們自己的編碼技巧及風格,
4 參考資料
《Spring技術內幕:深入決議Spring架構與設計原理》
《Spring原始碼深度決議》
作者:曹銘(大件技術工坊)
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/533518.html
標籤:其他
