Spring Bean的註冊
Bean的定義
開始介紹:BeanFactory是最頂層的介面,它定義了IOC容器的基本功能規範,可以看到xml配置中的某些屬性了,比方說單例,多例,型別匹配,別名等。
BeanFactory有三個子類:ListableBeanFactory、HierarchicalBeanFactory和AutowireCapableBeanFactory。
每個介面都有各自的定義,它主要是為了區分在Spring內部在操作過程中物件的傳遞和轉化過程中,對物件的資料訪問所做的限制。
- ListableBeanFactory介面表示這些Bean是可列表的。
- HierarchicalBeanFactory表示的是這些Bean是有繼承關係的,也就是每個Bean有可能有父Bean。
- AutowireCapableBeanFactory介面定義Bean的自動裝配規則。
四個介面共同定義了Bean的集合、Bean之間的關係、以及Bean行為。
從最頂級的BeanFactory開始看起。
- DefaultListableBeanFactory
- XmlBeanDefinitionReader
容器的初始化包括BeanDefinition的Resource定位、載入和註冊這三個基本的過程。
XmlBeanFactory來舉例吧。
private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this); 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)。
配置檔案處理
XmlBeanDefinitionReader載入資料就是在這裡,但是這裡有一個super(parentBeanFactory);一直往上跟會發現跟到了父類AbstractAutowireCapableBeanFactory。
public AbstractAutowireCapableBeanFactory() { super(); ignoreDependencyInterface(BeanNameAware.class); ignoreDependencyInterface(BeanFactoryAware.class); ignoreDependencyInterface(BeanClassLoaderAware.class); }
ignoreDependencyInterface:他會自動忽略給定介面的自動裝配功能。
Spring自動裝配:A依賴B,那麼當Spring獲取A的bean時後如果屬性B未初始化,那麼他就會自動初始化B;
有些情況下不能初始化B,比方說B實現了BeanNameAware介面;
- Spring是這樣描述的:自動裝配時忽略給定的依賴介面,典型的應用是通過其他方式解析ApplicationContext註冊依賴,類似於BeanFactory通過BeanFactoryAware進行注入或者ApplicationContext通過ApplicationContextAware進行注入。
讀取Bean配置檔案
繼續描述this.reader.loadBeanDefinitions(resource)這個是資源載入的入口。
1.XmlBeanDefinitionReader首先對這個Resource物件用EncodedResource進行封裝。
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}
2.拿著Resource物件獲取輸入流,並構造inputSource
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();
}
3.通過構造的InputSource和Resource繼續呼叫函式doLoadBeanDefinitions。繼續往裡面跟進我們可以發現
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();
}
這裡的doLoadBeanDefinitions就是核心處理繼續跟進可以發現。
try {
Document doc = doLoadDocument(inputSource, resource);
return registerBeanDefinitions(doc, resource);
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}...
可以看到註冊Bean之前做了這些事情,
- 獲取xml驗證模式。
- 載入xml並獲取一個Document物件,
- 然後用這兩個物件去註冊Bean。
獲取XML驗證模式:
- xml的驗證模式有DTD(document type definition)和XSD(xml schema definition)。
獲取Document:
- 採用SAX解析XML,如果是dtd型別的檔案,解析器直接在當前路徑,如果是xsd的預設到META_INF/Spring.schemas資料夾裡找XSD檔案載入。
解析註冊BeanDifinitions:
通過解析配置獲取到Bean的定義之後開始註冊Bean。
驗證xml模式先不看了,繼續往下跟進,直接到註冊bean的地方。
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
// 例項化一個Reader
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
// 統計當前BeanDefinition的個數
int countBefore = getRegistry().getBeanDefinitionCount();
// 載入註冊Bean
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
// 返回本次註冊Bean的個數
return getRegistry().getBeanDefinitionCount() - countBefore;
}
可以看到registerBeanDefinitions這裡就是載入註冊Bean的地方,繼續跟進下去。
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
Element root = doc.getDocumentElement();
doRegisterBeanDefinitions(root);
}
doRegisterBeanDefinitions(root)這個就是重點了,繼續跟進去
protected void doRegisterBeanDefinitions(Element root) {
// 預處理
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
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);
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;
}
}
}
// 解析前處理
preProcessXml(root);
// 處理
parseBeanDefinitions(root, this.delegate);
postProcessXml(root);
this.delegate = parent;
}
但是繼續跟進去就發現很奇怪的現象。
protected void preProcessXml(Element root) {
}
protected void postProcessXml(Element root) {
}
這裡用到了模板方法模式。他是為子類所設計的方法,在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);
}
}
到這裡就發現一種是自定義名稱空間的處理,一種是預設的處理。
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
processAliasRegistration(ele);
}
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate);
}
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// recurse
doRegisterBeanDefinitions(ele);
}
}
我們走倒數第二個就是最普通的Bean id這種方式裝配
接下來進去發現最後容器是一個併發容器ConcurrentHashMap,這樣就把Bean放到了Spring容器中了,這就完成了Spring的Bean的註冊。