Spring Bean生命周期之Bean元信息的配置與解析階段詳解
寫(xiě)在前面
注:本文章使用的 SpringBoot 版本為 2.2.4.RELEASE,其 Spring 版本為 5.2.3.RELEASE
雖然Bean的創(chuàng)建可以采用BeanDefinition
API 也可以直接采用注解方式,但從學(xué)習(xí)角度出發(fā)這里主要以API形式創(chuàng)建Bean。下面以一段Bean創(chuàng)建的示例來(lái)引出討論的議題。
public class XmlBeanMetaDataConfigDemo { public static void main(String[] args) { DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); int beanDefinitions = beanDefinitionReader.loadBeanDefinitions("META-INF/spring.xml"); System.out.println("加載的BeanDefinition個(gè)數(shù)為:" + beanDefinitions); //User就是普通的POJO類(lèi) 這里不再給出User定義 User user = beanFactory.getBean("user", User.class); System.out.println(user); } }
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="user" class="com.wojiushiwo.dto.User"> <property name="name" value="wojiushiwo"/> <property name="age" value="18"/> </bean> </beans>
上面代碼 是使用BeanDefinitionReader
去加載XML文件中的Bean定義
BeanDefinitionReader體系
先來(lái)看一下BeanDefinitionReader
的接口定義以及繼承關(guān)系
通過(guò)上面的類(lèi)圖我們發(fā)現(xiàn)BeanDefinitionReader
有三個(gè)實(shí)現(xiàn)類(lèi),可以看出針對(duì)BeanDefiniiton
的不同載體 均提供了解析手段,有XML形式的、有Properties形式的等等。
BeanDefinitionReader接口定義
public interface BeanDefinitionReader { //返回注冊(cè)了當(dāng)前BeanDefinition的 BeanFactory BeanDefinitionRegistry getRegistry(); @Nullable ResourceLoader getResourceLoader(); @Nullable ClassLoader getBeanClassLoader(); //BeanName 生成器,默認(rèn)是DefaultBeanNameGenerator BeanNameGenerator getBeanNameGenerator(); //從指定資源中加載BeanDefinition,并返回加載到的BeanDefinition的個(gè)數(shù) int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException; int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException; //從指定資源路徑中中加載BeanDefinition,并返回加載到的BeanDefinition的個(gè)數(shù) int loadBeanDefinitions(String location) throws BeanDefinitionStoreException; int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException; }
由于Bean元信息的配置與解析是息息相關(guān)的,下面的一些例子也是將它們?nèi)嘣谝黄鹩懻摰摹?/p>
元信息配置與解析方式
1、API方式
這種方式直接采用BeanDefinition
API 來(lái)構(gòu)成Bean元信息,并將其注入到IoC容器中,這里主要使用到BeanDefinitionBuilder
或GenericBeanDefinition
兩種方式來(lái)創(chuàng)建Bean元信息
關(guān)于BeanDefinition
這個(gè)議題的討論會(huì)放在其他篇章中,這里不再贅述了。
public class ApiBeanMetaDataConfigDemo { public static void main(String[] args) { //創(chuàng)建注解相關(guān)的上下文 AnnotationConfigApplicationContext context=new AnnotationConfigApplicationContext(); //創(chuàng)建GenericBeanDefinition GenericBeanDefinition beanDefinition=new GenericBeanDefinition(); //設(shè)置BeanClass beanDefinition.setBeanClass(User.class); //設(shè)置屬性 MutablePropertyValues propertyValues=new MutablePropertyValues(); propertyValues.addPropertyValue("name","我就是我"); propertyValues.addPropertyValue("age",18); beanDefinition.setPropertyValues(propertyValues); //注冊(cè)BeanDefinition context.registerBeanDefinition("user",beanDefinition); //刷新IoC容器 context.refresh(); //獲取Bean User user = context.getBean("user", User.class); System.out.println(user); //關(guān)閉上下文 context.close(); } }
2、面向XML配置
從XML配置資源處 加載BeanDefinition
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="user" class="com.wojiushiwo.dto.User"> <property name="name" value="wojiushiwo"/> <property name="age" value="18"/> </bean> </beans>
public class XmlBeanMetaDataConfigDemo { public static void main(String[] args) { DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); //加載指定文件的BeanDefinition,并獲取加載地BeanDefinition個(gè)數(shù) int beanDefinitions = beanDefinitionReader.loadBeanDefinitions("META-INF/spring.xml"); System.out.println("加載的BeanDefinition個(gè)數(shù)為:" + beanDefinitions); User user = beanFactory.getBean("user", User.class); System.out.println(user); } }
3、面向Properties配置
這種配置方式不太常用,配置資源時(shí)需要遵守規(guī)則,配置規(guī)則可參考PropertiesBeanDefinitionReader
注釋文檔,其規(guī)則如下
Properties 屬性名 | 使用場(chǎng)景 |
---|---|
(class) Bean | 類(lèi)全稱(chēng)限定名 |
(abstract) | 是否為抽象的 BeanDefinition |
(parent) | 指定 parent BeanDefinition 名稱(chēng) |
(lazy-init) | 是否為延遲初始化 |
(ref) | 引用其他 Bean 的名稱(chēng) |
(scope) | 設(shè)置 Bean 的 scope 屬性 |
${n} | n 表示第 n+1 個(gè)構(gòu)造器參數(shù) |
## 指定BeanClass user.(class)=com.wojiushiwo.dto.User ## 屬性 user.name=我就是我 user.age=19
public class PropertiesBeanMetaDataConfigDemo { public static void main(String[] args) { DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); PropertiesBeanDefinitionReader beanDefinitionReader = new PropertiesBeanDefinitionReader(beanFactory); ClassPathResource classPathResource=new ClassPathResource("META-INF/user.properties"); EncodedResource resource=new EncodedResource(classPathResource,"UTF-8"); int beanDefinitions = beanDefinitionReader.loadBeanDefinitions(resource); System.out.println("加載的BeanDefinition個(gè)數(shù)為:" + beanDefinitions); User user = beanFactory.getBean("user", User.class); System.out.println(user); } }
上面 Properties文件默認(rèn)讀寫(xiě)編碼為ISO-8859-1 因此這種直接加載方式會(huì)出現(xiàn)中文亂碼,可通過(guò)加載在加載資源時(shí)指定編碼方式來(lái)解決
4、面向注解
比如@Autowired
、@Bean
、@Component
、@Configuration
等,這些在當(dāng)下都比較常用不再贅述
XmlBeanDefinitionReader元信息解析 源碼分析
下面就XmlBeanDefinitionReader
調(diào)用鏈中比較重要的地方進(jìn)行分析
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { //解析Xml文件生成Document,這里不再展開(kāi) Document doc = doLoadDocument(inputSource, resource); // 解析Document 注冊(cè)BeanDefinition int count = registerBeanDefinitions(doc, resource); //省略日志打印 return count; } catch (BeanDefinitionStoreException ex) { //... 異常及日志 } }
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { //獲取DefaultBeanDefinitionReader BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); //獲取IoC容器中 已經(jīng)存在的BeanDefinition的個(gè)數(shù) int countBefore = getRegistry().getBeanDefinitionCount(); //這里實(shí)際上執(zhí)行解析Document文檔樹(shù) 注冊(cè)BeanDefinition documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); //返回此次加載的BeanDefinition個(gè)數(shù) return getRegistry().getBeanDefinitionCount() - countBefore; }
protected void doRegisterBeanDefinitions(Element root) { BeanDefinitionParserDelegate parent = this.delegate; this.delegate = createDelegate(getReaderContext(), root, parent); // namespace=http://www.springframework.org/schema/beans 表示為默認(rèn)命名空間 則使用默認(rèn)的解析方式去解析元素,否則將采用NamespaceHandler去解析 if (this.delegate.isDefaultNamespace(root)) { //獲取profile屬性,profile與Spring配置環(huán)境有關(guān)系 String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE); //如果配置了profile if (StringUtils.hasText(profileSpec)) { String[] specifiedProfiles = StringUtils.tokenizeToStringArray( profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS); //如果當(dāng)前Environment環(huán)境與profile不匹配 則流程結(jié)束 if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) { //省略日志 return; } } } //解析xml前置操作 preProcessXml(root); //解析xml parseBeanDefinitions(root, this.delegate); //解析xml后置操作 postProcessXml(root); this.delegate = parent; }
關(guān)于上面源碼分析中profile以及NameSpace的理解 請(qǐng)看這里的XML
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd" profile="dev"> <!-- 一些配置--> </beans>
可以看出 xmlns所指代的就是namespace,而profile也可以配置在beans標(biāo)簽上,其中
http://www.springframework.org/schema/beans
表示默認(rèn)命名空間。因?yàn)镾pring允許自定義標(biāo)簽,所以通過(guò)是否為默認(rèn)命名空間作為判斷依據(jù)來(lái)選擇使用不同的解析方式去解析標(biāo)簽
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { //如果是默認(rèn)命名空間 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 { // 使用NamespaceHandler去解析標(biāo)簽,比如ContextNamespaceHandler去解析<context:xx>的標(biāo)簽等 delegate.parseCustomElement(ele); } } } } else { // 使用NamespaceHandler去解析標(biāo)簽,比如ContextNamespaceHandler去解析<context:xx>的標(biāo)簽等 delegate.parseCustomElement(root); } }
上面的代碼邏輯,根據(jù)是否為默認(rèn)命名空間從而選擇不同的解析方式,自定義標(biāo)簽或非默認(rèn)命名空間指令 需要繼承NamespaceHandler
去實(shí)現(xiàn)自己的標(biāo)簽解析方式
非默認(rèn)命名空間指令舉例
<context:component-scan base-package="com.wojiushiwo"/><aop:xx></aop><context:component-scan base-package="com.wojiushiwo"/> <aop:xx></aop>
這里直接看針對(duì)默認(rèn)命名空間的解析代碼
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指令 else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) { processBeanDefinition(ele, delegate); } else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { // recurse doRegisterBeanDefinitions(ele); } }
針對(duì)bean指令
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); if (bdHolder != null) { bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder); try { //使用BeanRegistry對(duì)BeanDefinition進(jìn)行注冊(cè) BeanDefinitionReaderUtils.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)); } }
我們?cè)賮?lái)梳理下主要流程:
1、解析Xml文件 生成Document
2、針對(duì)Document命名空間進(jìn)行標(biāo)簽解析
- 默認(rèn)命名空間 標(biāo)簽解析
- 非默認(rèn)命名空間或自定義標(biāo)簽 自行實(shí)現(xiàn)
NamespaceHandler
去解析(Spring 內(nèi)置了一些NamespaceHandler解析自定義標(biāo)簽)
3、使用BeanRegistry對(duì)BeanDefinition進(jìn)行注冊(cè)
AnnotatedBeanDefinitionReader元信息解析 源碼分析
AnnotatedBeanDefinitionReader
從名稱(chēng)上看它似乎與BeanDefinitionReader
有千絲萬(wàn)縷的關(guān)系,實(shí)質(zhì)上二者沒(méi)有關(guān)系。AnnotatedBeanDefinitionReader
主要是 對(duì)注解Bean進(jìn)行解析的。
先舉例說(shuō)明下
@Configuration public class BeanInitializationDemo { public static void main(String[] args) { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); // 注冊(cè) Configuration Class(配置類(lèi)) applicationContext.register(BeanInitializationDemo.class); // 啟動(dòng) Spring 應(yīng)用上下文 applicationContext.refresh(); applicationContext.close(); } }
借助上面的例子我們看一下其調(diào)用流程
以上面的例子來(lái)看,AnnotationBeanDefinitionReader
的創(chuàng)建是在AnnotationConfigApplicationContext
構(gòu)造函數(shù)中進(jìn)行的。
public class AnnotationConfigApplicationContext extends GenericApplicationContext implements AnnotationConfigRegistry { private final AnnotatedBeanDefinitionReader reader; private final ClassPathBeanDefinitionScanner scanner; public AnnotationConfigApplicationContext() { //創(chuàng)建AnnotatedBeanDefinitionReader this.reader = new AnnotatedBeanDefinitionReader(this); this.scanner = new ClassPathBeanDefinitionScanner(this); } }
public static void registerBeanDefinition( BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException { // 獲取BeanName String beanName = definitionHolder.getBeanName(); //注冊(cè)bd到IoC容器 registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition()); // 如果bean存在別名,則將beanName與alias的關(guān)系也存起來(lái) String[] aliases = definitionHolder.getAliases(); if (aliases != null) { for (String alias : aliases) { registry.registerAlias(beanName, alias); } } }
透過(guò)上面XmlBeanDefinitionReader
和AnnotationBeanDefinitionReader
對(duì)BeanDefinition的解析來(lái)看,最終BeanDefinition的注冊(cè)都指向了BeanDefinitionReaderUtils.registerBeanDefinition
。我們先來(lái)大概看一下代碼實(shí)現(xiàn)
public static void registerBeanDefinition( BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException { // 獲取BeanName String beanName = definitionHolder.getBeanName(); //注冊(cè)bd到IoC容器 registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition()); // 如果bean存在別名,則將beanName與alias的關(guān)系也存起來(lái) String[] aliases = definitionHolder.getAliases(); if (aliases != null) { for (String alias : aliases) { registry.registerAlias(beanName, alias); } } }
總結(jié)
本篇文章就到這里了,希望能夠給你帶來(lái)幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
Java實(shí)現(xiàn)上傳文件圖片到指定服務(wù)器目錄
本文通過(guò)實(shí)例代碼給大家介紹了java上傳文件圖片到指定服務(wù)器目錄的相關(guān)知識(shí),代碼簡(jiǎn)單易懂,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-06-06Java thrift服務(wù)器和客戶(hù)端創(chuàng)建實(shí)例代碼
Thrift是一個(gè)軟件框架,用來(lái)進(jìn)行可擴(kuò)展且跨語(yǔ)言的服務(wù)的開(kāi)發(fā)。接下來(lái)通過(guò)本文給大家介紹Java thrift服務(wù)器和客戶(hù)端創(chuàng)建實(shí)例代碼,需要的朋友參考下吧2017-04-04Java Swing實(shí)現(xiàn)簡(jiǎn)單的體重指數(shù)(BMI)計(jì)算器功能示例
這篇文章主要介紹了Java Swing實(shí)現(xiàn)簡(jiǎn)單的體重指數(shù)(BMI)計(jì)算器功能,涉及Java Swing窗口組件布局、響應(yīng)及數(shù)值運(yùn)算相關(guān)操作技巧,需要的朋友可以參考下2017-12-12使用jpa的實(shí)體對(duì)象轉(zhuǎn)json符串時(shí)懶加載的問(wèn)題及解決
這篇文章主要介紹了使用jpa的實(shí)體對(duì)象轉(zhuǎn)json符串時(shí)懶加載的問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02Java用itextpdf導(dǎo)出PDF方法(通俗易懂)
因?yàn)轫?xiàng)目需要導(dǎo)出PDF文件,所以去找了一下能夠生成PDF的java工具,這篇文章主要給大家介紹了關(guān)于Java用itextpdf導(dǎo)出PDF的相關(guān)資料,文中介紹的方法通俗易懂,需要的朋友可以參考下2023-07-07java list與數(shù)組之間的轉(zhuǎn)換詳細(xì)解析
以下是對(duì)java中l(wèi)ist與數(shù)組之間的轉(zhuǎn)換進(jìn)行了詳細(xì)的分析介紹,需要的朋友可以過(guò)來(lái)參考下2013-09-09使用@Builder導(dǎo)致無(wú)法創(chuàng)建無(wú)參構(gòu)造方法的解決
這篇文章主要介紹了使用@Builder導(dǎo)致無(wú)法創(chuàng)建無(wú)參構(gòu)造方法的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12Spring?Boot?快速使用?HikariCP?連接池配置詳解
Spring Boot 2.x 將其作為默認(rèn)的連接池組件,項(xiàng)目中添加 spring-boot-starter-jdbc 或 spring-boot-starter-data-jpa 模塊后,HikariCP 依賴(lài)會(huì)被自動(dòng)引入,這篇文章主要介紹了Spring?Boot使用HikariCP連接池配置詳解,需要的朋友可以參考下2023-06-06SpringBoot如何實(shí)現(xiàn)并發(fā)任務(wù)并返回結(jié)果
這篇文章主要介紹了SpringBoot如何實(shí)現(xiàn)并發(fā)任務(wù)并返回結(jié)果問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07