Spring Bean生命周期之Bean元信息的配置與解析階段詳解
寫在前面
注:本文章使用的 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)建的示例來引出討論的議題。
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類 這里不再給出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體系
先來看一下BeanDefinitionReader的接口定義以及繼承關(guān)系

通過上面的類圖我們發(fā)現(xiàn)BeanDefinitionReader有三個(gè)實(shí)現(xiàn)類,可以看出針對(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方式
這種方式直接采用BeanDefinitionAPI 來構(gòu)成Bean元信息,并將其注入到IoC容器中,這里主要使用到BeanDefinitionBuilder或GenericBeanDefinition兩種方式來創(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 屬性名 | 使用場景 |
|---|---|
| (class) Bean | 類全稱限定名 |
| (abstract) | 是否為抽象的 BeanDefinition |
| (parent) | 指定 parent BeanDefinition 名稱 |
| (lazy-init) | 是否為延遲初始化 |
| (ref) | 引用其他 Bean 的名稱 |
| (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)讀寫編碼為ISO-8859-1 因此這種直接加載方式會(huì)出現(xiàn)中文亂碼,可通過加載在加載資源時(shí)指定編碼方式來解決
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,這里不再展開
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文檔樹 注冊(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)簽,所以通過是否為默認(rèn)命名空間作為判斷依據(jù)來選擇使用不同的解析方式去解析標(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è)賮硎崂硐轮饕鞒蹋?/strong>
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從名稱上看它似乎與BeanDefinitionReader有千絲萬縷的關(guān)系,實(shí)質(zhì)上二者沒有關(guān)系。AnnotatedBeanDefinitionReader主要是 對(duì)注解Bean進(jìn)行解析的。
先舉例說明下
@Configuration
public class BeanInitializationDemo {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
// 注冊(cè) Configuration Class(配置類)
applicationContext.register(BeanInitializationDemo.class);
// 啟動(dòng) Spring 應(yīng)用上下文
applicationContext.refresh();
applicationContext.close();
}
}
借助上面的例子我們看一下其調(diào)用流程

以上面的例子來看,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)系也存起來
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String alias : aliases) {
registry.registerAlias(beanName, alias);
}
}
}
透過上面XmlBeanDefinitionReader和AnnotationBeanDefinitionReader對(duì)BeanDefinition的解析來看,最終BeanDefinition的注冊(cè)都指向了BeanDefinitionReaderUtils.registerBeanDefinition。我們先來大概看一下代碼實(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)系也存起來
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String alias : aliases) {
registry.registerAlias(beanName, alias);
}
}
}
總結(jié)
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
Java實(shí)現(xiàn)動(dòng)態(tài)創(chuàng)建類操作示例
這篇文章主要介紹了Java實(shí)現(xiàn)動(dòng)態(tài)創(chuàng)建類操作,結(jié)合完整示例形式分析了Java動(dòng)態(tài)創(chuàng)建類的具體步驟與相關(guān)操作技巧,需要的朋友可以參考下2020-02-02
JDK版本管理工具jEnv解決不同jdk版本項(xiàng)目
本文主要介紹了JDK版本管理工具jEnv解決不同jdk版本項(xiàng)目,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07
Spring Boot實(shí)現(xiàn)圖片上傳/加水印一把梭操作實(shí)例代碼
這篇文章主要給大家介紹了關(guān)于Spring Boot實(shí)現(xiàn)圖片上傳/加水印一把梭操作的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-11-11
Spring boot項(xiàng)目集成Camel FTP的方法示例
這篇文章主要介紹了Spring boot項(xiàng)目集成Camel FTP的方法示例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-10-10
Java畢業(yè)設(shè)計(jì)實(shí)戰(zhàn)項(xiàng)目之在線服裝銷售商城系統(tǒng)的實(shí)現(xiàn)流程
基礎(chǔ)掌握怎么樣,用實(shí)戰(zhàn)檢驗(yàn)就知道了,本篇文章手把手帶你用java+SpringBoot+Maven+Vue+mysql實(shí)現(xiàn)一個(gè)在線服裝銷售商城系統(tǒng),大家可以在過程中查缺補(bǔ)漏,提升水平2022-01-01
MultipartResolver實(shí)現(xiàn)文件上傳功能
這篇文章主要為大家詳細(xì)介紹了MultipartResolver實(shí)現(xiàn)文件上傳功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-06-06
sql查詢返回值使用map封裝多個(gè)key和value實(shí)例
這篇文章主要介紹了sql查詢返回值使用map封裝多個(gè)key和value實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07

