代碼入口 之前寫文章都會啰啰嗦嗦一大堆再開始,進入【Spring源碼分析】這個板塊就直接切入正題了。 很多朋友可能想看Spring源碼,但是不知道應當如何入手去看,這個可以理解:Java開發者通常從事的都是Java Web的工作,對於程式員來說,一個Web項目用到Spring,只是配置一下配置文件而 ...
代碼入口
之前寫文章都會啰啰嗦嗦一大堆再開始,進入【Spring源碼分析】這個板塊就直接切入正題了。
很多朋友可能想看Spring源碼,但是不知道應當如何入手去看,這個可以理解:Java開發者通常從事的都是Java Web的工作,對於程式員來說,一個Web項目用到Spring,只是配置一下配置文件而已,Spring的載入過程相對是不太透明的,不太好去找載入的代碼入口。
下麵有很簡單的一段代碼可以作為Spring代碼載入的入口:
1 ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml"); 2 ac.getBean(XXX.class);
ClassPathXmlApplicationContext用於載入CLASSPATH下的Spring配置文件,可以看到,第二行就已經可以獲取到Bean的實例了,那麼必然第一行就已經完成了對所有Bean實例的載入,因此可以通過ClassPathXmlApplicationContext作為入口。為了後面便於代碼閱讀,先給出一下ClassPathXmlApplicationContext這個類的繼承關係:
大致的繼承關係是如上圖所示的,由於版面的關係,沒有繼續畫下去了,左下角的ApplicationContext應當還有一層繼承關係,比較關鍵的一點是它是BeanFactory的子介面。
最後聲明一下,本文使用的Spring版本為3.0.7,比較老,使用這個版本純粹是因為公司使用而已。
ClassPathXmlApplicationContext構造函數
看下ClassPathXmlApplicationContext的構造函數:
1 public ClassPathXmlApplicationContext(String configLocation) throws BeansException { 2 this(new String[] {configLocation}, true, null); 3 }
1 public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) 2 throws BeansException { 3 4 super(parent); 5 setConfigLocations(configLocations); 6 if (refresh) { 7 refresh(); 8 } 9 }
從第二段代碼看,總共就做了三件事:
1、super(parent)
沒什麼太大的作用,設置一下父級ApplicationContext,這裡是null
2、setConfigLocations(configLocations)
代碼就不貼了,一看就知道,裡面做了兩件事情:
(1)將指定的Spring配置文件的路徑存儲到本地
(2)解析Spring配置文件路徑中的${PlaceHolder}占位符,替換為系統變數中PlaceHolder對應的Value值,System本身就自帶一些系統變數比如class.path、os.name、user.dir等,也可以通過System.setProperty()方法設置自己需要的系統變數
3、refresh()
這個就是整個Spring Bean載入的核心了,它是ClassPathXmlApplicationContext的父類AbstractApplicationContext的一個方法,顧名思義,用於刷新整個Spring上下文信息,定義了整個Spring上下文載入的流程。
refresh方法
上面已經說了,refresh()方法是整個Spring Bean載入的核心,因此看一下整個refresh()方法的定義:
1 public void refresh() throws BeansException, IllegalStateException { 2 synchronized (this.startupShutdownMonitor) { 3 // Prepare this context for refreshing. 4 prepareRefresh(); 5 6 // Tell the subclass to refresh the internal bean factory. 7 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); 8 9 // Prepare the bean factory for use in this context. 10 prepareBeanFactory(beanFactory); 11 12 try { 13 // Allows post-processing of the bean factory in context subclasses. 14 postProcessBeanFactory(beanFactory); 15 16 // Invoke factory processors registered as beans in the context. 17 invokeBeanFactoryPostProcessors(beanFactory); 18 19 // Register bean processors that intercept bean creation. 20 registerBeanPostProcessors(beanFactory); 21 22 // Initialize message source for this context. 23 initMessageSource(); 24 25 // Initialize event multicaster for this context. 26 initApplicationEventMulticaster(); 27 28 // Initialize other special beans in specific context subclasses. 29 onRefresh(); 30 31 // Check for listener beans and register them. 32 registerListeners(); 33 34 // Instantiate all remaining (non-lazy-init) singletons. 35 finishBeanFactoryInitialization(beanFactory); 36 37 // Last step: publish corresponding event. 38 finishRefresh(); 39 } 40 41 catch (BeansException ex) { 42 // Destroy already created singletons to avoid dangling resources. 43 destroyBeans(); 44 45 // Reset 'active' flag. 46 cancelRefresh(ex); 47 48 // Propagate exception to caller. 49 throw ex; 50 } 51 } 52 }
每個子方法的功能之後一點一點再分析,首先refresh()方法有幾點是值得我們學習的:
1、方法是加鎖的,這麼做的原因是避免多線程同時刷新Spring上下文
2、儘管加鎖可以看到是針對整個方法體的,但是沒有在方法前加synchronized關鍵字,而使用了對象鎖startUpShutdownMonitor,這樣做有兩個好處:
(1)refresh()方法和close()方法都使用了startUpShutdownMonitor對象鎖加鎖,這就保證了在調用refresh()方法的時候無法調用close()方法,反之亦然,避免了衝突
(2)另外一個好處不在這個方法中體現,但是提一下,使用對象鎖可以減小了同步的範圍,只對不能併發的代碼塊進行加鎖,提高了整體代碼運行的效率
3、方法裡面使用了每個子方法定義了整個refresh()方法的流程,使得整個方法流程清晰易懂。這點是非常值得學習的,一個方法裡面幾十行甚至上百行代碼寫在一起,在我看來會有三個顯著的問題:
(1)擴展性降低。反過來講,假使把流程定義為方法,子類可以繼承父類,可以根據需要重寫方法
(2)代碼可讀性差。很簡單的道理,看代碼的人是願意看一段500行的代碼,還是願意看10段50行的代碼?
(3)代碼可維護性差。這點和上面的類似但又有不同,可維護性差的意思是,一段幾百行的代碼,功能點不明確,不易後人修改,可能會導致“牽一發而動全身”
prepareRefresh方法
下麵挨個看refresh方法中的子方法,首先是prepareRefresh方法,看一下源碼:
1 /** 2 * Prepare this context for refreshing, setting its startup date and 3 * active flag. 4 */ 5 protected void prepareRefresh() { 6 this.startupDate = System.currentTimeMillis(); 7 synchronized (this.activeMonitor) { 8 this.active = true; 9 } 10 11 if (logger.isInfoEnabled()) { 12 logger.info("Refreshing " + this); 13 } 14 }
這個方法功能比較簡單,顧名思義,準備刷新Spring上下文,其功能註釋上寫了:
1、設置一下刷新Spring上下文的開始時間
2、將active標識位設置為true
另外可以註意一下12行這句日誌,這句日誌列印了真正載入Spring上下文的Java類。
obtainFreshBeanFactory方法
obtainFreshBeanFactory方法的作用是獲取刷新Spring上下文的Bean工廠,其代碼實現為:
1 protected ConfigurableListableBeanFactory obtainFreshBeanFactory() { 2 refreshBeanFactory(); 3 ConfigurableListableBeanFactory beanFactory = getBeanFactory(); 4 if (logger.isDebugEnabled()) { 5 logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory); 6 } 7 return beanFactory; 8 }
其核心是第二行的refreshBeanFactory方法,這是一個抽象方法,有AbstractRefreshableApplicationContext和GenericApplicationContext這兩個子類實現了這個方法,看一下上面ClassPathXmlApplicationContext的繼承關係圖即知,調用的應當是AbstractRefreshableApplicationContext中實現的refreshBeanFactory,其源碼為:
1 protected final void refreshBeanFactory() throws BeansException { 2 if (hasBeanFactory()) { 3 destroyBeans(); 4 closeBeanFactory(); 5 } 6 try { 7 DefaultListableBeanFactory beanFactory = createBeanFactory(); 8 beanFactory.setSerializationId(getId()); 9 customizeBeanFactory(beanFactory); 10 loadBeanDefinitions(beanFactory); 11 synchronized (this.beanFactoryMonitor) { 12 this.beanFactory = beanFactory; 13 } 14 } 15 catch (IOException ex) { 16 throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex); 17 } 18 }
這段代碼的核心是第7行,這行點出了DefaultListableBeanFactory這個類,這個類是構造Bean的核心類,這個類的功能會在這一篇文章中詳細解讀,首先給出DefaultListableBeanFactory的繼承關係圖:
AbstractAutowireCapableBeanFactory這個類的繼承層次比較深,版面有限,就沒有繼續畫下去了,本圖基本上清楚地展示了DefaultListableBeanFactory的層析結構。
XML文件解析
另外一個核心是第10行,loadBeanDefinitions(beanFactory)方法,為什麼我們配置的XML文件最後能轉成Java Bean,首先就是由這個方法處理的。該方法最終的目的是將XML文件進行解析,以Key-Value的形式,Key表示BeanName,Value為BeanDefinition,最終存入DefaultListableBeanFactory中:
1 /** Map of bean definition objects, keyed by bean name */ 2 private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>(); 3 4 /** List of bean definition names, in registration order */ 5 private final List<String> beanDefinitionNames = new ArrayList<String>();
最終DefaultListableBeanFactory會先遍歷beanDefinitionNames,從beanDefinitionMap中拿到對應的BeanDefinition,最終轉為具體的Bean對象。BeanDefinition本身是一個介面,AbstractBeanDefinition這個抽象類存儲了Bean的屬性,看一下AbstractBeanDefinition這個抽象類的定義:
這個類的屬性與方法很多,這裡就列舉了一些最主要的方法和屬性,可以看到包含了bean標簽中的所有屬性,之後就是根據AbstractBeanDefinition中的屬性值構造出對應的Bean對象。
Spring沒有直接拿到XML中的bean定義就直接轉為具體的Bean對象,就是給Spring開發者留下了擴展點,比如之前BeanPostProcessor,在最後一部分會簡單提及。接著看一下XML是如何轉為Bean的,首先在AbstractXmlApplicationContext中將DefaultListableBeanFactory轉換為XmlBeanDefinitionReader:
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 }
XmlBeanDefinitionReader顧名思義,一個XML文件中讀取Bean定義的工具,然後追蹤13行的代碼,先追蹤到DefaultBeanDefinitionDocumentReader的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 }
XML文件的節點import、alias、bean分別有自己對應的方法去處理,以最常見的Bean為例,即第9行:
1 protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { 2 BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); 3 if (bdHolder != null) { 4 bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); 5 try { 6 // Register the final decorated instance. 7 BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); 8 } 9 catch (BeanDefinitionStoreException ex) { 10 getReaderContext().error("Failed to register bean definition with name '" + 11 bdHolder.getBeanName() + "'", ele, ex); 12 } 13 // Send registration event. 14 getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder)); 15 } 16 }
核心的解析bean節點的代碼為第2行,這裡已經調用到了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, 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 }
總結一下代碼邏輯:
(1)第2行和第3行,獲取id屬性和name屬性
(2)第5行~第9行,如果填寫了name屬性的話,將name屬性以",;",分割出來的字元串全部認為這個bean的別名,這裡我們可以學到Spring的StringUtils的tokenizeToStringArray方法,可以將字元串按照指定分割符分割為字元串數組
(3)第11行~第18行,預設beanName為id屬性,如果bean有配置別名(即上面的name屬性的話),以name屬性的第一個值作為beanName,發現很多人不知道beanName是什麼,這幾行代碼就表示了容器是如何定義beanName的
(4)第20行~第22行,這段用於保證beanName的唯一性的,BeanDefinitionParserDelegate中有一個屬性usedNames,這是一個Set,強制性地保證了beanName的唯一性
(5)第24行用於解析bean的其他屬性,後面的代碼不太重要,看一下parseBeanDefinitionElement的實現
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 bd.setResource(this.readerContext.getResource()); 29 bd.setSource(extractSource(ele)); 30 31 return bd; 32 } 33 catch (ClassNotFoundException ex) { 34 error("Bean class [" + className + "] not found", ele, ex); 35 } 36 catch (NoClassDefFoundError err) { 37 error("Class that bean class [" + className + "] depends on not found", ele, err); 38 } 39 catch (Throwable ex) { 40 error("Unexpected failure during bean definition parsing", ele, ex); 41 } 42 finally { 43 this.parseState.pop(); 44 } 45 46 return null; 47 }
這裡會取class屬性、parent屬性,18行的代碼可以跟進去看一下這裡就不貼了,會取得scope、lazy-init、abstract、depends-on屬性等等,設置到BeanDefinition中,這樣大致上,一個Bean的定義就被存入了BeanDefinition中。最後一步追溯到之前DefaultBeanDefinitionDocumentReader的processBeanDefinition方法:
1 BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
這句語句將BeanDefinition存入DefaultListableBeanFactory的beanDefinitionMap中,追蹤一下代碼最終到DefaultListableBeanFactory的registerBeanDefinition方法中:
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 }
大致上就是beanDefinitionNames中增加一個beanName,beanDefinitionMap將老的BeanDefinition替換(假如不允許BeanDefinition重寫的話會拋出異常)。這樣一個漫長的流程過後,XML文件中的各個bean節點被轉換為BeanDefinition,存入了DefaultListableBeanFactory中,後續DefaultListableBeanFactory可以根據BeanDefinition,構造對應的Bean對象出來。
其他部分
上面分析了Spring上下文載入的代碼入口以及refresh方法中的一些重點部分,下麵還有一些子方法沒有分析,這裡簡單分析一下:
1、prepareBeanFactory方法
prepareBeanFactory用於配置BeanFactory上下文的標準特征,比如載入Spring上下文使用的ClassLoader以及一些前置處理器
2、postProcessBeanFactory方法
在標準初始化之後修改應用上下文的內部BeanFactory,所有Bean的定義已經被載入完畢了,但是Bean還沒有被初始化,此時允許在特定的ApplicationContext上下文中註冊特定的BeanPostProcessor
3、invokeBeanFactoryPostProcessors方法
初始化並且調用所有已經註冊的BeanProcessor介面,按照BeanProcessor介面給定的順序調用。其實最終就是調用BeanProcessor介面的postProcessBeanFactory方法,傳入BeanFactory
4、registerBeanPostProcessors方法
註冊所有的BeanPostProcessor方法
5、initMessageSource方法
初始化上下文的MessageSource
6、initApplicationEventMulticaster方法
初始化上下文的多播事件
7、onRefresh方法
在指定的上下文子類中初始化其他特殊的Bean
8、finishBeanFactoryInitialization方法
初始化所有非懶載入單例Bean,標紅加粗表示這是重點,所有單例Bean通過這個方法載入,後文也是圍繞這個方法展開
9、finishRefresh方法
最後一步,結束刷新上下文,發佈正確的事件