前言
前兩篇文章介紹了如何使用
@Component
,@Import
注解來向spring容器中注冊組件(javaBean),本文將介紹通過FactoryBean
接口繼續向spring容器中注冊組件??赡艿谝挥∠笫莝pring中BeanFactory
接口,但是這里確實說的是FactoryBean
。
推薦閱讀
根據接口名稱,我們也可以簡單看出兩者的區別
- FactoryBean:它是spring中的一個Bean,只不過它是一個特殊的Bean(工廠Bean),我們可以通過它來自定義生產需要的普通JavaBean
- BeanFactory:它是spring的Bean工廠,是spring最為重要的接口之一,spring通過此接口獲取,管理容器中的各個Bean
接下來將進入本文正題,如何通過FactoryBean
接口向spring容器中注冊組件
正如前面說的,FactoryBean也是spring中的一個Bean,但是它又是一個特殊的Bean,它的存在是為了生產其他的JavaBean。首先我們看看FactoryBean
自身的接口定義
public interface FactoryBean<T> {
/**
* 從Spring容器中獲取Bean時會調用此方法,返回一個T對象
*/
@Nullable
T getObject() throws Exception;
/**
* 此工廠Bean返回對象的類型
*/
@Nullable
Class<?> getObjectType();
/**
* 工廠Bean創建的對象是否為單例,
* 如果返回false,說明getObject方法的實例對象不是單例的,
* Spring每次從容器中獲取T對象時,都調用getObject方法創建一個對象
*/
default boolean isSingleton() {
//spring 5 接口默認返回true(單例)
return true;
}
}
FactoryBean
接口定義簡單明了,就是用來獲取一個Bean的基本信息,下面我們自己實現該接口,來生產一個javaBean
/**
* 產生 Bike 對象的工廠Bean
*/
@Component
public class BikeFactoryBean implements FactoryBean<Bike> {
public Bike getObject() throws Exception {
System.out.println("......開始創建Bike對象......");
return new Bike();
}
public Class<?> getObjectType() {
return Bike.class;
}
public boolean isSingleton() {
return true;
}
}
自定義的一個JavaBean類
/**
* 待注冊的自定義組件
*/
@Data
public class Bike {
private String id = "by FactoryBean";
}
添加spring容器啟動的引導類
/**
* spring 容器啟動引導類,測試 FactoryBean 功能
*/
@ComponentScan("com.spring.study.ioc.factorybean")
public class TestFactoryBeanBootstrap {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext =
new AnnotationConfigApplicationContext(TestFactoryBeanBootstrap.class);
//獲取工廠Bean本身的Id
String[] beanNames = applicationContext.getBeanNamesForType(BikeFactoryBean.class);
System.out.println("BikeFactoryBean names:" + Arrays.asList(beanNames));
//獲取工廠Bean產生的Bean的Id
beanNames = applicationContext.getBeanNamesForType(Bike.class);
System.out.println("Bike bean names:" + Arrays.asList(beanNames));
Object bean = applicationContext.getBean("bikeFactoryBean");
System.out.println(bean);
bean = applicationContext.getBean(Bike.class);
System.out.println(bean);
// 獲取工廠Bean 本身的實例對象
bean = applicationContext.getBean(BeanFactory.FACTORY_BEAN_PREFIX + "bikeFactoryBean");
System.out.println(bean);
applicationContext.close();
}
}
啟動spring容器,控制臺打印結果:
BikeFactoryBean names:[&bikeFactoryBean]
Bike bean names:[bikeFactoryBean]
......開始創建Bike對象......
Bike(id=by FactoryBean)
Bike(id=by FactoryBean)
com.spring.study.ioc.factorybean.BikeFactoryBean@4eb7f003
由結果可以看出
BikeFactoryBean
類上加了@Component
注解,但是從spring容器仍然可以獲取到Bike
類的信息&
符,也就是說,工廠Bean定義的Id實際為getObject()方法返回Bean的Id,而工廠Bean本身的Id被添加了一個前綴&
符&
符,而此前綴在BeanFactory
接口中已經定義了FACTORY_BEAN_PREFIX
如果將BikeFactoryBean
的isSingleton()
方法返回了false
public boolean isSingleton() {
return false;
}
重新啟動spring容器,可以看如下結果:
BikeFactoryBean names:[&bikeFactoryBean]
Bike bean names:[bikeFactoryBean]
......開始創建Bike對象......
Bike(id=by FactoryBean)
......開始創建Bike對象......
Bike(id=by FactoryBean)
com.spring.study.ioc.factorybean.BikeFactoryBean@4eb7f003
由結果可以看出,唯一的變化在于從spring容器中多次獲取實際Bean時,工廠Bean的getObject()方法被多次進行了調用。這與spring容器中被標識為原型的普通Bean相同,每次從spring中獲取Bean時都會被實例化。
要想了解FactoryBean的執行過程,就需要結合Spring容器啟動的過程來進行分析。而spring容器的啟動過程經過了紛繁復雜的步驟。為了盡可能少的入坑和挖坑,下面僅結合FactoryBean相關的源碼進行說明。
在開始入坑之旅之前,結合前面的例子做幾點說明,方便后續講解
前面定義的 BikeFactoryBean 類上面直接添加了@Component注解,這樣spring會默認以類名首字母小寫(bikeFactoryBean)作為beanName;如果使用@Bean進行注冊時,spring默認會以方法名作為beanName,下面繼續以“BikeFactoryBean”為例。
spring容器啟動過程中,在執行完所有的BeanFactoryPostProcessor
,BeanPostProcessor
以及注冊Listener
后會執行org.springframework.context.support.AbstractApplicationContext#finishBeanFactoryInitialization()
方法,此方法會對剩下所有的非Abstract
、非LazyInit
的單實例Bean進行實例化,以下為部分代碼片段。
@Override
public void preInstantiateSingletons() throws BeansException {
...省略代碼...
// 1. 拷貝一份副本:spring容器中的所有的Bean名稱
List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
// 2. 遍歷每一個beanName,嘗試通過getBean()方法進行實例化
// 在getBean()方法內部會先嘗試從容器singletonObjects中獲取Bean,如果沒有才會進行實例化操作
for (String beanName : beanNames) {
// 3. 通過beanName獲取Bean定義信息 BeanDefinition
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
// 4. 根據BeanDefinition判斷該Bean是否不是抽象的,單例的,非懶加載的
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
// 5. 滿足上面的條件后,在根據beanName判斷此Bean是否是一個工廠Bean(實現了FactoryBean接口)
if (isFactoryBean(beanName)) {
// 6. 如果是一個工廠Bean,則在此處進行工廠Bean本身的實例化
Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
...省略代碼...
}
else {
// 如果不是工廠bean,也是調用getBean()方法進行實例化
getBean(beanName);
}
}
}
...省略代碼...
}
preInstantiateSingletons()是在finishBeanFactoryInitialization()方法內部調用的
根據上面的代碼流程,在第6步時,會對
BikeFactoryBean
類本身進行實例化,并且可以看出傳遞的beanName為初始注冊的name前添加了&
符前綴即&bikeFactoryBean
,用于在genBean()方法內部標識它是一個工廠Bean。但是在跟蹤源碼后發現,在getBean()方法內部,會先將傳入的beanName(&bikeFactoryBean
)開頭的&
符去除,并且最終實例化Bean后,在容器中保存的beanName還是不帶&
符前綴的名稱即bikeFactoryBean
根據第一步的結果,spring容器在啟動后,工廠Bean會像普通Bean一樣在spring容器中會保留一條自身的單實例Bean(spring容器中保存的數據為:<bikeFactoryBean, BikeFactoryBean>),既然spring容器中只保存了BikeFactoryBean本身,那么后續獲取Bike
類的beanName
和Bean實例時,又是怎么獲取到的呢?帶著疑問,我們繼續看后面的代碼。首先,上面的例子中調用了applicationContext.getBeanNamesForType(Bike.class)
方法來獲取Bike
類的beanName
。所以繼續跟蹤此方法看看到底發生了什么。
// getBeanNamesForType()方法內部最終調用了此方法,可斷點跟蹤至此
private String[] doGetBeanNamesForType(ResolvableType type, boolean includeNonSingletons, boolean allowEagerInit) {
// 用來保存符合條件的結果(beanName集合)
List<String> result = new ArrayList<>();
// 與上面的代碼相似,遍歷spring容器中注冊的所有的beanNames
for (String beanName : this.beanDefinitionNames) {
if (!isAlias(beanName)) {
try {
// 根據beanName獲取Bean的定義信息 BeanDefinition
RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
// 根據BeanDefinition 進行檢查
if (!mbd.isAbstract() && (allowEagerInit ||
(mbd.hasBeanClass() || !mbd.isLazyInit() || isAllowEagerClassLoading()) &&
!requiresEagerInitForType(mbd.getFactoryBeanName()))) {
// 根據beanName和Bean的定義信息判斷是否是工廠Bean
boolean isFactoryBean = isFactoryBean(beanName, mbd);
BeanDefinitionHolder dbd = mbd.getDecoratedDefinition();
boolean matchFound =
(allowEagerInit || !isFactoryBean ||
(dbd != null && !mbd.isLazyInit()) || containsSingleton(beanName)) &&
(includeNonSingletons ||
(dbd != null ? mbd.isSingleton() : isSingleton(beanName))) &&
// 根據Bean的定義信息判斷完后,在此方法中判斷此beanName對應的Bean實例是否與傳入的類型相匹配
isTypeMatch(beanName, type);
// 如果根據beanName獲得的是一個工廠Bean,并且與傳入的類型不匹配,則滿足條件,將beanName添加 & 符前綴
if (!matchFound && isFactoryBean) {
// 對于工廠Bean,接下來嘗試匹配工廠Bean實例本身
beanName = FACTORY_BEAN_PREFIX + beanName;
matchFound = (includeNonSingletons || mbd.isSingleton()) && isTypeMatch(beanName, type);
}
// 如果獲取的Bean實例與傳入的類型匹配,將beanName添加到結果集合中
if (matchFound) {
result.add(beanName);
}
}
}
catch (CannotLoadBeanClassException ex) {
// ...省略代碼...
}
catch (BeanDefinitionStoreException ex) {
// ...省略代碼...
}
}
}
// ...省略代碼...
return StringUtils.toStringArray(result);
}
isTypeMatch()方法中的部分代碼
public boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException {
// 對beanName進行處理,將開頭的 & 符過濾
String beanName = transformedBeanName(name);
// 從spring容器中獲取單實例Bean,由于spring容器啟動時已經將單實例Bean進行了實例化,
// 所以此時可以直接在容器中得到Bean實例
Object beanInstance = getSingleton(beanName, false);
if (beanInstance != null && beanInstance.getClass() != NullBean.class) {
// 獲取到Bean實例后,判斷是否為工廠Bean
if (beanInstance instanceof FactoryBean) {
// 如果是工廠Bean,并且獲取的beanName不是以&符開頭
if (!BeanFactoryUtils.isFactoryDereference(name)) {
// 將實例強轉為 FactoryBean 并調用 FactoryBean接口的getObjectType()方法,
// 獲取工廠Bean所生產的實例類型
Class<?> type = getTypeForFactoryBean((FactoryBean<?>) beanInstance);
// 判斷工廠Bean生產的實例類型與傳入的類型是否匹配
return (type != null && typeToMatch.isAssignableFrom(type));
}
else {
return typeToMatch.isInstance(beanInstance);
}
}
// ...省略代碼...
}
// ...省略代碼...
}
getTypeForFactoryBean()方法中的代碼
protected Class<?> getTypeForFactoryBean(final FactoryBean<?> factoryBean) {
try {
if (System.getSecurityManager() != null) {
return AccessController.doPrivileged((PrivilegedAction<Class<?>>)
factoryBean::getObjectType, getAccessControlContext());
}
else {
// 直接調用 FactoryBean 接口的 getObjectType()方法,獲取生產的類型
return factoryBean.getObjectType();
}
}
catch (Throwable ex) {
// Thrown from the FactoryBean's getObjectType implementation.
logger.info("FactoryBean threw exception from getObjectType, despite the contract saying " +
"that it should return null if the type of its object cannot be determined yet", ex);
return null;
}
}
以上便是getBeanNamesForType()
方法經過的部分重要代碼
由此可以看出,當我們想要獲取
BikeFactoryBean
本身的beanName時,doGetBeanNamesForType方法內部將bikeFactoryBean
前添加了&
符前綴,于是便獲取到了&bikeFactoryBean
;
當我們想要獲取Bike類型的beanName時,spring會通過容器遍歷已經注冊的所有的beanNames,然后根據beanName及對應的Bean定義信息BeanDefinition進行判斷過濾,并且對于所有的工廠Bean,會獲取spring容器中已經實例化的Bean對象,調用 FactoryBean 接口的 getObjectType()方法,得到工廠Bean所生產的實例類型,然后與Bike.class相比較,如果匹配,則將此beanName保存到結果集中,最后返回。所以,當我們想要獲取Bike類型的beanName時,從spring容器中便可以找到bikeFactoryBean
。
從spring容器中獲取到beanName
后,我們繼續獲取Bike實例
從前文中引導類的代碼可以看出,獲取Bike實例有兩種方式,跟蹤源碼可以發現,根據Bike類型獲取實例時,spring實際是通過第二步獲取到beanName
后再最終調用doGetBean
方法獲取實例對象。下面看看部分源碼
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
// 對傳入的beanName進行過濾,去除&符前綴
final String beanName = transformedBeanName(name);
Object bean;
// 從spring容器中獲取實例,由于spring容器啟動時已經將單實例Bean進行實例化,所以此時可以直接獲得
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
if (logger.isTraceEnabled()) {
if (isSingletonCurrentlyInCreation(beanName)) {
logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +
"' that is not fully initialized yet - a consequence of a circular reference");
}
else {
logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
}
}
// 獲取指定的Bean實例,如果是工廠bean,則為Bean實例本身或其創建的對象。
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
// ...省略代碼...
return (T) bean;
}
從上面的代碼可以看出,獲取Bike實例的具體代碼還在getObjectForBeanInstanc()
方法內部,我們繼續查看
protected Object getObjectForBeanInstance(
Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) {
// 判斷beanName是否是以&符開頭的
if (BeanFactoryUtils.isFactoryDereference(name)) {
if (beanInstance instanceof NullBean) {
return beanInstance;
}
if (!(beanInstance instanceof FactoryBean)) {
throw new BeanIsNotAFactoryException(transformedBeanName(name), beanInstance.getClass());
}
}
// 根據beanName從spring容器中獲取的Bean實例如果不是工廠Bean,或者beanName是以&符開頭,就直接返回這個Bean實例
// 當我們獲取Bike類型的實例時,beanName為“bikeFactoryBean”,
// beanInstance為“BikeFactoryBean”類型,是一個工廠Bean,所以條件不滿足,繼續向下走
if (!(beanInstance instanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name)) {
return beanInstance;
}
Object object = null;
if (mbd == null) {
// 根據beanName從緩存中獲取Bean實例,第一次來獲取Bike實例時為空,
// factoryBeanObjectCache.get(beanName);
// 后續再獲取時,便可以在此獲得到,然后返回
object = getCachedObjectForFactoryBean(beanName);
}
if (object == null) {
// 將獲取的工廠Bean強轉為 FactoryBean 類型,以便下面調用其getObject()方法獲取對象
FactoryBean<?> factory = (FactoryBean<?>) beanInstance;
// 獲取bean定義信息
if (mbd == null && containsBeanDefinition(beanName)) {
mbd = getMergedLocalBeanDefinition(beanName);
}
boolean synthetic = (mbd != null && mbd.isSynthetic());
//在此方法內部調用 FactoryBean 接口的 getObject()方法獲取對象
object = getObjectFromFactoryBean(factory, beanName, !synthetic);
}
return object;
}
距離真相還差兩步了,堅持就是勝利,我們繼續看getObjectFromFactoryBean()
的源碼
protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) {
// 此處調用 FactoryBean 的isSingleton()方法,判斷是否是一個單列
// 如果是單例的,走if內部,獲取到對象后,會保存到factoryBeanObjectCache緩存中,以便后續使用
if (factory.isSingleton() && containsSingleton(beanName)) {
synchronized (getSingletonMutex()) {
// 檢查緩存中是否已經存在
Object object = this.factoryBeanObjectCache.get(beanName);
if (object == null) {
// 調用最后一個方法,執行FactoryBean 的 getObject()方法獲取對象
object = doGetObjectFromFactoryBean(factory, beanName);
// 再次檢查緩存
Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
if (alreadyThere != null) {
object = alreadyThere;
}
else {
// ... 省略代碼 ...
if (containsSingleton(beanName)) {
// 將獲取的對象放入factoryBeanObjectCache緩存中,以便后續使用
this.factoryBeanObjectCache.put(beanName, object);
}
}
}
return object;
}
}
// 如果不是單例的,每次獲取的對象直接返回,不會放入緩存中,所以每次都會調用getObject()方法
else {
Object object = doGetObjectFromFactoryBean(factory, beanName);
if (shouldPostProcess) {
try {
object = postProcessObjectFromFactoryBean(object, beanName);
}
catch (Throwable ex) {
throw new BeanCreationException(beanName, "Post-processing of FactoryBean's object failed", ex);
}
}
return object;
}
}
根據上面的流程,終于來到了最后一步
private Object doGetObjectFromFactoryBean(final FactoryBean<?> factory, final String beanName)
throws BeanCreationException {
Object object;
try {
if (System.getSecurityManager() != null) {
AccessControlContext acc = getAccessControlContext();
try {
object = AccessController.doPrivileged((PrivilegedExceptionAction<Object>) factory::getObject, acc);
} catch (PrivilegedActionException pae) {
throw pae.getException();
}
}
else {
// 直接調用 FactoryBean 接口的 getObject()方法獲取實例對象
object = factory.getObject();
}
}
catch (FactoryBeanNotInitializedException ex) {
throw new BeanCurrentlyInCreationException(beanName, ex.toString());
}
// ... 省略代碼 ...
return object;
}
經過如此多的代碼,spring終于幫我們獲取到Bike對象實例
通過BikeFactoryBean來獲取Bike類的實例時,spring先獲取Bike類型對應的beanName(bikeFactoryBean),然后根據beanName獲取到工廠Bean實例本身(BikeFactoryBean),最終spring會調用BikeFactoryBean 的 getObject()方法來獲取Bike對象實例。并且根據 BikeFactoryBean 實例的 isSingleton() 方法來判斷Bike類型的實例是否時單例的,依此來決定要不要將獲取的Bike對象放入到緩存中,以便后續使用。
本文主要講解了如何通過 FactoryBean
接口向spring容器中注入組件,通過簡單的案例進行模擬,并根據案例對源碼的執行過程進行跟蹤,分析了FactoryBean
接口的執行過程。
另外,在每一次跟蹤spring源碼時,都會有新的收獲。在spring龐大的體系下,只有定位好自己的目標,明確自己的需求,才不會被spring無限的代碼所淹沒。
學習永遠都不是一件簡單的事情,可以有迷茫,可以懶惰,但是前進的腳步永遠都不能停止。
不積跬步,無以至千里;不積小流,無以成江海;
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。