1 緣起
作為一個應(yīng)用開發(fā)人員而言,會使用某一個工具分為兩個層次(個人觀點):
第一個層次,知道工具,會使用這個工具解決問題;
第二個層次,理解工具的實現(xiàn)原理。
關(guān)于Spring的學(xué)習(xí),還在第一個層次轉(zhuǎn)悠,缺少原理的研究,
隨著學(xué)習(xí)的深入,開始研究些Spring源碼,配合IDEA調(diào)試,
逐漸理解一些Spring原理,先從創(chuàng)建Bean開始,
分享如下。
2 新建Bean
對于Spring學(xué)習(xí)、使用和研究人員而言,
Bean必修課,Bean從何而來,又如何獲取,
弄清楚這些,會加深對Spring的理解。
首先從創(chuàng)建Bean開始,常見的創(chuàng)建方式有3種:
- 注解@Bean方式
- XML方式
- BeanDefinitionBuilder方式
通過XML實現(xiàn)的Bean注入有多種方式:構(gòu)造方法注入、set方法注入、靜態(tài)工廠注入和實例工廠注入
Spring提供了多種方式創(chuàng)建Bean,這里的創(chuàng)建Bean是創(chuàng)建自定義的Bean,
不涉及Spring啟動時需要創(chuàng)建的系統(tǒng)Bean,
但是殊途同歸,最終都是通過BeanDefinition構(gòu)建Bean,
創(chuàng)建自定義的Bean會經(jīng)歷兩個核心步驟:
- 注冊BeanDefinition
填充beanDefinitionMap和beanDefiinitionNames,為填充singletonObjects準(zhǔn)備 - 創(chuàng)建單例Bean
填充singletonObjects,供后續(xù)獲取Bean使用
2.1 @Bean方式
通過@Bean方式創(chuàng)建自定義Bean是最明顯的方式,
直接在對應(yīng)的方法上添加@Bean注解,表明這是Bean,
結(jié)合@Configuration,Spring會自動創(chuàng)建Bean,
測試樣例及注釋如下:
package com.monkey.springboottemplate.modules.bean_definition;
import com.monkey.springboottemplate.common.entity.UserEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 注解新建Bean.
*
* @author xindaqi
* @since 2022-12-14 16:01
*/
@Configuration
public class BeanDefinitionByAnnotation {
private static final Logger logger = LoggerFactory.getLogger(BeanDefinitionByAnnotation.class);
@Bean
public UserEntity myUserBean() {
return new UserEntity("xiaohua", "歐洲");
}
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
// 注冊BeanDefinition
applicationContext.register(BeanDefinitionByAnnotation.class);
// 加載或刷新配置(Java基礎(chǔ)配置/XML/properties等)的持久化描述,
// 這里關(guān)注填充singletonObjects
applicationContext.refresh();
logger.info(">>>>>>>>>>應(yīng)用程序上下文啟動");
UserEntity user = applicationContext.getBean(UserEntity.class);
logger.info(">>>>>>>>Bean:{}", user);
applicationContext.close();
logger.info(">>>>>>>>關(guān)閉應(yīng)用程序上下文");
}
}
2.2 XML方式
通過XML創(chuàng)建Bean,首先要構(gòu)建對應(yīng)的XML文件,
2.2.1 set方法注入
配置樣例及注釋如下:
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 在Spring framework中啟用@Resource注解 -->
<context:annotation-config />
<!-- 構(gòu)建User Bean實例:Bean id為user1 -->
<bean id="userA" class="com.monkey.springboottemplate.common.entity.UserEntity">
<property name="nickname" value="xiaoxml" />
<property name="address" value="歐洲" />
</bean>
</beans>
2.2.2 構(gòu)造器注入
配置樣例及注釋如下:
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 在Spring framework中啟用@Resource注解 -->
<context:annotation-config />
<!-- 構(gòu)建User Bean實例:Bean id為user1 -->
<bean id="userA" class="com.monkey.springboottemplate.common.entity.UserEntity">
<constructor-arg name="nickname" value="xiaoxml" />
<constructor-arg name="address" value="歐洲" />
</bean>
</beans>
2.2.3 靜態(tài)工廠注入
package com.monkey.springboottemplate.common.entity;
/**
* User工廠.
*
* @author xindaqi
* @since 2023-04-08 15:26
*/
public class UserFactory {
public static UserEntity getUserA() {
return new UserEntity("A", "China");
}
}
配置樣例及注釋如下:
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 在Spring framework中啟用@Resource注解 -->
<context:annotation-config />
<!-- 構(gòu)建User Bean實例:Bean id為user1 -->
<bean id="userA" class="com.monkey.springboottemplate.common.entity.UserFactory" factroy-method="getUserA"/>
</beans>
2.2.4 實例工廠注入
package com.monkey.springboottemplate.common.entity;
/**
* User工廠.
*
* @author xindaqi
* @since 2023-04-08 15:26
*/
public class UserFactory {
public String getName() {
return "A";
}
public String getAddress() {
return "China";
}
}
配置樣例及注釋如下:
<?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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 在Spring framework中啟用@Resource注解 -->
<context:annotation-config />
<!-- 構(gòu)建UserFactory實例:供后續(xù)調(diào)用 -->
<bean id="userFactroy" class="com.monkey.springboottemplate.common.entity.UserFactory"/>
<bean id="name" factory-bean="userFactory" factory-method="getName"/>
<bean id="address" factory-bean="userFactory" factory-method="getAddress"/>
</beans>
XML文件只是存儲Bean的持久化配置文件,
想要使該Bean加載到Spring容器,仍需要通過相關(guān)類加載該XML文件,
測試樣例以及注釋如下:
package com.monkey.springboottemplate.modules.bean_definition;
import com.monkey.springboottemplate.common.entity.UserEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* XML新建Bean.
*
* @author xindaqi
* @since 2022-12-14 15:59
*/
public class BeanDefinitionByXml {
private static final Logger logger = LoggerFactory.getLogger(BeanDefinitionByXml.class);
public static void main(String[] args) {
String beanConfig = "bean-creation.xml";
// 通過XML配置文件構(gòu)建Bean
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(beanConfig);
UserEntity user = applicationContext.getBean("userA", UserEntity.class);
logger.info(">>>>>>>>Creation Bean using XML, User Bean :{}", user);
}
}
2.3 BeanDefinition
上面兩種創(chuàng)建Bean的方式對于應(yīng)用開發(fā)者而言是顯式的,
而,更貼近Spring底層的方式是通過BeanDefinition創(chuàng)建Bean,
Spring創(chuàng)建Bean的第一個過程即注冊BeanDefinition,就是下面的樣例,
通過顯式的方式注冊BeanDefinition,
然后再創(chuàng)建單例的Bean,測試樣例及注釋如下:
package com.monkey.springboottemplate.modules.bean_definition;
import com.monkey.springboottemplate.common.entity.UserEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* BeanDefinition新建Bean.
*
* @author xindaqi
* @since 2022-12-14 16:01
*/
public class BeanDefinitionByBuilder {
private static final Logger logger = LoggerFactory.getLogger(BeanDefinitionByBuilder.class);
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
// 構(gòu)建BeanDefinition,填充Bean屬性
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(UserEntity.class);
beanDefinitionBuilder.addPropertyValue("nickname", "xiaoxiao");
AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
// 注冊BeanDefinition
applicationContext.registerBeanDefinition("myBean", beanDefinition);
// 加載或刷新配置(Java基礎(chǔ)配置/XML/properties等)的持久化描述,
// 這里關(guān)注填充singletonObjects
applicationContext.refresh();
logger.info(">>>>>>>>>>應(yīng)用程序上下文啟動");
UserEntity user1 = applicationContext.getBean(UserEntity.class);
UserEntity user2 = (UserEntity) applicationContext.getBean("myBean");
logger.info(">>>>>>>>>>User:{}, user2:{}", user1, user2);
applicationContext.close();
logger.info(">>>>>>>>關(guān)閉應(yīng)用程序上下文");
}
}
3 源碼分析
3.1 創(chuàng)建Bean流程
Spring中創(chuàng)建自定義Bean的核心流程如下圖所示,
由圖可知,創(chuàng)建自定義Bean分成兩個部分:
- 注冊BeanDefition
- 注冊Bean
核心都是填充對應(yīng)的數(shù)據(jù)結(jié)構(gòu),
最終把Bean添加到Spring構(gòu)建的Bean容器中,供后續(xù)取用。
雖然干巴巴流程太流于表面,沒有質(zhì)感,但是,這些使用源碼中摳出來的
雖然散,但是,易于理解和記憶,
后面會詳細(xì)給出流程的源碼及源碼分析。
3.2 BeanDefinition相關(guān)
先從注冊BeanDefinition開始分析,
BeanDefiniton,從命名即可知,是Bean的定義,用于描述Bean,
Spring將BeanDefinition設(shè)計為一個interface,面向接口,
BeanDefinition源碼長這樣,如下圖所示,
這里僅需要知道BeanDefinition是用于描述Bean的即可,
會在其他文章詳細(xì)講解BeanDefinition。
為什么要注冊BeanDefinition?
因為BeanDefinition用于描述Bean實例,因此,創(chuàng)建Bean的時候需要先注冊BeanDefition,描述Bean,然后再創(chuàng)建Bean,兩者是關(guān)聯(lián)的。
注冊BeanDefinition的核心是填充beanDefinitionMap和beanDefinitionNames。
-
beanDefinitionMap
位置:org.springframework.beans.factory.support.DefaultListableBeanFactory#beanDefinitionMap
源碼如下圖所示,由注釋可知,beanDefinitionMap用于存儲bean名稱和beanDefintion,所謂的關(guān)聯(lián)關(guān)系就在這里,(key, value)->(beanName, beanDefinition),為后續(xù)通過beanName獲取beanDefinition做準(zhǔn)備。 -
beanDefinitionNames
位置:org.springframework.beans.factory.support.DefaultListableBeanFactory#beanDefinitionNames
源碼如下圖所示,由注釋可知,beanDefintionNames存儲Bean名稱,為后續(xù)填充singletonObjects做準(zhǔn)備。
3.2.1 流程
前置知識已介紹,下面進入正題,講解流程。
源碼調(diào)試流程如下圖所示,感興趣的可以參照流程圖中打斷點調(diào)試。
(1)AnnotationConfigApplicationContext中注冊BeanDefinition:registerBeanDefinition
(2)GenericApplicationContext中注冊BeanDefinition:registerBeanDefinition
(3)DefaultListableBeanFactory中注冊BeanDefinition:registerBeanDefinition
(4)DefaultListableBeanFactory中填充beanDefinitionMap:beanDefinitionMap.put
(5)DefaultListableBeanFactory中填充beanDefinitionNames:beanDefinitionNames.add
3.2.2 源碼調(diào)試過程
3.2.2.1 AnnotationConfigApplicationContext中注冊BeanDefinition:registerBeanDefinition
3.2.2.2 GenericApplicationContext中注冊BeanDefinition:registerBeanDefinition
3.2.2.3 DefaultListableBeanFactory中注冊BeanDefinition:registerBeanDefinition
3.2.2.4 DefaultListableBeanFactory中填充beanDefinitionMap:beanDefinitionMap.put &
3.2.2.5 DefaultListableBeanFactory中填充beanDefinitionNames:beanDefinitionNames.add
合并步驟4和5,填充對應(yīng)的數(shù)據(jù)。
3.3 Bean相關(guān)
完成上面的準(zhǔn)備工作,接下來就要創(chuàng)建Bean,
這個Bean實際單例對象,在singletonObjects中存儲,為后面獲取Bean做準(zhǔn)備。
singletonObjects:
位置:org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#singletonObjects
源碼如下圖所示,由注釋可知,singletonObjects緩存單例Bean實例。
3.3.1 流程
下面開始講解如何填充這個Map,
源碼調(diào)試流程如下圖所示,感興趣的可以參照流程圖中打斷點調(diào)試。
(1)AnnotationConfigApplicationContext中刷新相關(guān)的Bean:refresh
(2)同步鎖,AbstractApplicationContext中刷新相關(guān)Bean:refresh
(3)結(jié)束BeanFactory初始化,AbstractApplicationContext初始化的最后一步,完成準(zhǔn)備工作之后,需要固化相關(guān)的操作:finishBeanFactoryInitialization
(4)預(yù)處理,DefaultListableBeanFactory實例化單體Bean:preInstantiateSingletons
(5)獲取Bean,AbstractBeanFactory根據(jù)Bean名稱獲取Bean:getBean(java.lang.String)
(6)獲取Bean,AbstractBeanFactory獲取Bean:doGetBean
(7)獲取單例Bean,DefaultSingletonBeanRegistry根據(jù)Bean名稱獲取單例Bean:getSingleton(java.lang.String, org.springframework.beans.factory.ObjectFactory<?>)
(8)添加單例Bean,DefaultSingletonBeanRegistry添加單例Bean:addSingleton
(9)添加Bean,DefaultSingletonBeanRegistry:singletonObjects.put
3.3.2 源碼調(diào)試過程
3.3.2.1 AnnotationConfigApplicationContext中刷新相關(guān)的Bean:refresh
3.3.2.2 同步鎖,AbstractApplicationContext中刷新相關(guān)Bean:refresh
3.3.2.3 結(jié)束BeanFactory初始化,AbstractApplicationContext初始化的最后一步,完成準(zhǔn)備工作之后,需要固化相關(guān)的操作:finishBeanFactoryInitialization
3.3.2.4 預(yù)處理,DefaultListableBeanFactory實例化單體Bean:preInstantiateSingletons
3.3.2.5 獲取Bean,AbstractBeanFactory根據(jù)Bean名稱獲取Bean:getBean(java.lang.String)
3.3.2.6 獲取Bean,AbstractBeanFactory獲取Bean:doGetBean
3.3.2.7 獲取單例Bean,DefaultSingletonBeanRegistry根據(jù)Bean名稱獲取單例Bean:getSingleton(java.lang.String, org.springframework.beans.factory.ObjectFactory<?>)
3.3.2.8 添加單例Bean,DefaultSingletonBeanRegistry添加單例Bean:addSingleton
3.3.2.9 添加Bean,DefaultSingletonBeanRegistry:singletonObjects.put
3.4 獲取Bean
通過上面的準(zhǔn)備工作,填充了相關(guān)的數(shù)據(jù),
下面可以通過應(yīng)用上下文獲取Bean。
3.4.1 流程
源碼調(diào)試流程如下圖所示,感興趣的可以參照流程圖中打斷點調(diào)試。
(1)獲取Bean,AnnotationConfigApplicationContext:getBean
(2)獲取Bean,AbstractApplicationContext:getBean(java.lang.String)
(3)獲取Bean,AbstractBeanFactory:getBean(java.lang.String)
(4)處理邏輯,獲取Bean,AbstractBeanFactory:doGetBean
(5)從填充的singletonObjects獲取Bean,DefaultSingletonBeanRegistry:getSingleton(java.lang.String)
(6)獲取Bean,得到查詢的Bean,DefaultSingletonBeanRegistry:getSingleton(java.lang.String, boolean)
3.4.2 源碼調(diào)試
3.4.2.1 獲取Bean,AnnotationConfigApplicationContext:getBean
3.4.2.2 獲取Bean,AbstractApplicationContext:getBean(java.lang.String)
3.4.2.3 獲取Bean,AbstractBeanFactory:getBean(java.lang.String)
3.4.2.4 處理邏輯,獲取Bean,AbstractBeanFactory:doGetBean
3.4.2.5 從填充的singletonObjects獲取Bean,DefaultSingletonBeanRegistry:getSingleton(java.lang.String)
3.4.2.6 獲取Bean,得到查詢的Bean,DefaultSingletonBeanRegistry:getSingleton(java.lang.String, boolean)
最終返回查到的Bean。文章來源:http://www.zghlxwxcb.cn/news/detail-400024.html
4 小結(jié)
(1)新建Bean的三種方式:XML、@Bean和BeanDefinition;
(2)創(chuàng)建Bean的核心過程:
(2.1)注冊BeanDefinition,填充beanDefinitionMap和beanDefinitionNames;
(2.2)注冊Bean,填充singletonObjects;
(3)通過應(yīng)用上下文獲取Bean,是通過singletonObjects查詢獲取。文章來源地址http://www.zghlxwxcb.cn/news/detail-400024.html
到了這里,關(guān)于實戰(zhàn)講解及分析Spring新建Bean的幾種方式以及創(chuàng)建過程(圖+文+源碼)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!