相關背景及資源:html
曹工說Spring Boot源碼(1)-- Bean Definition究竟是什麼,附spring思惟導圖分享java
曹工說Spring Boot源碼(2)-- Bean Definition究竟是什麼,我們對着接口,逐個方法講解node
曹工說Spring Boot源碼(3)-- 手動註冊Bean Definition不比遊戲好玩嗎,咱們來試一下git
曹工說Spring Boot源碼(4)-- 我是怎麼自定義ApplicationContext,從json文件讀取bean definition的?程序員
曹工說Spring Boot源碼(5)-- 怎麼從properties文件讀取beanspring
曹工說Spring Boot源碼(6)-- Spring怎麼從xml文件裏解析bean的sql
工程結構圖:bootstrap
你們看到這個標題,不知道內心有答案了沒?你們再想一想,xml文件裏都有什麼呢?設計模式
這麼一想,spring的xml文件裏,內容真的不少,估計不少元素你也沒配置過,尤爲是這兩年新出來的程序員,估計都在吐槽了,如今不都是註解了嗎,誰還用xml?但其實,不論是xml,仍是註解,都是配置信息,只是不一樣的表現形式而已,看過我前面幾講的同窗,應該知道,咱們用json、properties文件寫過bean的配置信息。
因此,具體形式不重要,xml和註解只是最經常使用的兩種表達方式罷了,咱們此次就以xml爲例來說解。
xml中,其實仍是頗有條理的,各類元素,都按照namespace分得明明白白的,我列了個表格以下:
namespace | element |
---|---|
util | constant、property-path、list、set、map、properties |
context | property-placeholder、property-override、annotation-config、component-scan、load-time-weaver、spring-configured、mbean-export、mbean-server |
beans | import、bean、alias |
task | annotation-driven、scheduler、scheduled-tasks、executor |
cache | advice、annotation-driven |
aop | config、scoped-proxy、aspectj-autoproxy |
你們看到了嗎,spring其實對xml的支持纔是最全面的,註解有的,xml基本都有。做爲一個工做了6年的碼農,我發現好多元素我都沒配置過,更別說熟悉其內在原理了。可是呢,咱們仍是不能忘記了今天的標題,這麼多元素,難道沒有什麼共性嗎?spring解析這些元素,到底都是怎麼實現的呢,且不說這些元素怎麼生效,讀了東西總須要地方存起來吧,那,是怎麼存放的呢?
咱們會挑選一些元素來說解。咱們本講,先講解spring採用的xml解析方式;再從util這個namespace開始,挑了constant這個元素進行深刻講解。
上一講,咱們講了,spring是怎麼解析xml元素的,我今天想辦法從spring源碼裏,把它用來解析xml的主幹代碼提取了一下,基本就是下面這樣的,好比針對以下xml文件,咱們打算遍歷一遍:
test-xml-read.xml: <?xml version="1.0" encoding="UTF-8"?> <f:table xmlns:f="http://www.w3school.com.cn/furniture" xmlns:t="http://www.w3school.com.cn/t"> <f:name>African Coffee Table</f:name> <f:width>80</f:width> <f:length>120</f:length> <t:abc></t:abc> </f:table>
那麼,spring裏的代碼骨架,大概以下:
package org.springframework.bootstrap.sample; import lombok.extern.slf4j.Slf4j; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.*; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import java.io.IOException; import java.io.InputStream; import java.net.URL; @Slf4j public class XmlSimpleUse { public static void main(String[] args) { //讀取xml文件 URL url = Thread.currentThread().getContextClassLoader() .getResource("test-xml-read.xml"); InputStream inputStream = url.openStream(); //將流轉變爲InputSource,在後續xml解析使用 InputSource inputSource = new InputSource(inputStream); DocumentBuilderFactory factory = createDocumentBuilderFactory(); DocumentBuilder docBuilder = factory.newDocumentBuilder(); // 可選,設置實體解析器,其實就是:你能夠自定義去哪裏加載xsd/dtd文件 docBuilder.setEntityResolver(new EntityResolver() { @Override public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException { return null; } }); // 設置回調處理器,當解析出現錯誤時,(好比xsd裏指定了不能出現a元素,而後xml裏出現了a元素) docBuilder.setErrorHandler(null); //解析xml文件,獲取到Document,表明了整個文件 Document document = docBuilder.parse(inputSource); // 獲取根元素 Element root = document.getDocumentElement(); log.info("root is {}",root); //獲取根元素下的每一個child元素 NodeList nodeList = root.getChildNodes(); for (int i = 0; i < nodeList.getLength(); i++) { Node node = nodeList.item(i); if (node instanceof Element) { Element ele = (Element) node; log.info("ele:{}",ele); } } } protected static DocumentBuilderFactory createDocumentBuilderFactory() { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setValidating(true); // Enforce namespace aware for XSD... factory.setNamespaceAware(true); return factory; } }
輸出以下:
21:38:19.638 [main] INFO o.s.bootstrap.sample.XmlSimpleUse - root is [f:table: null] 21:38:19.653 [main] INFO o.s.bootstrap.sample.XmlSimpleUse - ele:[f:name: null] 21:38:19.653 [main] INFO o.s.bootstrap.sample.XmlSimpleUse - ele:[f:width: null] 21:38:19.653 [main] INFO o.s.bootstrap.sample.XmlSimpleUse - ele:[f:length: null] 21:38:19.654 [main] INFO o.s.bootstrap.sample.XmlSimpleUse - ele:[t:abc: null]
你們能夠看上面的demo代碼,沒有依賴任何spring的類,基本還原了spring解析xml時的大致過程,在spring中多出來的細節部分,主要有兩處:
docBuilder.setEntityResolver,這個部分,咱們上面是默認實現。
你們看咱們前面的xml,有必定了解的同窗可能知道,前面定義了兩個namespace,語法通常是下面這樣的:
xmlns:namespace-prefix="namespaceURI"
因此,咱們這邊的兩個namespace,前綴分別是f、t,內容分別是:
http://www.w3school.com.cn/furniture、http://www.w3school.com.cn/t
可是,咱們通常xml文件是有格式要求的,好比spring裏,好比
那,這個約束是在哪裏呢?在namespaceURI
對應的dtd/xsd等文件中。
像上面截圖這樣,就是:
這一句,定義一個命名空間 xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd // 下面這個,你要當成key/value來理解,key就是:http://www.springframework.org/schema/context, //value,就是對應的xsd文件 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"
有了上面的基礎知識,再來講那個接口:
public interface EntityResolver { // 通常傳入的systemId即爲後邊這樣的:http://www.springframework.org/schema/context/spring-context.xsd public abstract InputSource resolveEntity (String publicId, String systemId) throws SAXException, IOException; }
這個接口呢,就是讓咱們自定義一個方法,來解析外部xml實體,通常傳入的參數以下:
即,publicId爲null,systemId爲xsd的uri,這個uri通常是能夠經過網絡獲取的,好比:
http://www.springframework.org/schema/context/spring-context.xsd
可是,spring是自定義了本身的entityResolver
,實現類爲:org.springframework.beans.factory.xml.ResourceEntityResolver
。
這個類,會在本地尋找對應的xsd文件,主要邏輯就是去查找classpath下的META-INF/spring.schemas
,咱們能夠看看spring-beans包內的該文件:
spring爲何要自定義EntityResolver呢,spring爲啥要在本地找呢,緣由是:
若是不自定義,jdk的dom解析類,就會直接使用http://www.springframework.org/schema/context/spring-context.xsd
這個東西,去做爲URL,創建socket網絡鏈接來獲取。而部分環境,好比生產環境,基本是外網隔離的,你這時候是沒辦法去下載這個xsd文件的,豈不是就無法校驗xml文件的語法、格式了嗎?
因此,spring要將這個外部的xsd引用,轉爲在classpath下的查找。
這部分,你們再看下以前的骨架代碼:
Document document = docBuilder.parse(inputSource); Element root = document.getDocumentElement(); log.info("root is {}",root); NodeList nodeList = root.getChildNodes(); for (int i = 0; i < nodeList.getLength(); i++) { Node node = nodeList.item(i); //遍歷每一個元素,咱們這裏只是簡單輸出 if (node instanceof Element) { Element ele = (Element) node; log.info("ele:{}",ele); } }
骨架代碼裏,遍歷每一個元素,在spring裏,遍歷到每一個ele時,要去判斷對應的namespace,若是是默認的,交給xxx處理;若是不是默認的,要根據namespace找到對應的namespacehandler,具體你們能夠看看上一節:
曹工說Spring Boot源碼(6)-- Spring怎麼從xml文件裏解析bean的
咱們在前面說了,本講只先挑一個元素來說解,即
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"> <util:constant id="chin.age" static-field= "java.sql.Connection.TRANSACTION_SERIALIZABLE"/> </beans>
以上,即定義了一個常量bean,這個bean的值,就是static-field
中指定的,這裏是java.sql.Connection#TRANSACTION_SERIALIZABLE
,值爲8。
/** * A constant indicating that * dirty reads, non-repeatable reads and phantom reads are prevented. * This level includes the prohibitions in * <code>TRANSACTION_REPEATABLE_READ</code> and further prohibits the * situation where one transaction reads all rows that satisfy * a <code>WHERE</code> condition, a second transaction inserts a row that * satisfies that <code>WHERE</code> condition, and the first transaction * rereads for the same condition, retrieving the additional * "phantom" row in the second read. */ int TRANSACTION_SERIALIZABLE = 8;
咱們的測試代碼以下:
package org.springframework.utilnamespace; import com.alibaba.fastjson.JSONObject; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.util.MyFastJson; import java.util.List; import java.util.Map; @Slf4j public class TestConstant { public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"classpath:util-namespace-test-constant.xml"},false); context.refresh(); Map<String, Object> map = context.getDefaultListableBeanFactory().getAllSingletonObjectMap(); log.info("singletons:{}", JSONObject.toJSONString(map)); List<BeanDefinition> list = context.getBeanFactory().getBeanDefinitionList(); MyFastJson.printJsonStringForBeanDefinitionList(list); // Object bean = context.getBean("chin.age"); // System.out.println("bean:" + bean); } }
注意,這裏,咱們沒有調用getBean
等方法,咱們只是簡單地,採用json輸出了其bean definition
,輸出以下:
{ "abstract":false, "autowireCandidate":true, "autowireMode":0, // bean的class,好像是個factory,不是個int啊,咱們那個bean,按理說,是int類型的 "beanClass":"org.springframework.beans.factory.config.FieldRetrievingFactoryBean", "beanClassName":"org.springframework.beans.factory.config.FieldRetrievingFactoryBean", "constructorArgumentValues":{ "argumentCount":0, "empty":true, "genericArgumentValues":[], "indexedArgumentValues":{} }, "dependencyCheck":0, "enforceDestroyMethod":true, "enforceInitMethod":true, "lazyInit":false, "lenientConstructorResolution":true, "methodOverrides":{ "empty":true, "overrides":[] }, "nonPublicAccessAllowed":true, "primary":false, "propertyValues":{ "converted":false, "empty":false, // 這個是咱們給這個常量bean設置的值,被放在了property "propertyValueList":[ { "converted":false, "name":"staticField", "optional":false, "value":"java.sql.Connection.TRANSACTION_SERIALIZABLE" } ] }, "prototype":false, "qualifiers":[], "resolvedAutowireMode":0, "role":0, "scope":"", "singleton":true, "synthetic":false }
咱們放開前面註釋的兩行代碼:
Object bean = context.getBean("chin.age"); System.out.println("bean:" + bean);
output:
bean:8
有些同窗估計有點蒙了,不要慌,這裏簡單說下結論,由於
org.springframework.beans.factory.config.FieldRetrievingFactoryBean
。
當咱們去getBean的時候,spring發現其爲factory bean,就會調用這個工廠bean的工廠方法,去生產。
因此,這裏呢,bean是誰?是那個工廠org.springframework.beans.factory.config.FieldRetrievingFactoryBean
,而不是這個工廠的產品:數字8。
從以前的骨架入手,spring裏也有相似的代碼:
DefaultBeanDefinitionDocumentReader#parseBeanDefinitions // 這個方法,裏面能夠看到經過root獲得了children,而後對children遍歷 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 { // 這裏,若是是<util:constant>,由於不是默認命名空間,因此走這裏。 delegate.parseCustomElement(ele); } } } } else { delegate.parseCustomElement(root); } }
進入BeanDefinitionParserDelegate#parseCustomElement(org.w3c.dom.Element)
:
public BeanDefinition parseCustomElement(Element ele) { return parseCustomElement(ele, null); }
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) { String namespaceUri = getNamespaceURI(ele); //這裏,經過namespaceUri,查找對應的handler,咱們這裏,會獲得:org.springframework.beans.factory.xml.UtilNamespaceHandler NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri); if (handler == null) { error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele); return null; } // 調用org.springframework.beans.factory.xml.UtilNamespaceHandler,解析constant元素 return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd)); }
好了,咱們看看這個UtilNamespaceHandler
:
public class UtilNamespaceHandler extends NamespaceHandlerSupport { public void init() { registerBeanDefinitionParser("constant", new ConstantBeanDefinitionParser()); registerBeanDefinitionParser("property-path", new PropertyPathBeanDefinitionParser()); registerBeanDefinitionParser("list", new ListBeanDefinitionParser()); registerBeanDefinitionParser("set", new SetBeanDefinitionParser()); registerBeanDefinitionParser("map", new MapBeanDefinitionParser()); registerBeanDefinitionParser("properties", new PropertiesBeanDefinitionParser()); } private static class ConstantBeanDefinitionParser extends AbstractSimpleBeanDefinitionParser { @Override protected Class getBeanClass(Element element) { return FieldRetrievingFactoryBean.class; } @Override protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) { String id = super.resolveId(element, definition, parserContext); if (!StringUtils.hasText(id)) { id = element.getAttribute("static-field"); } return id; } } ...省略 }
這裏,很明顯,UtilNamespaceHandler
定義了每一個元素,該由什麼類來處理。
咱們仍是接着前面的代碼handler.parse(Element element, ParserContext parserContext)
往下看,是否是這樣吧:
父類 NamespaceHandlerSupport#parse public BeanDefinition parse(Element element, ParserContext parserContext) { //這裏,繼續調用了本類的另外一個方法來獲取Parser return findParserForElement(element, parserContext).parse(element, parserContext); } // 這裏就是上面調用的方法,來獲取Parser private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) { String localName = parserContext.getDelegate().getLocalName(element); // 這裏的parser是一個map,map的元素就是來自於子類的init方法 BeanDefinitionParser parser = this.parsers.get(localName); if (parser == null) { parserContext.getReaderContext().fatal( "Cannot locate BeanDefinitionParser for element [" + localName + "]", element); } return parser; }
這兩個方法是在父類中實現的,總體來講,NamespaceHandlerSupport
中維護了一個map,裏面保存本namespace下,具體的元素及其對應的parser。
/** * Stores the {@link BeanDefinitionParser} implementations keyed by the * local name of the {@link Element Elements} they handle. */ private final Map<String, BeanDefinitionParser> parsers = new HashMap<String, BeanDefinitionParser>();
咱們看看,這個parser是何時存了東西進去的吧?經過find usage,發現以下方法會進行put操做:
NamespaceHandlerSupport#registerBeanDefinitionParser protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) { this.parsers.put(elementName, parser); }
這個方法還比較熟悉,由於在前面出現過了:
public class UtilNamespaceHandler extends NamespaceHandlerSupport { private static final String SCOPE_ATTRIBUTE = "scope"; // 這個init方法,就是在以前經過namespaceUri來獲取對應的handler時,初始化的 @override public void init() { registerBeanDefinitionParser("constant", new ConstantBeanDefinitionParser()); registerBeanDefinitionParser("property-path", new PropertyPathBeanDefinitionParser()); registerBeanDefinitionParser("list", new ListBeanDefinitionParser()); registerBeanDefinitionParser("set", new SetBeanDefinitionParser()); registerBeanDefinitionParser("map", new MapBeanDefinitionParser()); registerBeanDefinitionParser("properties", new PropertiesBeanDefinitionParser()); }
經過上一節的講解,咱們知道了UtilNamespaceHandler下的元素,及其對應的Parser。咱們看看其類圖(圖小,可在單獨tab查看):
咱們先經過其實現的接口,來了解其核心功能:
package org.springframework.beans.factory.xml; import org.w3c.dom.Element; import org.springframework.beans.factory.config.BeanDefinition; /** * 該接口主要被DefaultBeanDefinitionDocumentReader使用,來處理頂級的,非默認命名空間下的的頂級元素(直接在<beans></beans>下) * Interface used by the {@link DefaultBeanDefinitionDocumentReader} to handle custom, * top-level (directly under {@code <beans/>}) tags. * * 實現類能夠自由地經過該元素中的元數據,來轉換爲任意多個BeanDefinition。(好比<context:component-scan></>) * <p>Implementations are free to turn the metadata in the custom tag into as many * {@link BeanDefinition BeanDefinitions} as required. * * Dom解析器,經過元素所在的命名空間,找到對應的NamespaceHandler,再從NamespaceHandler中找到對應的BeanDefinitionParser * <p>The parser locates a {@link BeanDefinitionParser} from the associated * {@link NamespaceHandler} for the namespace in which the custom tag resides. * * @author Rob Harrop * @since 2.0 * @see NamespaceHandler * @see AbstractBeanDefinitionParser */ public interface BeanDefinitionParser { /** * 解析指定的element,註冊其返回的BeanDefinition到BeanDefinitionRegistry * (使用參數ParserContext#getRegistry()獲得BeanDefinitionRegistry) */ BeanDefinition parse(Element element, ParserContext parserContext); }
你們發現接口的意義了嗎,雖然前面的類,很複雜,但咱們經過接口,能夠立刻知道其核心功能。在這裏,就是解析元素,得到BeanDefinition
。
其實,到這裏,基本能夠回答,標題所提出的問題了,Spring解析xml,獲得了什麼?
從這個接口,能夠知道,獲得了BeanDefinition
!
咱們具體看看
AbstractBeanDefinitionParser#parse public final BeanDefinition parse(Element element, ParserContext parserContext) { // 這個方法就是個骨架,用了模板方法設計模式, // 1:調用另外一個方法,獲取BeanDefinition AbstractBeanDefinition definition = parseInternal(element, parserContext); if (definition != null && !parserContext.isNested()) { // 2:得到id String id = resolveId(element, definition, parserContext); // 3:得到別名 String name = element.getAttribute(NAME_ATTRIBUTE); String[] aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name)); // 4:將beanDefinition的id、別名、definition等放進一個holder類 BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases); // 5:經過parserContext,獲得BeanDefinitionRegistry,註冊本bean進去 registerBeanDefinition(holder, parserContext.getRegistry()); if (shouldFireEvents()) { BeanComponentDefinition componentDefinition = new BeanComponentDefinition(holder); postProcessComponentDefinition(componentDefinition); parserContext.registerComponent(componentDefinition); } } return definition; }
咱們上面,第一步的註釋那裏,說用了模板設計模式,由於這個parseInternal是個抽象方法:
protected abstract AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext);
具體的實現,還在子類,鑑於這個類的層次有點深,咱們再看看類圖:
這個parseInternal
就在`AbstractSingleBeanDefinitionParser
:
// 這個方法也足夠簡單,就是構造一個BeanDefinition,用了builder設計模式。 protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) { BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(); String parentName = getParentName(element); // ConstantBeanDefinitionParser覆蓋了這個方法 Class<?> beanClass = getBeanClass(element); if (beanClass != null) { builder.getRawBeanDefinition().setBeanClass(beanClass); } else { String beanClassName = getBeanClassName(element); if (beanClassName != null) { builder.getRawBeanDefinition().setBeanClassName(beanClassName); } } builder.getRawBeanDefinition().setSource(parserContext.extractSource(element)); if (parserContext.isDefaultLazyInit()) { // Default-lazy-init applies to custom bean definitions as well. builder.setLazyInit(true); } // 子類.AbstractSimpleBeanDefinitionParser重寫了這個方法 doParse(element, parserContext, builder); return builder.getBeanDefinition(); }
這裏的getBeanClass
,在ConstantBeanDefinitionParser被重寫了,返回了一個工廠類class:
private static class ConstantBeanDefinitionParser extends AbstractSimpleBeanDefinitionParser { @Override protected Class getBeanClass(Element element) { return FieldRetrievingFactoryBean.class; } ... }
而doParse
,也在ConstantBeanDefinitionParser
的父類中AbstractSimpleBeanDefinitionParser
進行了重寫:
protected void doParse(Element element, ParserContext parserContext, BeanDefinitionBuilder builder) { NamedNodeMap attributes = element.getAttributes(); // 這裏,獲取element下的屬性 for (int x = 0; x < attributes.getLength(); x++) { Attr attribute = (Attr) attributes.item(x); if (isEligibleAttribute(attribute, parserContext)) { String propertyName = extractPropertyName(attribute.getLocalName()); // 經過attribute.getValue()獲取屬性值,propertyName爲屬性名,加入到BeanDefinition builder.addPropertyValue(propertyName, attribute.getValue()); } } postProcess(builder, element); }
回頭看看咱們的xml:
<util:constant id="chin.age" static-field= "java.sql.Connection.TRANSACTION_SERIALIZABLE"/>
再看看咱們debug時,此時的beanDefinition:
這裏,獲取了beanDefinition後,就是進入到本小節開始的地方,去進行beanDefinition的註冊了。
總的來講,
public class FieldRetrievingFactoryBean implements FactoryBean<Object>, BeanNameAware, BeanClassLoaderAware, InitializingBean
這就是一個工廠bean。工廠bean在後續怎麼被使用的,留待下一篇。
本篇源碼在:
https://gitee.com/ckl111/spring-boot-first-version-learn/tree/master/all-demo-in-spring-learning/spring-xml-demo/src/main/java/org/springframework/utilnamespace中的TestConstant.java
xml的解析demo在:
我發現,一個東西,本身看懂可能還行,相對容易點,可是要把這個東西寫出來,倒是一個大工程。。。看似簡單的元素解析,你要把它講清楚,還真的要點篇幅,哈哈,因此,這也是爲何本篇比較長的緣由。
總的來講,再次回答標題,spring到底獲得了什麼,獲得了beanDefinition,本篇裏,只獲得了一個beanDefinition,仍是工廠類型的;後面,咱們會看到其餘多種多樣的元素解析方式。
ok,就到這裏,若是你們以爲有幫助,記得點贊。