SPRING原理解析-Ioc容器初始化
第一過程是Resource定位過程。這個Resource定位指的是BeanDefinition的資源定位,它由ResourceLoader通過統一的Resource介面來完成,這個Resource對各種形式的BeanDefinition的使用提供了統一介面。對於這些BeanDefinition的存在形式,相信大家都不會感到陌生。比如說,在檔案系統中的Bean定義資訊可以使用FileSystemResource來進行抽象;在類路徑中可以使用前面提到的ClassPathResource來使用,等等。這個過程類似於容器尋找資料的過程,就像用水桶裝水先要把水找到一樣。
第二個關鍵的部分是BeanDefinition的載入,該載入過程把使用者定義好的Bean表示成IoC容器內部的資料結構,而這個容器內部的資料結構就是BeanDefinition,下面可以看到這個資料結構的詳細定義。總地說來,這個BeanDefinition實際上就是POJO物件在IoC容器中的抽象,這個BeanDefinition定義了一系列的資料來使得IoC容器能夠方便地對POJO物件也就是Spring的Bean進行管理。即BeanDefinition就是Spring的領域物件。下面我們會對這個載入的過程進行詳細的分析,便於大家對整個過程有比較清楚的瞭解。
第三個過程是向IoC容器註冊這些BeanDefinition的過程。這個過程是通過呼叫BeanDefinitionRegistry介面的實現來完成的,這個註冊過程把載入過程中解析得到的BeanDefinition向IoC容器進行註冊。可以看到,在IoC容器內部將BeanDefinition注入到一個HashMap中去,Ioc容器是通過這個HashMap來持有這些BeanDefinition資料的。
BeanDefinition的Resource的定位
在AbstractBeanDefinitionReader.loadBeanDefinitions()方法中,呼叫了defaultResourceLoader的getResource()方法,這裡即為resouce的定位,如下
public Resource getResource(String location) { Assert.notNull(location, "Location must not be null"); if (location.startsWith(CLASSPATH_URL_PREFIX)) { return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader()); } else { try { // Try to parse the location as a URL... URL url = new URL(location); return new UrlResource(url); } catch (MalformedURLException ex) { // No URL -> resolve as resource path. return getResourceByPath(location); } } }
getResourceByPath的實現如下:
protected Resource getResourceByPath(String path) {
return new ClassPathContextResource(path, getClassLoader());
}
beanDefinition的載入
beanDefinition的載入在XmlBeanDefinitionReader中的loadBeanDefinitions()方法實現:
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()); } Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get(); if (currentResources == null) { currentResources = new HashSet<EncodedResource>(4); this.resourcesCurrentlyBeingLoaded.set(currentResources); } if (!currentResources.add(encodedResource)) { throw new BeanDefinitionStoreException( "Detected cyclic loading of " + encodedResource + " - check your import definitions!"); } try { 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(); } } }
注:上面的程式碼中提到,獲得xml檔案,得到IO的InputSOurce準備進行讀取,具體的讀取在doLoadBeanDefinitions()方法中找到,這是從特定的xml檔案中實際載入BeanDefinition的地方;解析的一步在documentLoader中,也就是defaultDocumentLoader按照xml解析得到Document物件,而第二步,defaultBeanDefinitionDocumentreader按照Spring的bean規則把從Document物件解析為BeanDefinition,最後BeanDefinition物件集合到封裝類BeanDefinitionHolder中。
其中,doLoadBeanDefinitions()方法在該類中實現如下
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
int validationMode = getValidationModeForResource(resource);
Document doc = this.documentLoader.loadDocument(
inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware());
return registerBeanDefinitions(doc, resource);
}
catch(){
//異常處理
}
}
其中,registerBeanDefinitions()方法在該類中實現如下(解析第二步):
//按照Spring的Bean語義要求將Bean定義資源解析並轉換為容器內部資料結構
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
//得到BeanDefinitionDocumentReader來對xml格式的BeanDefinition解析
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
//獲得容器中註冊的Bean數量
int countBefore = getRegistry().getBeanDefinitionCount();
//解析過程入口,這裡使用了委派模式,BeanDefinitionDocumentReader只是個介面,//具體的解析實現過程有實現類DefaultBeanDefinitionDocumentReader完成
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
//統計解析的Bean數量
return getRegistry().getBeanDefinitionCount() - countBefore;
}
//建立BeanDefinitionDocumentReader物件,解析Document物件
protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {
return BeanDefinitionDocumentReader.class.cast(BeanUtils.instantiateClass(this.documentReaderClass));
}
DefaultBeanDefinitionDocumentReader 對Bean定義的Document物件解析:
BeanDefinitionDocumentReader介面通過registerBeanDefinitions方法呼叫其實現類DefaultBeanDefinitionDocumentReader對Document物件進行解析,解析的程式碼如下:
//根據Spring DTD對Bean的定義規則解析Bean定義Document物件
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
//獲得XML描述符
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
//獲得Document的根元素
Element root = doc.getDocumentElement();
//具體的解析過程由BeanDefinitionParserDelegate實現,
//BeanDefinitionParserDelegate中定義了Spring Bean定義XML檔案的各種元素
BeanDefinitionParserDelegate delegate = createHelper(readerContext, root);
//在解析Bean定義之前,進行自定義的解析,增強解析過程的可擴充套件性
preProcessXml(root);
//從Document的根元素開始進行Bean定義的Document物件
parseBeanDefinitions(root, delegate);
//在解析Bean定義之後,進行自定義的解析,增加解析過程的可擴充套件性
postProcessXml(root);
}
//建立BeanDefinitionParserDelegate,用於完成真正的解析過程
protected BeanDefinitionParserDelegate createHelper(XmlReaderContext readerContext, Element root) {
BeanDefinitionParserDelegate delegate = new BeanDefinitionParserDelegate(readerContext);
//BeanDefinitionParserDelegate初始化Document根元素
delegate.initDefaults(root);
return delegate;
}
//使用Spring的Bean規則從Document的根元素開始進行Bean定義的Document物件
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
//Bean定義的Document物件使用了Spring預設的XML名稱空間
if (delegate.isDefaultNamespace(root)) {
//獲取Bean定義的Document物件根元素的所有子節點
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
//獲得Document節點是XML元素節點
if (node instanceof Element) {
Element ele = (Element) node;
//Bean定義的Document的元素節點使用的是Spring預設的XML名稱空間
if (delegate.isDefaultNamespace(ele)) {
//使用Spring的Bean規則解析元素節點
parseDefaultElement(ele, delegate);
}
else {
//沒有使用Spring預設的XML名稱空間,則使用使用者自定義的解//析規則解析元素節點
delegate.parseCustomElement(ele);
}
}
}
}
else {
//Document的根節點沒有使用Spring預設的名稱空間,則使用使用者自定義的
//解析規則解析Document根節點
delegate.parseCustomElement(root);
}
}
//使用Spring的Bean規則解析Document元素節點
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
//如果元素節點是<Import>匯入元素,進行匯入解析
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
//如果元素節點是<Alias>別名元素,進行別名解析
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
processAliasRegistration(ele);
}
//元素節點既不是匯入元素,也不是別名元素,即普通的<Bean>元素,
//按照Spring的Bean規則解析元素
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate);
}
}
//解析<Import>匯入元素,從給定的匯入路徑載入Bean定義資源到Spring IoC容器中
protected void importBeanDefinitionResource(Element ele) {
//獲取給定的匯入元素的location屬性
String location = ele.getAttribute(RESOURCE_ATTRIBUTE);
//如果匯入元素的location屬性值為空,則沒有匯入任何資源,直接返回
if (!StringUtils.hasText(location)) {
getReaderContext().error("Resource location must not be empty", ele);
return;
}
//使用系統變數值解析location屬性值
location = SystemPropertyUtils.resolvePlaceholders(location);
Set<Resource> actualResources = new LinkedHashSet<Resource>(4);
//標識給定的匯入元素的location是否是絕對路徑
boolean absoluteLocation = false;
try {
absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();
}
catch (URISyntaxException ex) {
//給定的匯入元素的location不是絕對路徑
}
//給定的匯入元素的location是絕對路徑
if (absoluteLocation) {
try {
//使用資源讀入器載入給定路徑的Bean定義資源
int importCount = getReaderContext().getReader().loadBeanDefinitions(location, actualResources);
if (logger.isDebugEnabled()) {
logger.debug("Imported " + importCount + " bean definitions from URL location [" + location + "]");
}
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error(
"Failed to import bean definitions from URL location [" + location + "]", ele, ex);
}
}
else {
//給定的匯入元素的location是相對路徑
try {
int importCount;
//將給定匯入元素的location封裝為相對路徑資源
Resource relativeResource = getReaderContext().getResource().createRelative(location);
//封裝的相對路徑資源存在
if (relativeResource.exists()) {
//使用資源讀入器載入Bean定義資源
importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource);
actualResources.add(relativeResource);
}
//封裝的相對路徑資源不存在
else {
//獲取Spring IoC容器資源讀入器的基本路徑
String baseLocation = getReaderContext().getResource().getURL().toString();
//根據Spring IoC容器資源讀入器的基本路徑載入給定匯入
//路徑的資源
importCount = getReaderContext().getReader().loadBeanDefinitions(
StringUtils.applyRelativePath(baseLocation, location), actualResources);
}
if (logger.isDebugEnabled()) {
logger.debug("Imported " + importCount + " bean definitions from relative location [" + location + "]");
}
}
catch (IOException ex) {
getReaderContext().error("Failed to resolve current resource location", ele, ex);
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to import bean definitions from relative location [" + location + "]",
ele, ex);
}
}
Resource[] actResArray = actualResources.toArray(new Resource[actualResources.size()]);
//在解析完<Import>元素之後,傳送容器匯入其他資源處理完成事件
getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele));
}
//解析<Alias>別名元素,為Bean向Spring IoC容器註冊別名
protected void processAliasRegistration(Element ele) {
//獲取<Alias>別名元素中name的屬性值
String name = ele.getAttribute(NAME_ATTRIBUTE);
//獲取<Alias>別名元素中alias的屬性值
String alias = ele.getAttribute(ALIAS_ATTRIBUTE);
boolean valid = true;
//<alias>別名元素的name屬性值為空
if (!StringUtils.hasText(name)) {
getReaderContext().error("Name must not be empty", ele);
valid = false;
}
//<alias>別名元素的alias屬性值為空
if (!StringUtils.hasText(alias)) {
getReaderContext().error("Alias must not be empty", ele);
valid = false;
}
if (valid) {
try {
//向容器的資源讀入器註冊別名
getReaderContext().getRegistry().registerAlias(name, alias);
}
catch (Exception ex) {
getReaderContext().error("Failed to register alias '" + alias +
"' for bean with name '" + name + "'", ele, ex);
}
//在解析完<Alias>元素之後,傳送容器別名處理完成事件
getReaderContext().fireAliasRegistered(name, alias, extractSource(ele));
}
}
//解析Bean定義資源Document物件的普通元素
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
// BeanDefinitionHolder是對BeanDefinition的封裝,即Bean定義的封裝類
//對Document物件中<Bean>元素的解析由BeanDefinitionParserDelegate實現 BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
//向Spring IoC容器註冊解析得到的Bean定義,這是Bean定義向IoC容器註冊的入口 BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with name '" +
bdHolder.getBeanName() + "'", ele, ex);
}
//在完成向Spring IoC容器註冊解析得到的Bean定義之後,傳送註冊事件
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
通過上述Spring IoC容器對載入的Bean定義Document解析可以看出,我們使用Spring時,在Spring配置檔案中可以使用<Import>元素來匯入IoC容器所需要的其他資源,Spring IoC容器在解析時會首先將指定匯入的資源載入進容器中。使用<Ailas>別名時,Spring IoC容器首先將別名元素所定義的別名註冊到容器中。
對於既不是<Import>元素,又不是<Alias>元素的元素,即Spring配置檔案中普通的<Bean>元素的解析由BeanDefinitionParserDelegate類的parseBeanDefinitionElement方法來實現,程式碼如下:
//解析<Bean>元素的入口
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {
return parseBeanDefinitionElement(ele, null);
}
//解析Bean定義資原始檔中的<Bean>元素,這個方法中主要處理<Bean>元素的id,name
//和別名屬性
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {
//獲取<Bean>元素中的id屬性值
String id = ele.getAttribute(ID_ATTRIBUTE);
//獲取<Bean>元素中的name屬性值
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
////獲取<Bean>元素中的alias屬性值
List<String> aliases = new ArrayList<String>();
//將<Bean>元素中的所有name屬性值存放到別名中
if (StringUtils.hasLength(nameAttr)) {
String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, BEAN_NAME_DELIMITERS);
aliases.addAll(Arrays.asList(nameArr));
}
String beanName = id;
//如果<Bean>元素中沒有配置id屬性時,將別名中的第一個值賦值給beanName
if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
beanName = aliases.remove(0);
if (logger.isDebugEnabled()) {
logger.debug("No XML 'id' specified - using '" + beanName +
"' as bean name and " + aliases + " as aliases");
}
}
//檢查<Bean>元素所配置的id或者name的唯一性,containingBean標識<Bean>
//元素中是否包含子<Bean>元素
if (containingBean == null) {
//檢查<Bean>元素所配置的id、name或者別名是否重複
checkNameUniqueness(beanName, aliases, ele);
}
//詳細對<Bean>元素中配置的Bean定義進行解析的地方
AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
if (beanDefinition != null) {
if (!StringUtils.hasText(beanName)) {
try {
if (containingBean != null) {
//如果<Bean>元素中沒有配置id、別名或者name,且沒有包含子//<Bean>元素,為解析的Bean生成一個唯一beanName並註冊
beanName = BeanDefinitionReaderUtils.generateBeanName(
beanDefinition, this.readerContext.getRegistry(), true);
}
else {
//如果<Bean>元素中沒有配置id、別名或者name,且包含了子//<Bean>元素,為解析的Bean使用別名向IoC容器註冊
beanName = this.readerContext.generateBeanName(beanDefinition);
//為解析的Bean使用別名註冊時,為了向後相容 //Spring1.2/2.0,給別名新增類名字尾
String beanClassName = beanDefinition.getBeanClassName();
if (beanClassName != null &&
beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
!this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
aliases.add(beanClassName);
}
}
if (logger.isDebugEnabled()) {
logger.debug("Neither XML 'id' nor 'name' specified - " +
"using generated bean name [" + beanName + "]");
}
}
catch (Exception ex) {
error(ex.getMessage(), ele);
return null;
}
}
String[] aliasesArray = StringUtils.toStringArray(aliases);
return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
}
//當解析出錯時,返回null
return null;
}
//詳細對<Bean>元素中配置的Bean定義其他屬性進行解析,由於上面的方法中已經對//Bean的id、name和別名等屬性進行了處理,該方法中主要處理除這三個以外的其他屬性資料
public AbstractBeanDefinition parseBeanDefinitionElement(
Element ele, String beanName, BeanDefinition containingBean) {
//記錄解析的<Bean>
this.parseState.push(new BeanEntry(beanName));
//這裡只讀取<Bean>元素中配置的class名字,然後載入到BeanDefinition中去
//只是記錄配置的class名字,不做例項化,物件的例項化在依賴注入時完成
String className = null;
if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
}
try {
String parent = null;
//如果<Bean>元素中配置了parent屬性,則獲取parent屬性的值
if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
parent = ele.getAttribute(PARENT_ATTRIBUTE);
}
//根據<Bean>元素配置的class名稱和parent屬性值建立BeanDefinition
//為載入Bean定義資訊做準備
AbstractBeanDefinition bd = createBeanDefinition(className, parent);
//對當前的<Bean>元素中配置的一些屬性進行解析和設定,如配置的單態(singleton)屬性等
parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
//為<Bean>元素解析的Bean設定description資訊 bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
//對<Bean>元素的meta(元資訊)屬性解析
parseMetaElements(ele, bd);
//對<Bean>元素的lookup-method屬性解析
parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
//對<Bean>元素的replaced-method屬性解析
parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
//解析<Bean>元素的構造方法設定
parseConstructorArgElements(ele, bd);
//解析<Bean>元素的<property>設定
parsePropertyElements(ele, bd);
//解析<Bean>元素的qualifier屬性
parseQualifierElements(ele, bd);
//為當前解析的Bean設定所需的資源和依賴物件
bd.setResource(this.readerContext.getResource());
bd.setSource(extractSource(ele));
return bd;
}
catch (ClassNotFoundException ex) {
error("Bean class [" + className + "] not found", ele, ex);
}
catch (NoClassDefFoundError err) {
error("Class that bean class [" + className + "] depends on not found", ele, err);
}
catch (Throwable ex) {
error("Unexpected failure during bean definition parsing", ele, ex);
}
finally {
this.parseState.pop();
}
//解析<Bean>元素出錯時,返回null
return null;
}
只要使用過Spring,對Spring配置檔案比較熟悉的人,通過對上述原始碼的分析,就會明白我們在Spring配置檔案中<Bean>元素的中配置的屬性就是通過該方法解析和設定到Bean中去的。
注意:在解析<Bean>元素過程中沒有建立和例項化Bean物件,只是建立了Bean物件的定義類BeanDefinition,將<Bean>元素中的配置資訊設定到BeanDefinition中作為記錄,當依賴注入時才使用這些記錄資訊建立和例項化具體的Bean物件。
上面方法中一些對一些配置如元資訊(meta)、qualifier等的解析,我們在Spring中配置時使用的也不多,我們在使用Spring的<Bean>元素時,配置最多的是<property>屬性,因此我們下面繼續分析原始碼,瞭解Bean的屬性在解析時是如何設定的。
5.BeanDefinitionParserDelegate解析<property>元素:
BeanDefinitionParserDelegate在解析<Bean>呼叫parsePropertyElements方法解析<Bean>元素中的<property>屬性子元素,通過原始碼分析,我們可以瞭解在Spring配置檔案中,<Bean>元素中<property>元素的相關配置是如何處理的:
a. ref被封裝為指向依賴物件一個引用。
b.value配置都會封裝成一個字串型別的物件。
c.ref和value都通過“解析的資料型別屬性值.setSource(extractSource(ele));”方法將屬性值/引用與所引用的屬性關聯起來。
在方法的最後對於<property>元素的子元素通過parsePropertySubElement方法解析,我們繼續分析該方法的原始碼,瞭解其解析過程。
6.解析<property>元素的子元素:
在BeanDefinitionParserDelegate類中的parsePropertySubElement方法對<property>中的子元素解析,通過原始碼分析,我們明白了在Spring配置檔案中,對<property>元素中配置的Array、List、Set、Map、Prop等各種集合子元素的都通過上述方法解析,生成對應的資料物件,比如ManagedList、ManagedArray、ManagedSet等,這些Managed類是Spring物件BeanDefiniton的資料封裝,對集合資料型別的具體解析有各自的解析方法實現,解析方法的命名非常規範,一目瞭然,我們對<list>集合元素的解析方法進行原始碼分析,瞭解其實現過程。
7.解析<list>子元素:
在BeanDefinitionParserDelegate類中的parseListElement方法就是具體實現解析<property>元素中的<list>集合子元素。
經過對Spring Bean定義資原始檔轉換的Document物件中的元素層層解析,Spring IoC現在已經將XML形式定義的Bean定義資原始檔轉換為Spring IoC所識別的資料結構——BeanDefinition,它是Bean定義資原始檔中配置的POJO物件在Spring IoC容器中的對映,我們可以通過AbstractBeanDefinition為入口,榮IoC容器進行索引、查詢和操作。
通過Spring IoC容器對Bean定義資源的解析後,IoC容器大致完成了管理Bean物件的準備工作,即初始化過程,但是最為重要的依賴注入還沒有發生,現在在IoC容器中BeanDefinition儲存的只是一些靜態資訊,接下來需要向容器註冊Bean定義資訊才能全部完成IoC容器的初始化過程。
beanDefinition的解析BeanDefinitionParserDelegate是一個重要的輔助類,它實現了對具體Bean元素在Bean定義資訊的解析。
InputSource(InputStream)獲取resource物件,接著使用DocumentLoader來對XML形式的Bean定義資訊進行讀入,轉換成DOM資料。
BeanDefinition的載入分兩部分,首先通過呼叫xml的解析器得到document,物件,但這些document物件並沒有按照Spring的Bean規則進行解析。在完成
通用的xml解析以後,才是按照Spring的Bean規則進行解析的地方,這個地方就是DomcumentReader中完成BeanDefinition的處理。處理的結果由BeanDefinitionHolder物件持有,這個BeanDefinitionHolder物件還持有BeanDefinition使用相關的資訊,比如Bean名字,別名id,name等屬性元素,這些屬性的解析還是比較簡單的。對於其他元素配置的解析,比如各種Bean的屬性配置,通過一個較複雜的解析過程,這個過程是友parseBeanDefinitionElement來完成的。解析完成後,會放到BeanDefinition物件中並設定到BeanDefinitionHolder中去。
beanDefinition的解析是在BeanDefinitionParserDelegate類的parseBeanDefinitionElement(Elementele, BeanDefinition containingBean)方法中完成。該方法首先呼叫了
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
獲取bean的id和bean的name。接著,呼叫
AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean) ;
對bean元素進行詳細解析,呼叫parseBeanDefinitionAttributes()解析bean的屬性;
parsePropertyElements(ele, bd)解析bean的property。
bean屬性就是注入scope、isLazyInit、autowire、sigleton等屬性,都是按照類似方法來做:
if (ele.hasAttribute(SCOPE_ATTRIBUTE)) {
// Spring 2.x "scope" attribute
beanDefinition.setScope(ele.getAttribute(SCOPE_ATTRIBUTE));
}
呼叫beanDefinition的set方法來實現。
解析propterty方法具體在parsePropertyElements()方法中,主要:
1. bean定義中如果有同名的propterty,那麼只解析第一個property,對於後續的同名propterty不做任何處理。
2. 需要判斷是ref還是value。
BeanDefinition的註冊解析過後的BeanDefinition在IoC容器中的註冊:
讓我們繼續跟蹤程式的執行順序,接下來會到我們第3步中分析DefaultBeanDefinitionDocumentReader對Bean定義轉換的Document物件解析的流程中,在其parseDefaultElement方法中完成對Document物件的解析後得到封裝BeanDefinition的BeanDefinitionHold物件,然後呼叫BeanDefinitionReaderUtils的registerBeanDefinition方法向IoC容器註冊解析的Bean,BeanDefinitionReaderUtils的註冊的原始碼如下:
當呼叫BeanDefinitionReaderUtils向IoC容器註冊解析的BeanDefinition時,真正完成註冊功能的是DefaultListableBeanFactory。
9.DefaultListableBeanFactory向IoC容器註冊解析後的BeanDefinition:
DefaultListableBeanFactory中使用一個HashMap的集合物件存放IoC容器中註冊解析的BeanDefinition,向IoC容器註冊的主要原始碼如下:
//儲存註冊的俄BeanDefinition
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>();
//向IoC容器註冊解析的BeanDefiniton
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {
Assert.hasText(beanName, "Bean name must not be empty");
Assert.notNull(beanDefinition, "BeanDefinition must not be null");
//校驗解析的BeanDefiniton
if (beanDefinition instanceof AbstractBeanDefinition) {
try {
((AbstractBeanDefinition) beanDefinition).validate();
}
catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Validation of bean definition failed", ex);
}
}
//註冊的過程中需要執行緒同步,以保證資料的一致性
synchronized (this.beanDefinitionMap) {
Object oldBeanDefinition = this.beanDefinitionMap.get(beanName);
//檢查是否有同名的BeanDefinition已經在IoC容器中註冊,如果已經註冊,
//並且不允許覆蓋已註冊的Bean,則丟擲註冊失敗異常
if (oldBeanDefinition != null) {
if (!this.allowBeanDefinitionOverriding) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
"': There is already [" + oldBeanDefinition + "] bound.");
}
else {//如果允許覆蓋,則同名的Bean,後註冊的覆蓋先註冊的
if (this.logger.isInfoEnabled()) {
this.logger.info("Overriding bean definition for bean '" + beanName +
"': replacing [" + oldBeanDefinition + "] with [" + beanDefinition + "]");
}
}
}
//IoC容器中沒有已經註冊同名的Bean,按正常註冊流程註冊
else {
this.beanDefinitionNames.add(beanName);
this.frozenBeanDefinitionNames = null;
}
this.beanDefinitionMap.put(beanName, beanDefinition);
//重置所有已經註冊過的BeanDefinition的快取
resetBeanDefinition(beanName);
}
}
最後就通過beanDefinitionMap來操作beanDefinition了。
Ioc容器的例項化與依賴注入
Spring用CGLIB對Bean進行例項化,CGLIB是一個常用的位元組碼生成器的類庫,它提供了一系列的API來提供生產和轉換Java的位元組碼的功能。在Spring Aop中也使用CGLIB對java的位元組碼進行增強。在IOc容器中,要了解怎麼使用CGLIB來生成Bean,需要了解SimpleInstantiationStrategy類,該Strategy是Spring用來生成Bean物件的預設類,它提供了兩種例項化java物件的方法,一個是通過beanUtils,它使用jvm的反射功能,一種是通過前面提到的CGLIB(比如Enhance類)來生成。
依賴注入的發生實在BeanWrapper的setPropertyValues中實現的,具體的完成卻是在BeanWrapper的子類BeanWrapperImpl中實現的。
在Bean的建立和物件依賴注入的過程中,需要依據BeanDefinition中的資訊來遞迴地完成依賴注入。
FileSystemXmlApplicationContext的建構函式如下,啟動了IoC容器。
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
refresh()方法在FileSystemXmlApplicationContext的父類AbstractApplicationContext中實現。在refresh()方法中,最終呼叫了refreshBeanFactory()方法。
完整IoC容器啟動分析(以FileSystemXmlApplicationContext為例)
1. FileSystemXmlApplicationContext構造方法呼叫refresh()方法,這裡是載入beanDefinition的入口
2. refresh()方法在FileSystemXmlApplicationContext的父類
AbstractApplicationContext中的實現,呼叫refreshBeanFactory()方法;
3. refreshBeanFactory()方法在AbstractApplicationContext的子類AbstractRefreshableApplicationContext中實現,呼叫了loadBeanDefinition()方法,啟動對beanDefinition的載入;
4. loadBeanDefinition()在AbstractXmlApplicationContext中實現,呼叫了XmlBeanDefinitionReader的loadBeanDefinitions()方法。
5. XmlBeanDefinitionReader的loadBeanDefinitions()實現了對於承載beanDefnition定義的xml檔案的讀入,以I/O的方式。
6. 讀入後,對beanDefinition進行解析。Bean解析採用SAX工具,先按照XML檔案格式解析,再按照spring bean也有的定義解析,在BeanDefinitionParserDelegate.parseBeanDefinitionElement()實現。
7. 最後對beanDefinition資訊進行註冊。就是將每個beanDefinition以key =beanName,value = beanDefinition放入一個hashMap中,在DefaultListenableFactoty.RegisterBeanDefinition()中實現。
經過IoC容器的初始化後,IoC容器持有beanDefintion,為依賴注入bean即呼叫getBean()方法奠定了基礎。