Spring源碼閱讀——Bean的載入和獲取過程
我們經常使用Spring,並且也都了解其大概原理。我想我們一定會對Spring源碼的解讀有迫切的渴望。
我也如此。所以,我打算閱讀一下Spring的源碼。再此之前,我也為此準備了很多。包括,去複習熟練java反射,理解常用的設計模式。當然,這些複習筆記也會在今後的複習中順便記錄在我的csdn博客。(當然,可能寫的不好,也可能理解不正確(可以一起交流嘛)。但是樂於分享總歸是好的。)
首先看下spring的各個組件。
可以看到,在Core Container(核心容器)中包含有Core、Beans、Context和Spring Expression Language.Core和Beans模塊是Spring框架的基礎部分,提供IoC控制反轉和依賴注入的特性。
Core模塊主要包含著Spring框架基本的核心工具類,供其它組件使用。
Beans模塊是所有應用都要用到的,它包含訪問配置文件、創建和管理bean以及ioc、依賴注入。
Context模塊構建於Core和Beans之上。是spring的上下文環境,為Spring核心提供了大量的擴展,天津挨了對國際化、事件傳播、資源載入等支持。ApplicationContext介面是Context模塊的關鍵。
Spring Expression Language為Spring提供了一個強大的表達式語言用於在運行時查詢草操縱對象 。
現在我們已經了解了Spring的基礎組件,我們現在就在代碼中跟蹤一下Spring Bean的創建和獲取過程。
beans.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.xsd">
<bean id="hello" class="bean.HelloSpring" lazy-init="false"></bean>
</beans>
HelloSpring.java
package bean;
/**
* Created by yuyufeng on 2016/11/17.
*/
public class HelloSpring {
private String name;
public HelloSpring() {
System.out.println("##HelloSpring.HelloSpring初始化……………………………………");
}
public HelloSpring(String name) {
this.name = name;
}
public void sayHello(String something){
System.out.println("hello"+something);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "HelloSpring{" +
"name="" + name + """ +
"}";
}
}
BeanFactoryTest.java
package spring.ioc;
import bean.HelloSpring;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
/**
* Created by yuyufeng on 2016/11/18.
* Spring中Bean的載入過程
*/
public class BeanFactoryTest {
public static void main(String[] args) {
//spring如何初始化有兩種方式 beanFactory applicationContext
Resource resource = new ClassPathResource("spring/ioc/beans.xml");
BeanFactory beanFactory = new XmlBeanFactory(resource);
HelloSpring helloSpring = (HelloSpring) beanFactory.getBean("hello");
helloSpring.sayHello("張三");
}
}
先從表面上可以看到 bean的載入可大致可以分為:從xml讀取bean的信息載入到Spring容器中,通過xml配置的id從Spring容器反射得到這個類的實例對象。
現在,我們進行詳細分析
1.Resource resource = new ClassPathResource("spring/ioc/beans.xml");
我們通過Sring Core模塊的工具從本地獲得了xml資源,並生成Resource對象。這一過程就不詳細跟進了。
2.通過XmlBeanFactory來創建BeanFactory對象。
直接debug進入
3.首先我們會跟進 DefaultSingletonBeanRegistry 其中有靜態對象需要實例化。至於為什麼會跟進這個類,我們來看下類的繼承關係就知道了(為什麼會先實例其中的靜態類,可以複習以下java對象的實例順序)
4.接著會進入DefaultListableBeanFactory創建裡面的靜態對象實例以及執行裡面的靜態模塊
5.通過類載入器注入DefaultListableBeanFactory對象
然後又實例化了一個存放DefaultListableBeanFactory的map
6.接著再執行XmlBeanFactory的構造方法,其中把配置文件Resource賦值給了resource
7.執行this.reader.loadBeanDefinitions(resource); //可以看到這個步驟是要把resource載入到容器中去了。這裡是整個資源載入進入的切入點。
8.接著再跟進,直到進入public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException ;方法
9.對導入資源再進行一定包裝處理後進入doLoadBeanDefinitions(inputSource, encodedResource.getResource()); //對於encode我們是比較熟悉的 肯定是處理編碼相關的
10.現在已經進入到了XmlBeanDefinitionReader.java,
再包裝處理(畢竟xml文件規則什麼的驗證啊 獲取比較麻煩,不知道你暈了沒有)
xml還是包裝成了Document委託給DocumentLoader去處理執行
11.現在又進入public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException ;
程序結果以上的處理,已經獲取了xml文檔的document,已經可以準備提取註冊bean了。
12.經過documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
最終我們獲取到了root,protected void doRegisterBeanDefinitions(Element root)這個方法,開始真正的解析已經處理過的資源。
13.解析完成後就是註冊了,
debug到如下代碼
14.可以看到在這裡,把bean存到了beanDefinitionMap中,
對於beanDefinitionMap是什麼,就是存在內存中的map,bean就存在裡面供外部獲取。
跟蹤了這麼多的源代碼,肯定有點亂。做下總結吧。
Spring中bean的載入過程
1.獲取配置文件資源
2.對獲取的xml資源進行一定的處理檢驗
3.處理包裝資源
4.解析處理包裝過後的資源
5.載入提取bean並註冊(添加到beanDefinitionMap中)
至於bean的獲取,那就比上面的簡單多了。
斷點進入AbstractBeanFactory
入口
final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
進入之後
protected RootBeanDefinition getMergedLocalBeanDefinition(String beanName) throws BeansException {
// Quick check on the concurrent map first, with minimal locking.
RootBeanDefinition mbd = this.mergedBeanDefinitions.get(beanName);
if (mbd != null) {
return mbd;
}
return getMergedBeanDefinition(beanName, getBeanDefinition(beanName));
}
再進入
return getMergedBeanDefinition(beanName, getBeanDefinition(beanName));
我們發現又進入了
DefaultListableBeanFactory.java,是不是有種熟悉的感覺。
當你看到這條語句,你就豁然開朗了,
BeanDefinition bd = this.beanDefinitionMap.get(beanName);
就是之前載入bean放入到的map嗎?
其實整個過程還是比較容易理解的,就是裡面的包裝解析很複雜