javascript
Spring IoC 源码系列(一)BeanDefinition 初始化与注册
一、BeanDefinition
1.1 什么是 BeanDefinition
在一般的 Spring 項目中,主要通過 XML 的方式配置 bean,而 BeanDefinition 就是 XML 配置屬性的載體,XML 文件首先會被轉化成 Document 對象,通過解析 Document,把 XML 中 <bean /> 標簽轉化成 BeanDefinition 供 IoC 容器創建 bean 時使用。
我們可以來做個測試。
<bean id="typeMismatch" class="org.springframework.tests.sample.beans.TestBean" scope="prototype"><property name="name"><value>typeMismatch</value></property><property name="age"><value>34x</value></property><property name="spouse"><ref bean="rod"/></property></bean>下面是 debug 后抓到的 BeanDefinition 屬性。
1.2 BeanDefinition 初始化流程圖
二、源碼分析
Debug 測試類入口:org.springframework.beans.factory.xml.XmlBeanDefinitionReaderTests#withOpenInputStream。
下面只是把核心流程拿出來作了分析,一些細節知識點,有興趣的可以自行了解。
測試代碼如下
@Test(expected = BeanDefinitionStoreException.class)public void withOpenInputStream() {/*** 注意這里初始化的是 SimpleBeanDefinitionRegistry 不具備 BeanFactory 功能* 僅僅用來注冊 BeanDefinition,不能用來創建 bean*/SimpleBeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry();Resource resource = new InputStreamResource(getClass().getResourceAsStream("test.xml"));new XmlBeanDefinitionReader(registry).loadBeanDefinitions(resource);}loadBeanDefinitions 源碼如下
@Overridepublic int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {// 使用 EncodedResource 包裝 Resource,EncodedResource 可以指定字符集編碼return loadBeanDefinitions(new EncodedResource(resource));}public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {Assert.notNull(encodedResource, "EncodedResource must not be null");if (logger.isTraceEnabled()) {logger.trace("Loading XML bean definitions from " + encodedResource);}// 獲取已經加載過的資源集合Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();if (currentResources == null) {// 初始化 currentResourcescurrentResources = new HashSet<>(4);// 設置初始化的 currentResourcesthis.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());}// 核心邏輯,加載 bean 資源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();}}}2.1 獲取輸入流
測試類中定義的是 InputStreamResource,下面 InputStreamResource 中 getInputStream() 的實現。
@Overridepublic InputStream getInputStream() throws IOException {InputStream is;if (this.clazz != null) {is = this.clazz.getResourceAsStream(this.path);}else if (this.classLoader != null) {is = this.classLoader.getResourceAsStream(this.path);}else {is = ClassLoader.getSystemResourceAsStream(this.path);}if (is == null) {throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");}return is;}2.2 轉化 Document 對象
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)throws BeanDefinitionStoreException {try {// 獲取 XML 對應 document 實例Document doc = doLoadDocument(inputSource, resource);// 調用 registerBeanDefinitions 方法注冊 BeanDefinitionsint count = registerBeanDefinitions(doc, resource);if (logger.isDebugEnabled()) {logger.debug("Loaded " + count + " bean definitions from " + resource);}return count;}catch (Throwable ex) {throw new BeanDefinitionStoreException(resource.getDescription(),"Unexpected exception parsing XML document from " + resource, ex);}}doLoadBeanDefinitions 方法中調用 doLoadDocument 初始化 Document 對象,內部實現比較簡單,下面一起來看一下。
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,getValidationModeForResource(resource), isNamespaceAware());}上面涉及到幾個關于 XML 的知識點,下面簡單的介紹一下,最后有列出參考文章,有興趣的可以翻翻。
- EntityResolver:XML 文件解析器
- errorHandler:解析出錯處理機制
- getValidationModeForResource(): 獲取 XML 驗證格式,XML 一般支持 DTD 與 XSD,也可以自定義,主要用來約束與驗證 XML 文檔格式
- isNamespaceAware():判斷解析器是否支持解析當前 XML 文件,<beans xmlns=""/> 其中 xmlns 就是命名空間
上面 DocumentBuilderFactory 與 DocumentBuilder 都是 JDK 中提供的類,根據 XML 輸入流獲取 Document 的過程沒有深入跟蹤,這里就不展開分析了。
2.3 處理 Document 節點
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {// 通過反射創建一個 BeanDefinitionDocumentReader 對象BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();// 獲取已經注冊的 BeanDefinition 的數量,-> beanDefinitionMap 的 sizeint countBefore = getRegistry().getBeanDefinitionCount();// 創建 XmlReaderContext,注冊 BeanDefinitionsdocumentReader.registerBeanDefinitions(doc, createReaderContext(resource));// 返回最新注冊的 bean 的數量return getRegistry().getBeanDefinitionCount() - countBefore;}@Overridepublic void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {this.readerContext = readerContext;doRegisterBeanDefinitions(doc.getDocumentElement());}protected void doRegisterBeanDefinitions(Element root) {BeanDefinitionParserDelegate parent = this.delegate;this.delegate = createDelegate(getReaderContext(), root, parent);// 檢查 <beans> 標簽的命名空間是否為空,或者是 http://www.springframework.org/schema/beansif (this.delegate.isDefaultNamespace(root)) {// 獲取 profile 的值,beans 標簽可以設置 profile 屬性用于多環境配置管理String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);if (StringUtils.hasText(profileSpec)) {// 處理 profile 多個值String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);// 判斷是否有默認啟用的 profileif (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {if (logger.isDebugEnabled()) {logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +"] not matching: " + getReaderContext().getResource());}return;}}}// 解析前處理,空實現,可自定義preProcessXml(root);// 解析 document 實例parseBeanDefinitions(root, this.delegate);// 解析后處理,空實現,可自定義postProcessXml(root);this.delegate = parent;}protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {// 命名空間檢查,Spring XML 中不僅可以配置 <beans /> 標簽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 {// samples:<tx:annotation-driven>delegate.parseCustomElement(root);}}private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {// import,samples:<import resource="classpath:/org/springframework/beans/factory/xml/test.xml"/>if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {// 根據 resource 值定位資源,遞歸調用 loadBeanDefinitions 逐個加載importBeanDefinitionResource(ele);}// aliaselse if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {// 處理別名標簽,最終注冊到 aliasMap 中processAliasRegistration(ele);}// beanelse if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {// 轉化成 BeanDefinitionprocessBeanDefinition(ele, delegate);}// beanselse if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {// 遞歸調用doRegisterBeanDefinitions(ele);}}在獲取 Document 對象后,后續流程會遍歷所有子節點,根據子標簽名分別走不同的處理流程,我們主要是來了解怎么初始化與注冊 BeanDefinition 的,其他標簽就不詳細介紹了。
2.3 BeanDefinition 初始化
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {// 處理 <bean /> 標簽,把標簽屬性與子標簽信息封裝在 BeanDefinition 中BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);if (bdHolder != null) {// 裝飾 BeanDefinitionbdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);try {// Register the final decorated instance.// 注冊 BeadDefinitionBeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());}catch (BeanDefinitionStoreException ex) {getReaderContext().error("Failed to register bean definition with name '" +bdHolder.getBeanName() + "'", ele, ex);}// Send registration event.// 發送解析注冊完成響應事件,通知相關的監聽器getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));}}@Nullablepublic BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {return parseBeanDefinitionElement(ele, null);}@Nullablepublic BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {// <bean name="b1,b2,b3" /> id 與 name 的屬性功能類似,name 屬性支持創建多個別名// 獲取 id 屬性String id = ele.getAttribute(ID_ATTRIBUTE);// 獲取 name 屬性String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);// 處理 name 屬性的別名List<String> aliases = new ArrayList<>();if (StringUtils.hasLength(nameAttr)) {String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);aliases.addAll(Arrays.asList(nameArr));}// 優先使用 id 屬性的別名String beanName = id;// 如果 id 屬性為空,且 name 屬性不為空使用 name 屬性中的第一個if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {// 移出別名集合beanName = aliases.remove(0);if (logger.isTraceEnabled()) {logger.trace("No XML 'id' specified - using '" + beanName +"' as bean name and " + aliases + " as aliases");}}// 需要檢查 beanName 的唯一性,避免與其他 bean 重復if (containingBean == null) {checkNameUniqueness(beanName, aliases, ele);}// 獲取 AbstractBeanDefinition 實例,該對象中保存了 <bean /> 標簽中的基本屬性信息AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);if (beanDefinition != null) {// TODO 定義一個 bean 并不一必須定要為其定義 id、name 屬性// 如果沒有定義 id 或者 name 屬性,則想辦法生成 beanNameif (!StringUtils.hasText(beanName)) {try {if (containingBean != null) {/*** 使用 className 屬性生成 beanName* beanName + # + beanDefinition 哈希值的十六進制字符*/beanName = BeanDefinitionReaderUtils.generateBeanName(beanDefinition, this.readerContext.getRegistry(), true);}else {beanName = this.readerContext.generateBeanName(beanDefinition);String beanClassName = beanDefinition.getBeanClassName();if (beanClassName != null &&beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&!this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {aliases.add(beanClassName);}}if (logger.isTraceEnabled()) {logger.trace("Neither XML 'id' nor 'name' specified - " +"using generated bean name [" + beanName + "]");}}catch (Exception ex) {error(ex.getMessage(), ele);return null;}}// 構造 BeanDefinitionHolder 對象并返回String[] aliasesArray = StringUtils.toStringArray(aliases);return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);}return null;}@Nullablepublic AbstractBeanDefinition parseBeanDefinitionElement(Element ele, String beanName, @Nullable BeanDefinition containingBean) {// 將 beanName 存儲到一個 LinkedList 中this.parseState.push(new BeanEntry(beanName));// 記錄 classNameString className = null;// 獲取 class 屬性if (ele.hasAttribute(CLASS_ATTRIBUTE)) {className = ele.getAttribute(CLASS_ATTRIBUTE).trim();}String parent = null;// 判斷是否有 parent 屬性if (ele.hasAttribute(PARENT_ATTRIBUTE)) {parent = ele.getAttribute(PARENT_ATTRIBUTE);}try {// 創建用于承載 XML 屬性配置的 AbstractBeanDefinition 實例AbstractBeanDefinition bd = createBeanDefinition(className, parent);// 解析 <bean /> 標簽的各種屬性,比如 scope、lazy-init、autowire 等parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);// 解析描述信息bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));/*** 以下處理 <bean /> 的子標簽,這里可以優化,循環一次對子標簽分類處理* 下面每處理一個子標簽就需要循環一次 <bean /> 所有的子標簽*/// 處理 <meta/>parseMetaElements(ele, bd);// 解析 lookup-method 屬性 <lookup-method />parseLookupOverrideSubElements(ele, bd.getMethodOverrides());// 解析 replaced-method 屬性 <replaced-method />parseReplacedMethodSubElements(ele, bd.getMethodOverrides());// 解析構造函數參數 <constructor-arg />parseConstructorArgElements(ele, bd);// 解析 property 子元素 <property />parsePropertyElements(ele, bd);// 解析 qualifier 子元素 <qualifier />parseQualifierElements(ele, bd);// 設置 Resource 實例bd.setResource(this.readerContext.getResource());bd.setSource(extractSource(ele));return bd;}catch (Throwable ex) {error("Unexpected failure during bean definition parsing", ele, ex);}finally {this.parseState.pop();}return null;}初始化 BeanDefinition 的過程就是把 <bean /> 標簽及子標簽的屬性保存到 BeanDefinition 中,沒有什么復雜的邏輯。
2.4 注冊 BeanDefinition
public static void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)throws BeanDefinitionStoreException {// Register bean definition under primary name.// 獲取 beanName 并根據 beanName 注冊 BeanDefinitionString beanName = definitionHolder.getBeanName();registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());// Register aliases for bean name, if any.// 從 BeanDefinition 中獲取所有的別名,并根據 beanName 注冊別名String[] aliases = definitionHolder.getAliases();if (aliases != null) {for (String alias : aliases) {// 注冊所有的別名,保存到 aliasMap 中registry.registerAlias(beanName, alias);}}}@Overridepublic void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)throws BeanDefinitionStoreException {Assert.hasText(beanName, "'beanName' must not be empty");Assert.notNull(beanDefinition, "BeanDefinition must not be null");// 保存到 beanDefinitionMap 中this.beanDefinitionMap.put(beanName, beanDefinition);}因為測試類中定義的是 SimpleBeanDefinitionRegistry,因此應該定位到 SimpleBeanDefinitionRegistry 中的 registerBeanDefinition,這里的處理流程很簡單,直接把 BeanDefinition 與 beanName 關聯保存到 beanDefinitionMap 中。
SimpleBeanDefinitionRegistry 并不是一個工廠,不具備初始化 bean 的能力。后面在創建 bean 的流程中還會接觸到 DefaultListableBeanFactory#registerBeanDefinition 注冊流程,稍微比這個復雜些,但是其核心邏輯都是保存到 beanDefinitionMap 。
參考閱讀
XML中DTD,XSD的區別與應用
xsd,dtd,tld有什么區別和聯系?
細說java解析XML文檔的常用方法(含實例)
詳解Spring中的Profile
總結
以上是生活随笔為你收集整理的Spring IoC 源码系列(一)BeanDefinition 初始化与注册的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 康明斯柴油电喷发动机改大泵需不需要改缸盖
- 下一篇: 业务总结003:抽奖活动