日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

Spring IoC 源码系列(一)BeanDefinition 初始化与注册

發(fā)布時間:2024/9/30 javascript 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Spring IoC 源码系列(一)BeanDefinition 初始化与注册 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

一、BeanDefinition

1.1 什么是 BeanDefinition

在一般的 Spring 項目中,主要通過 XML 的方式配置 bean,而 BeanDefinition 就是 XML 配置屬性的載體,XML 文件首先會被轉(zhuǎn)化成 Document 對象,通過解析 Document,把 XML 中 <bean /> 標簽轉(zhuǎn)化成 BeanDefinition 供 IoC 容器創(chuàng)建 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。

下面只是把核心流程拿出來作了分析,一些細節(jié)知識點,有興趣的可以自行了解。

測試代碼如下

@Test(expected = BeanDefinitionStoreException.class)public void withOpenInputStream() {/*** 注意這里初始化的是 SimpleBeanDefinitionRegistry 不具備 BeanFactory 功能* 僅僅用來注冊 BeanDefinition,不能用來創(chuàng)建 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);}// 獲取已經(jīng)加載過的資源集合Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();if (currentResources == null) {// 初始化 currentResourcescurrentResources = new HashSet<>(4);// 設(shè)置初始化的 currentResourcesthis.resourcesCurrentlyBeingLoaded.set(currentResources);}if (!currentResources.add(encodedResource)) {throw new BeanDefinitionStoreException("Detected cyclic loading of " + encodedResource + " - check your import definitions!");}try {// 根據(jù) 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() 的實現(xiàn)。

@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 轉(zhuǎn)化 Document 對象

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)throws BeanDefinitionStoreException {try {// 獲取 XML 對應(yīng) document 實例Document doc = doLoadDocument(inputSource, resource);// 調(diào)用 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 方法中調(diào)用 doLoadDocument 初始化 Document 對象,內(nèi)部實現(xiàn)比較簡單,下面一起來看一下。

protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,getValidationModeForResource(resource), isNamespaceAware());}

上面涉及到幾個關(guān)于 XML 的知識點,下面簡單的介紹一下,最后有列出參考文章,有興趣的可以翻翻。

  • EntityResolver:XML 文件解析器
  • errorHandler:解析出錯處理機制
  • getValidationModeForResource(): 獲取 XML 驗證格式,XML 一般支持 DTD 與 XSD,也可以自定義,主要用來約束與驗證 XML 文檔格式
  • isNamespaceAware():判斷解析器是否支持解析當(dāng)前 XML 文件,<beans xmlns=""/> 其中 xmlns 就是命名空間
@Overridepublic Document loadDocument(InputSource inputSource, EntityResolver entityResolver,ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {// 創(chuàng)建 DocumentBuilderFactoryDocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);if (logger.isTraceEnabled()) {logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");}// 創(chuàng)建一個 DocumentBuilderDocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);// 以 SAX 形式解析 XMLreturn builder.parse(inputSource);}

上面 DocumentBuilderFactory 與 DocumentBuilder 都是 JDK 中提供的類,根據(jù) XML 輸入流獲取 Document 的過程沒有深入跟蹤,這里就不展開分析了。

2.3 處理 Document 節(jié)點

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {// 通過反射創(chuàng)建一個 BeanDefinitionDocumentReader 對象BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();// 獲取已經(jīng)注冊的 BeanDefinition 的數(shù)量,-> beanDefinitionMap 的 sizeint countBefore = getRegistry().getBeanDefinitionCount();// 創(chuàng)建 XmlReaderContext,注冊 BeanDefinitionsdocumentReader.registerBeanDefinitions(doc, createReaderContext(resource));// 返回最新注冊的 bean 的數(shù)量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 標簽可以設(shè)置 profile 屬性用于多環(huán)境配置管理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;}}}// 解析前處理,空實現(xiàn),可自定義preProcessXml(root);// 解析 document 實例parseBeanDefinitions(root, this.delegate);// 解析后處理,空實現(xiàn),可自定義postProcessXml(root);this.delegate = parent;}protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {// 命名空間檢查,Spring XML 中不僅可以配置 <beans /> 標簽if (delegate.isDefaultNamespace(root)) {// 獲取所有的子節(jié)點,遍歷處理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)) {// 根據(jù) resource 值定位資源,遞歸調(diào)用 loadBeanDefinitions 逐個加載importBeanDefinitionResource(ele);}// aliaselse if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {// 處理別名標簽,最終注冊到 aliasMap 中processAliasRegistration(ele);}// beanelse if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {// 轉(zhuǎn)化成 BeanDefinitionprocessBeanDefinition(ele, delegate);}// beanselse if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {// 遞歸調(diào)用doRegisterBeanDefinitions(ele);}}

在獲取 Document 對象后,后續(xù)流程會遍歷所有子節(jié)點,根據(jù)子標簽名分別走不同的處理流程,我們主要是來了解怎么初始化與注冊 BeanDefinition 的,其他標簽就不詳細介紹了。

  • 利用反射創(chuàng)建 BeanDefinitionDocumentReader 對象
  • 獲取已經(jīng)注冊的 BeanDefinition 的數(shù)量,其實就是 beanDefinitionMap 的 size 大小
  • 檢查 <beans /> 標簽命名空間是否為空,或者配置為 http://www.springframework.org/schema/beans,如果條件滿足,獲取 profile 指定的環(huán)境屬性值,判斷指定的環(huán)境是否有處于啟用狀態(tài)的,都不啟用直接返回,不會對 Document 進行解析,關(guān)于 profile 屬性的作用,最后會給一些參考文章
  • 解析前置處理,空實現(xiàn),可自定義
  • 如果是默認命名空間,獲取 下所有子標簽,進行遍歷,判斷子標簽是 import、alias、bean 還是 beans
  • 解析后置處理,空實現(xiàn),可自定義
  • 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.// 發(fā)送解析注冊完成響應(yīng)事件,通知相關(guān)的監(jiān)聽器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 屬性支持創(chuàng)建多個別名// 獲取 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));}// 優(yōu)先使用 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 重復(fù)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;}}// 構(gòu)造 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 {// 創(chuàng)建用于承載 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 /> 的子標簽,這里可以優(yōu)化,循環(huán)一次對子標簽分類處理* 下面每處理一個子標簽就需要循環(huán)一次 <bean /> 所有的子標簽*/// 處理 <meta/>parseMetaElements(ele, bd);// 解析 lookup-method 屬性 <lookup-method />parseLookupOverrideSubElements(ele, bd.getMethodOverrides());// 解析 replaced-method 屬性 <replaced-method />parseReplacedMethodSubElements(ele, bd.getMethodOverrides());// 解析構(gòu)造函數(shù)參數(shù) <constructor-arg />parseConstructorArgElements(ele, bd);// 解析 property 子元素 <property />parsePropertyElements(ele, bd);// 解析 qualifier 子元素 <qualifier />parseQualifierElements(ele, bd);// 設(shè)置 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 中,沒有什么復(fù)雜的邏輯。

    2.4 注冊 BeanDefinition

    public static void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)throws BeanDefinitionStoreException {// Register bean definition under primary name.// 獲取 beanName 并根據(jù) beanName 注冊 BeanDefinitionString beanName = definitionHolder.getBeanName();registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());// Register aliases for bean name, if any.// 從 BeanDefinition 中獲取所有的別名,并根據(jù) 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,因此應(yīng)該定位到 SimpleBeanDefinitionRegistry 中的 registerBeanDefinition,這里的處理流程很簡單,直接把 BeanDefinition 與 beanName 關(guān)聯(lián)保存到 beanDefinitionMap 中。

    SimpleBeanDefinitionRegistry 并不是一個工廠,不具備初始化 bean 的能力。后面在創(chuàng)建 bean 的流程中還會接觸到 DefaultListableBeanFactory#registerBeanDefinition 注冊流程,稍微比這個復(fù)雜些,但是其核心邏輯都是保存到 beanDefinitionMap 。

    參考閱讀

    XML中DTD,XSD的區(qū)別與應(yīng)用
    xsd,dtd,tld有什么區(qū)別和聯(lián)系?
    細說java解析XML文檔的常用方法(含實例)
    詳解Spring中的Profile

    總結(jié)

    以上是生活随笔為你收集整理的Spring IoC 源码系列(一)BeanDefinition 初始化与注册的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。