Spring原始碼之bean的基本解析
先看這樣一段兒程式碼:
spring bean xml配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd" >
<bean id="myTestBean" class="org.springframework.myTests.MyTestBean">
</bean>
</beans>
定義一個測試bean:
public class MyTestBean {
private String myBean = "hello bean";
public String getMyBean() {
return myBean;
}
public MyTestBean setMyBean(String myBean) {
this .myBean = myBean;
return this;
}
}
獲得這個bean,並執行
public class ContextBeanTest {
/**
* 用XmlBeanFactory這個方式獲得bean,現在已經不用這個方式了
*/
@Test
public void test() {
XmlBeanFactory xmlBeanFactory = new XmlBeanFactory(new ClassPathResource("spring-base.xml"));
MyTestBean myTestBean = (MyTestBean) xmlBeanFactory.getBean("myTestBean" );
System.out.println(myTestBean.getMyBean());
}
}
列印結果很明顯.就是:hello bean.
DefaultListableBeanFactory
類似於上面直接使用XmlBeanFactory的方式其實已經過時了,目前大家都用ApplicationContext,那麼ApplicationContext和XmlBeanFactory有什麼關係?簡單來說,可以理解為ApplicationContext是在XmlBeanFactory基礎上的擴充套件,前者擁有後者的全部功能,而且在其基礎上做了封裝.在ApplicationContext初始化的過程中,它是獲得了BeanFactory的示例,並享受其功能的,在那裡,XmlBeanFactory會獲得新生~
研究spring,最好的辦法就是clone原始碼,自己跟著示例來看一下.到spring官網,clone下程式碼,eclipse和IDEA都有相應的.md格式的安裝指南或執行指令碼,除了耗時一點外,沒別的難處,我把我的spring原始碼放到了我的github上,會一直更新原始碼的中文註釋,感興趣的可以去看看spring原始碼中文註釋
這裡的程式碼在spring.beans的jar包,或者spring-beans這個工程內.
可以看一下XmlBeanFactory這個類:
public class XmlBeanFactory extends DefaultListableBeanFactory
繼承自DefaultListableBeanFactory
@SuppressWarnings("serial")
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
這個類是是整個bean載入的核心部分,是spring註冊及載入的預設實現,他的子類,XmlBeanFactory和他不同的地方在於,XmlBeanFactory使用了自定義的XML讀取器XmlBeanDefinitionReader,當前類繼承了AbstractAutowireCapableBeanFactory,並且實現了ConfigurableListableBeanFactory等.
下面給出一個相關的UML類圖:
這個圖基本從XmlBeanFactory繼承了DefaultListableBeanFactory開始,把這塊兒的脈絡畫了出來,過程很痛苦,結果很好,下面介紹圖中各個類的作用:
- AliasRegistry 定義對alias的操作
- SimpleAliasRegistry 實現了AliasRegistry ,map來儲存
- SingletonBeanRegistry 單例的註冊和獲取
- BeanFactory 定義和獲取bean,核心介面
- DefaultSingletonBeanRegistry SingletonBeanRegistry的預設實現
- BeanDefinitionRegistry BeanDefinition的增刪蓋茶
- FactoryBeanRegistrySupport 在DefaultSingletonBeanRegistry的基礎上,擴充套件了一些功能,比如對FactoryBean的處理
- ConfigurableBeanFactory 提供配置factory的方法
- ListableBeanFactory 根據各種條件獲取bean的清單
- AbstractBeanFactory 由圖中可知,綜合了FactoryBeanRegistrySupport和ConfigurableBeanFactory
- ConfigurableListableBeanFactory beanFactory配置清單,指定忽略型別和介面
XmlBeanDefinitionReader
xmlBeanFactory對DefaultListableBeanFactory進行了擴充套件,作用主要是從xml中讀取bean的定義.和父類主要的不同,在於XmlBeanDefinitionReader.
private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
xmlBeanFactory的屬性reader用的是XmlBeanDefinitionReader.下面看這個類,首先,UML圖:
相關類的介紹:
- AbstractBeanDefinitionReader 實現EnvironmentCapable和BeanDefinitionReader
- EnvironmentCapable 定義獲取相關環境
- BeanDefinitionReader 定義資原始檔,讀取或轉換BeanDefinition
XmlBeanFactory的核心實現
說這個之前,對上述兩個類:DefaultListableBeanFactory和XmlBeanDefinitionReader做了一個概括的分析,下面,從細節處來看XmlBeanFactory.
在我們的測試類:javaXmlBeanFactory xmlBeanFactory = new XmlBeanFactory(new ClassPathResource("spring-base.xml"));
程式碼中XmlBeanFactory(new ClassPathResource(“spring-base.xml”)),構造方法的呼叫看下面:
public XmlBeanFactory(Resource resource) throws BeansException {
this(resource, null);
}
//最終呼叫這個
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);
this.reader.loadBeanDefinitions(resource);
}
this.reader.loadBeanDefinitions(resource);這個才是資源載入的真正實現!點進去:
/**
* 進來第一步,將resource封裝成EncodedResource型別
*/
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}
繼續點進去:
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (logger.isInfoEnabled()) {
logger.info("Loading XML bean definitions from " + encodedResource.getResource());
}
//resourcesCurrentlyBeingLoaded的初始化是new NamedThreadLocal<>("XML bean definition resources currently being loaded");
//通過ThreadLocal來儲存屬性的值
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (currentResources == null) {
currentResources = new HashSet<>(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
try {
//獲取Resource物件,並獲取流
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
//真正的核心程式碼
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
finally {
inputStream.close();
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"IOException parsing XML document from " + encodedResource.getResource(), ex);
}
finally {
currentResources.remove(encodedResource);
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
}
進去核心方法”doLoadBeanDefinitions”:
/**
* 排除一堆catch,其實程式碼沒有幾句
*/
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
//方法一,驗證xml的驗證模式,載入xml獲取對相應的Document
Document doc = doLoadDocument(inputSource, resource);
////根據返回的DOC,註冊bean
return registerBeanDefinitions(doc, resource);
}
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
//驗證xml的驗證模式,載入xml獲取對相應的Document
return this.documentLoader.loadDocument(inputSource, getEntityResolver(),
this.errorHandler,getValidationModeForResource(resource), isNamespaceAware());
}
上面主要做了:
- 獲取xml的驗證模式
- 獲得xml物件的Document
- 根據返回的doc物件,註冊bean
這些基本就是spring容器基礎部分的實現,當然,第三部應該是最核心的部分,前兩部只是他的基礎工作,方法傳進去了一個doc,一個資源,doLoadDocument又獲得了驗證模式,那麼,接下來的工作,就是載入bean了,這應該是一個很複雜的過程,至於文件的驗證模式,主要有DTD,XSD兩種,有興趣可以研究一下
下面進registerBeanDefinitions(doc, resource);
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
//例項化BeanDefinitionDocumentReader,用了DefaultBeanDefinitionDocumentReader
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
//當前bean定義的個數
int countBefore = getRegistry().getBeanDefinitionCount();
//載入及註冊bean
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
//僅返回本次的載入bean個數
return getRegistry().getBeanDefinitionCount() - countBefore;
}
註釋中說了例項化BeanDefinitionDocumentReader,用了DefaultBeanDefinitionDocumentReader,那下面進入他的egisterBeanDefinitions(doc, createReaderContext(resource))方法:
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
//獲取檔案的root
Element root = doc.getDocumentElement();
//將root節點,傳入下面方法
doRegisterBeanDefinitions(root);
}
protected void doRegisterBeanDefinitions(Element root) {
// Any nested <beans> elements will cause recursion in this method. In
// order to propagate and preserve <beans> default-* attributes correctly,
// keep track of the current (parent) delegate, which may be null. Create
// the new (child) delegate with a reference to the parent for fallback purposes,
// then ultimately reset this.delegate back to its original (parent) reference.
// this behavior emulates a stack of delegates without actually necessitating one.
//delegate屬性賦值給一變數parent,初始化parent為new BeanDefinitionParserDelegate(readerContext);
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
//判斷節點的名稱空間是否是http://www.springframework.org/schema/beans,如果是,進入
if (this.delegate.isDefaultNamespace(root)) {
//準備處理profile屬性,這個屬性不常用
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
if (logger.isInfoEnabled()) {
logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +
"] not matching: " + getReaderContext().getResource());
}
return;
}
}
}
//protected的空方法,叫給子類定製一些自己的實現
preProcessXml(root);
//繼續點進去
parseBeanDefinitions(root, this.delegate);
//protected的空方法,叫給子類定製一些自己的實現
postProcessXml(root);
this.delegate = parent;
}
這個類裡面,很多方法都是protected,從這可以看出spring這塊的面向繼承的思想.其中,<beans>
標籤的profile屬性,我們並不常用,當然,可以用這個屬性,配合web.xml檔案,來設定dev,test,prod各種不同環境的bean配置.下面進入程式碼:parseBeanDefinitions(root, this.delegate);
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root)) {
//獲得beans節點的子節點
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
//如果子節點的element
if (node instanceof Element) {
Element ele = (Element) node;
if (delegate.isDefaultNamespace(ele)) {
//bean的解析
parseDefaultElement(ele, delegate);
}
else {
//bean的解析
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
如果是預設的名稱空間,則呼叫上面的bean解析,如果不是,呼叫下面的bean解析,預設的話,spring會正常的去解析,如果不是預設的,那麼應該是一個稍顯複雜的過程,需要重寫一些類,或者實現一些介面,從這開始,其實就進入了spring對預設的標籤的一些解析,非預設的暫時不想去看,下一篇,會寫到spring的預設標籤解析,然後就是bean的載入,儲存,獲取等,最後就是ApplicationContext的一些實現,這個主線才算略顯完整一些.