1、bean組件:
1. BeanFactory: listableBeanFactory & hierarchicalBeanFactory & AutoWireCapableBeanFactory
最終實現:defaultListableBeanFactoryhtml
2. Bean: 最終BeanDefinition,全部操做對象基於此。spring
3. Bean解析:XmlBeanDefinitionReader完成。編程
2、context組件:
1. 功能:spring運行環境,保存各對象關係,建立bean,捕獲事件。
2. 繼承beanFactory ,說明spring主要是bean。
繼承ResourceLoader,可訪問全部外部資源。設計模式
3、Core組件:
1. Resource定義了資源的訪問方式
Resource封裝了多種資源類型:
即,全部資源均可經過getInputStream()獲取,返回InputStream。同時知足了資源提供者、資源使用者的方便。
2. 經過ResourceLoader加載資源:
Resource resource = ResourceLoader.getResource(String location);
3. Context資源加載、解析和描述經過ResourcePatternResolver完成。數據結構
IOC:Inversion of Control(控制反轉)。IOC它所體現的並非一種技術,而是一種思想,一種將設計好的對象交給容器來管理的思想。IOC的核心思想就體如今控制、反轉這兩個詞上面,要理解就必需要理解幾個問題:app
一、誰控制誰?在傳統的開發工做中,咱們通常都是主動去new一個對象,這個是主動控制依賴對象。可是對於IOC而已,控制權會被移交給容器,因此應該是IOC容器控制對象。ide
二、控制什麼?既然是IOC容器控制對象,那控制什麼呢?IOC容器除了負責控制對象的生成還包括外部資源的獲取。函數
三、爲什麼是反轉?對象主動生成依賴對象,咱們稱之爲「正轉」,可是如今有IOC來負責了,因此反轉則是IOC容器來負責對象的生成和注入過程。post
四、那些地方反轉?依賴對象的獲取被反轉了。學習
對於IOC而言,它強調是將主動變爲被動,由IOC容器來負責依賴對象的建立和查找,由IOC容器來進行注入組合對象。將原來的強聯繫、高耦合轉變爲了弱關係、鬆耦合。IOC,它能指導咱們如何設計出鬆耦合、更加優良的程序,把應用程序從原來須要維護依賴對象之間關係中完全解放出來而更加專一於業務邏輯,這樣會使得程序的整個體系機構變得很是靈活。
其實IoC對編程帶來的最大改變不是從代碼上,而是從思想上,發生了「主從換位」的變化。應用程序本來是老大,要獲取什麼資源都是主動出擊,可是在IoC/DI思想中,應用程序就變成被動的了,被動的等待IoC容器來建立並注入它所須要的資源了。
有了IOC就有必要提到DI了。DI,Dependency Injection,即「依賴注入」。其實IOC和DI本就是同一個概念的兩種不一樣的表述,DI所描述的是由容器動態地將某個依賴關係注入到主鍵當中去,其須要理解以下幾個概念:
一、誰依賴誰?應用程序依賴IOC容器。
二、依賴什麼?由於應用程序再也不主動去建立對象,由IOC容器來嚮應用程序注入,因此應該是應用程序依賴IOC容器來提供的外部資源。
三、誰注入誰?由IOC容器嚮應用程序注入。
四、注入什麼?注入的某個對象所依賴的外部資源。
通俗點將就是IOC就是容器控制應用程序所須要外部資源的建立和管理,而後將其反轉給應用程序;而DI是應用程序依賴容器提供的外部對象,容器將其依賴的外部資源在運行期注入到應用程序中。二者表達的意思都是容器負責應用程序的建立和管理,應用程序只須要在須要它們的時候等待容器將其所依賴的外部資源提供就行,至於來自哪裏,怎麼來的應用程序都不須要知道。
具體的IOC理解我就很少闡述了,網上實在是太多了,這裏推薦幾篇博客:
二、【第二章】 IoC 之 2.1 IoC基礎 ——跟我學Spring3
四、spring ioc原理(看完後你們能夠本身寫一個spring)
IOC做爲一個容器,它裏面放得都是bean、bean與bean之間的對應關係,而bean之間的對應關係咱們開始都是經過xml配置文件來體現的。那麼這裏就反饋了以下幾個問題:
一、對應與對象之間的關係是經過xml配置文件來描述的(固然也能夠是properties等文件)。
二、描述的文件存放位置在那裏,通常來講咱們都是放在classpath目錄下的,可是也但是是URL、fileSystem。
三、文件的解析。
四、Bean在容器中的表現形式,也就是它的數據結構。
對於Spring而言,它用Resource、BeanDefinition、BeanDefinitionReader、BeanFactory、ApplicationContext五個組件來實現以上問題,而同時這5個接口定義了 spring ioc 容器的基本代碼組件結構。下面咱們逐一瞭解這五個結構
Resource
Resource,對資源的抽象,它的每個實現類都表明了一種資源的訪問策略,如ClasspathResource 、 URLResource ,FileSystemResource 等。
BeanDefinition
用來描述和抽象一個具體的Bean對象,它是描述Bean對象的基本數據結構。
BeanDefinitionReader
外部資源所表達的語義須要統一轉化爲統一的內部數據結構BeanDefinition,這個時候BeanDefinitionReader就起到統一解析的做用力了。對應不一樣的描述須要有不一樣的 Reader 。如 XmlBeanDefinitionReader 用來讀取xml 描述配置的 bean 對象。
BeanFactory
BeanFactory是一個很是純粹的bean容器,它是IOC必備的數據結構,其中BeanDefinition是她的基本結構,它內部維護着一個BeanDefinition map,並可根據BeanDefinition 的描述進行 bean 的建立和管理。
ApplicationContext
這個就是大名鼎鼎的Spring容器,它叫作應用上下文,與咱們應用息息相關,她繼承BeanFactory,因此它是BeanFactory的擴展升級版,若是BeanFactory是屌絲的話,那麼ApplicationContext則是名副其實的高富帥。因爲ApplicationContext的結構就決定了它與BeanFactory的不一樣,其主要區別有:
一、繼承MessageSource,提供國際化的標準訪問策略。
二、繼承ApplicationEventPublisher,提供強大的事件機制。
三、擴展ResourceLoader,能夠用來加載多個Resource,能夠靈活訪問不一樣的資源。
四、對Web應用的支持。
下圖是上面組合關係圖(以ClasspathXmlApplicationContext 爲例)
以上圖片均來自:啃啃老菜: Spring IOC核心源碼學習(一)
ClassPathXmlApplicationContext的refresh() 方法負責完成了整個容器的初始化。
爲 什麼叫refresh?也就是說實際上是刷新的意思,該IOC容器裏面維護了一個單例的BeanFactory,若是bean的配置有修改,也能夠直接調用 refresh方法,它將銷燬以前的BeanFactory,從新建立一個BeanFactory。因此叫refresh也是能理解的。
如下是Refresh的基本步驟:
1.把配置xml文件轉換成resource。resource的轉換是先經過ResourcePatternResolver來解析可識別格式的配置文件的路徑(如"classpath*:"等),若是沒有指定格式,默認會按照類路徑的資源來處理。
2.利用XmlBeanDefinitionReader完成對xml的解析,將xml Resource裏定義的bean對象轉換成統一的BeanDefinition。
3.將BeanDefinition註冊到BeanFactory,完成對BeanFactory的初始化。BeanFactory裏將會維護一個BeanDefinition的Map。
當getBean的時候就會根據調用BeanFactory,根據bean的BeanDifinition來實例化一個bean。固然根據bean的lazy-init、protetype等屬性設置不一樣以上過程略有差異。
refresh()代碼以下:
public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Prepare this context for refreshing. prepareRefresh(); // Tell the subclass to refresh the internal bean factory. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare the bean factory for use in this context. prepareBeanFactory(beanFactory); try { // Allows post-processing of the bean factory in context subclasses. postProcessBeanFactory(beanFactory); // Invoke factory processors registered as beans in the context. invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation. registerBeanPostProcessors(beanFactory); // Initialize message source for this context. initMessageSource(); // Initialize event multicaster for this context. initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. onRefresh(); // Check for listener beans and register them. registerListeners(); // Instantiate all remaining (non-lazy-init) singletons. finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event. finishRefresh(); } catch (BeansException ex) { // Destroy already created singletons to avoid dangling resources. beanFactory.destroySingletons(); // Reset 'active' flag. cancelRefresh(ex); // Propagate exception to caller. throw ex; } } }
以上的obtainFreshBeanFactory是很關鍵的一個方法,裏面會調用loadBeanDefinition方法,以下:
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws IOException { // Create a new XmlBeanDefinitionReader for the given BeanFactory. XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); // Configure the bean definition reader with this context's // resource loading environment. beanDefinitionReader.setResourceLoader(this); beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this)); // Allow a subclass to provide custom initialization of the reader, // then proceed with actually loading the bean definitions. initBeanDefinitionReader(beanDefinitionReader); loadBeanDefinitions(beanDefinitionReader); }
LoadBeanDifinition方法很關鍵,這裏特定於整個IOC容器,實例化了一個XmlBeanDefinitionReader來解析Resource文件。關於Resource文件如何初始化和xml文件如何解析都在
loadBeanDefinitions(beanDefinitionReader);
裏面的層層調用完成,這裏不在累述。
下面LZ將盡全力闡述IOC的初始化過程和在該過程當中涉及的重要組件,這系列博客是也是LZ學習、研究Spring機制和源碼的學習筆記,其中不免會參考別人的博客,若有雷同,純屬借鑑。同時也避免不了錯誤之處,博文中的錯誤望各位博友指出,不勝感激!!!
參考資料
首先咱們先來看看以下一段代碼
ClassPathResource resource = new ClassPathResource("bean.xml"); DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory); reader.loadBeanDefinitions(resource);
博友是否對這段簡單代碼記憶猶新呢? 這段代碼是編程式使用IOC容器,經過這個簡單的程序咱們初步斷定IOC容器的使用過程:
一、建立IOC配置文件的抽閒資源,也就是Resource接口。
二、建立BeanFactory,DefaultListtableBeanFactory是BeanFactory模式實現類。
三、建立一個BeanDefinitionReader對象,該對象爲BeanDefinition的讀取器。xml文件就使用XMLBeanDefinitionReader。
四、使用Reader來裝載配置文件。loadBeanDefinitions就包括了資源文件的解析和注入過程。
經過上面四個步驟咱們就能夠輕鬆地使用IOC容器了,在整個過程能夠剖析爲三個步驟,這三個步驟也是IOC容器的初始化過程:Resource定位、載入、註冊。以下:
Resource定位
咱們通常使用外部資源來描述Bean對象,因此IOC容器第一步就是須要定位Resource外部資源。Resource的定位其實就是BeanDefinition的資源定位,它是由ResourceLoader經過統一的Resource接口來完成的,這個Resource對各類形式的BeanDefinition的使用都提供了統一接口。
載入
第二個過程就是BeanDefinition的載入。BeanDefinitionReader讀取、解析Resource定位的資源,也就是將用戶定義好的Bean表示成IOC容器的內部數據結構也就是BeanDefinition。在IOC容器內部維護着一個BeanDefinition Map的數據結構,經過這樣的數據結構,IOC容器可以對Bean進行更好的管理。
在配置文件中每個<bean>都對應着一個BeanDefinition對象。
註冊
第三個過程則是註冊,即向IOC容器註冊這些BeanDefinition,這個過程是經過BeanDefinitionRegistery接口來實現的。在IOC容器內部實際上是將第二個過程解析獲得的BeanDefinition注入到一個HashMap容器中,IOC容器就是經過這個HashMap來維護這些BeanDefinition的。在這裏須要注意的一點是這個過程並無完成依賴注入,依賴註冊是發生在應用第一次調用getBean向容器所要Bean時。固然咱們能夠經過設置預處理,即對某個Bean設置lazyinit屬性,那麼這個Bean的依賴注入就會在容器初始化的時候完成。
通過這三個步驟,IOC容器的初始化過程就已經完成了,後面LZ會結合源代碼詳細闡述這三個過程的實現。下面來看看與IOC容器相關的體系結構圖,以ClassPathXmlApplicationContext爲例(圖片來自:【Spring】IOC核心源碼學習(二):容器初始化過程)
左邊黃色部分是 ApplicationContext 體系繼承結構,右邊是 BeanFactory 的結構體系。
咱們知道Spring的IoC起到了一個容器的做用,其中裝得都是各類各樣的Bean。同時在咱們剛剛開始學習Spring的時候都是經過xml文件來定義Bean,Spring會某種方式加載這些xml文件,而後根據這些信息綁定整個系統的對象,最終組裝成一個可用的基於輕量級容器的應用系統。
Spring IoC容器總體能夠劃分爲兩個階段,容器啓動階段,Bean實例化階段。其中容器啓動階段蛀牙包括加載配置信息、解析配置信息,裝備到BeanDefinition中以及其餘後置處理,而Bean實例化階段主要包括實例化對象,裝配依賴,生命週期管理已經註冊回調。下面LZ先介紹容器的啓動階段的第一步,即定位配置文件。
咱們使用編程方式使用DefaultListableBeanFactory時,首先是須要定義一個Resource來定位容器使用的BeanDefinition。
ClassPathResource resource = new ClassPathResource("bean.xml");
經過這個代碼,就意味着Spring會在類路徑中去尋找bean.xml並解析爲BeanDefinition信息。固然這個Resource並不能直接被使用,他須要被BeanDefinitionReader進行解析處理(這是後面的內容了)。
對於各類applicationContext,如FileSystemXmlApplicationContext、ClassPathXmlApplicationContext等等,咱們從這些類名就能夠看到他們提供了那些Resource的讀入功能。下面咱們以FileSystemXmlApplicationContext爲例來闡述Spring IoC容器的Resource定位。
先看FileSystemXmlApplicationContext繼承體系結構:
從圖中能夠看出FileSystemXMLApplicationContext繼承了DefaultResourceLoader,具有了Resource定義的BeanDefinition的能力,其源代碼以下:
public class FileSystemXmlApplicationContext extends AbstractXmlApplicationContext { /** * 默認構造函數 */ public FileSystemXmlApplicationContext() { } public FileSystemXmlApplicationContext(ApplicationContext parent) { super(parent); } public FileSystemXmlApplicationContext(String configLocation) throws BeansException { this(new String[] {configLocation}, true, null); } public FileSystemXmlApplicationContext(String... configLocations) throws BeansException { this(configLocations, true, null); } public FileSystemXmlApplicationContext(String[] configLocations, ApplicationContext parent) throws BeansException { this(configLocations, true, parent); } public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh) throws BeansException { this(configLocations, refresh, null); } //核心構造器 public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException { super(parent); setConfigLocations(configLocations); if (refresh) { refresh(); } } //經過構造一個FileSystemResource對象來獲得一個在文件系統中定位的BeanDefinition //採用模板方法設計模式,具體的實現用子類來完成 protected Resource getResourceByPath(String path) { if (path != null && path.startsWith("/")) { path = path.substring(1); } return new FileSystemResource(path); } }
AbstractApplicationContext中的refresh()方法是IoC容器初始化的入口,也就是說IoC容器的初始化是經過refresh()方法來完成整個調用過程的。在覈心構造器中就對refresh進行調用,經過它來啓動IoC容器的初始化工做。getResourceByPath爲一個模板方法,經過構造一個FileSystemResource對象來獲得一個在文件系統中定位的BeanDEfinition。getResourceByPath的調用關係以下(部分):
refresh爲初始化IoC容器的入口,可是具體的資源定位仍是在XmlBeanDefinitionReader讀入BeanDefinition時完成,loadBeanDefinitions() 加載BeanDefinition的載入。
protected final void refreshBeanFactory() throws BeansException { //判斷是否已經建立了BeanFactory,若是建立了則銷燬關閉該BeanFactory if (hasBeanFactory()) { destroyBeans(); closeBeanFactory(); } try { //建立DefaultListableBeanFactory實例對象 DefaultListableBeanFactory beanFactory = createBeanFactory(); beanFactory.setSerializationId(getId()); customizeBeanFactory(beanFactory); //加載BeanDefinition信息 loadBeanDefinitions(beanFactory); synchronized (this.beanFactoryMonitor) { this.beanFactory = beanFactory; } } catch (IOException ex) { throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex); } }
DefaultListableBeanFactory做爲BeanFactory默認實現類,其重要性不言而喻,而createBeanFactory()則返回該實例對象。
protected DefaultListableBeanFactory createBeanFactory() { return new DefaultListableBeanFactory(getInternalParentBeanFactory()); }
loadBeanDefinition方法加載BeanDefinition信息,BeanDefinition就是在這裏定義的。AbstractRefreshableApplicationContext對loadBeanDefinitions僅僅只是定義了一個抽象的方法,真正的實現類爲其子類AbstractXmlApplicationContext來實現:
AbstractRefreshableApplicationContext:
protected abstract void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException;
AbstractXmlApplicationContext:
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException { //建立bean的讀取器(Reader),即XmlBeanDefinitionReader,並經過回調設置到容器中 XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); // beanDefinitionReader.setEnvironment(getEnvironment()); //爲Bean讀取器設置Spring資源加載器 beanDefinitionReader.setResourceLoader(this); //爲Bean讀取器設置SAX xml解析器 beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this)); // initBeanDefinitionReader(beanDefinitionReader); //Bean讀取器真正實現的地方 loadBeanDefinitions(beanDefinitionReader); }
程序首先首先建立一個Reader,在前面就提到過,每一類資源都對應着一個BeanDefinitionReader,BeanDefinitionReader提供統一的轉換規則;而後設置Reader,最後調用loadBeanDefinition,該loadBeanDefinition纔是讀取器真正實現的地方:
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException { //獲取Bean定義資源的定位 Resource[] configResources = getConfigResources(); if (configResources != null) { reader.loadBeanDefinitions(configResources); } //獲取Bean定義資源的路徑。在FileSystemXMLApplicationContext中經過setConfigLocations能夠配置Bean資源定位的路徑 String[] configLocations = getConfigLocations(); if (configLocations != null) { reader.loadBeanDefinitions(configLocations); } }
首先經過getConfigResources()獲取Bean定義的資源定位,若是不爲null則調用loadBeanDefinitions方法來讀取Bean定義資源的定位。
loadBeanDefinitions是中的方法:
public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException { Assert.notNull(locations, "Location array must not be null"); int counter = 0; for (String location : locations) { counter += loadBeanDefinitions(location); } return counter; }
繼續:
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException { return loadBeanDefinitions(location, null); }
再繼續:
public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException { //獲取ResourceLoader資源加載器 ResourceLoader resourceLoader = getResourceLoader(); if (resourceLoader == null) { throw new BeanDefinitionStoreException( "Cannot import bean definitions from location [" + location + "]: no ResourceLoader available"); } // if (resourceLoader instanceof ResourcePatternResolver) { try { //調用DefaultResourceLoader的getResourceByPath完成具體的Resource定位 Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location); int loadCount = loadBeanDefinitions(resources); if (actualResources != null) { for (Resource resource : resources) { actualResources.add(resource); } } if (logger.isDebugEnabled()) { logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]"); } return loadCount; } catch (IOException ex) { throw new BeanDefinitionStoreException( "Could not resolve bean definition resource pattern [" + location + "]", ex); } } else { //調用DefaultResourceLoader的getResourceByPath完成具體的Resource定位 Resource resource = resourceLoader.getResource(location); int loadCount = loadBeanDefinitions(resource); if (actualResources != null) { actualResources.add(resource); } if (logger.isDebugEnabled()) { logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]"); } return loadCount; } }
在這段源代碼中經過調用DefaultResourceLoader的getResource方法:
public Resource getResource(String location) { Assert.notNull(location, "Location must not be null"); if (location.startsWith("/")) { return getResourceByPath(location); } //處理帶有classPath標識的Resource else if (location.startsWith(CLASSPATH_URL_PREFIX)) { return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader()); } else { try { //處理URL資源 URL url = new URL(location); return new UrlResource(url); } catch (MalformedURLException ex) { return getResourceByPath(location); } } }
在getResource方法中咱們能夠清晰地看到Resource資源的定位。這裏能夠清晰地看到getResourceByPath方法的調用,getResourceByPath方法的具體實現有子類來完成,在FileSystemXmlApplicationContext實現以下:
protected Resource getResourceByPath(String path) { if (path != null && path.startsWith("/")) { path = path.substring(1); } return new FileSystemResource(path); }
這樣代碼就回到了博客開初的FileSystemXmlApplicationContext 中來了,它提供了FileSystemResource 來完成從文件系統獲得配置文件的資源定義。固然這僅僅只是Spring IoC容器定位資源的一種邏輯,咱們能夠根據這個步驟來查看Spring提供的各類資源的定位,如ClassPathResource、URLResource等等。下圖是ResourceLoader的繼承關係:
這裏就差很少分析了Spring IoC容器初始化過程資源的定位,在BeanDefinition定位完成的基礎上,就能夠經過返回的Resource對象來進行BeanDefinition的載入、解析了。
下篇博客將探索Spring IoC容器初始化過程的解析,Spring Resource體系結構會在後面詳細講解。