Spring容器系列-FactoryBean使用/原理

欢乐豆123發表於2024-06-19

Spring- FactoryBean使用/原理

概要

在某些情況下,例項化Bean過程比較複雜,若按照傳統的方式,則需要在中提供大量的配置資訊,不夠靈活,這時採用編碼的方式能得到一個簡單的方案。
Spring為此提供了一個org.springframework.bean.factory.FactoryBean的工廠類介面,使用者可以透過實現該介面定製例項化Bean的邏輯。

FactoryBean介面在Spring中佔重要地位,Spring自身就提供了70多個Factory Bean的實現。它們隱藏了例項化一些複雜Bean的細節,給上層應用帶來了便利。

從Spring3.0開始,FactoryBean開始支援泛型,即介面宣告改為FactoryBean<T>的形式。

一、普通bean與FactoryBean的區別

Spring容器中有兩種bean:普通bean和工廠bean。

1. 普通bean

透過反射來例項化被標記為bean的類。例:@Component指定的類。

2. FactoryBean

FactoryBean返回的物件不是指定類的一個例項,而是Factory Bean#getObject方法返回的物件。若想獲取Factory Bean本身,要在bean的名稱新增字首&來獲取FactoryBean物件本身(applicationContext.getBean("&"+beanName)。

FactoryBean介面原始碼:

 1 public interface FactoryBean<T>{
 2    String OBJECT_TYPE_ATTRIBUTE "factoryBeanObjectType";
 3 
 4    @Nullable
 5    T getobject()throws Exception;
 6 
 7    @Nullable
 8    Class<?>getobjectType();
 9 
10    default boolean isSingleton(){
11       return true;
12    }
13 }

3. FactoryBean不遵循Spring的生命週期

Spring的作者想要放權給使用者,讓使用者自己實現建立一個bean的邏輯,所以Spring並不會過多的插手該Bean的例項化過程,使得一個Bean的例項化完全由使用者本人去實現。

這個類並不會像普通bean那樣在Spring容器初始化時進行例項化,而是類似於懶載入,在獲取時才進行建立和返回。至於是不是單例,要取決於isSingleton()方法的返回值。

二 、FactoryBean的用法

FactoryBean的特殊之處在於它可以向容器中註冊兩個Bean,一個是它本身,一個是FactoryBean.getObject()方法返回值所代表的Bean。先透過如下示例程式碼來感受下FactoryBean的用處吧。

自定義一個類CustomerFactoryBean,讓它實現了FactoryBean介面,重寫了介面中的兩個方法,在getObejct()方法中,返回了一個UserService的例項物件;在getObjectType()方法中返回了UserService.class。

然後在CustomerFactoryBean新增了註解@Component註解,意思是將CustomerFactoryBean類交給Spring管理。

UserService類:

1 public class UserService {
2     public UserService() {
3         System.out.println("userService construct");
4     }
5 }

CustomerFactoryBean類:

 1 package org.example.factory;
 2 
 3 import org.example.service.UserService;
 4 import org.springframework.beans.factory.FactoryBean;
 5 import org.springframework.stereotype.Component;
 6 
 7 /**
 8  * @author hxq
 9  * @date 2024/6/19 11:07
10  */
11 @Component
12 public class CustomerFactoryBean  implements FactoryBean<UserService> {
13     public UserService getObject() throws Exception {
14         return new UserService();
15     }
16 
17     public Class<?> getObjectType() {
18         return UserService.class;
19     }
20 }

AppConfig類:

1 @Configuration
2 @ComponentScan("org.example.factory")
3 public class AppConfig {
4 
5 }

啟動類:

 1 public class App {
 2     public static void main(String[] args) {
 3         AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
 4         System.out.println("容器啟動完成");
 5         CustomerFactoryBean rawBean = (CustomerFactoryBean) applicationContext.getBean("&customerFactoryBean");
 6         System.out.println(rawBean);
 7 
 8         UserService userService = applicationContext.getBean(UserService.class);
 9         System.out.println(userService);
10         Object customerFactoryBean = applicationContext.getBean("customerFactoryBean");
11         System.out.println(customerFactoryBean);
12     }
13 }
14 
15 # 執行結果
16 容器啟動完成
17 org.example.factory.CustomerFactoryBean@35fc6dc4
18 userService construct
19 org.example.service.UserService@1dd92fe2
20 org.example.service.UserService@1dd92fe2

從上面例子可以看出,FactoryBean是一個特殊的Bean。我們自定義的CustomerFactoryBean實現了FactoryBean介面,所以當CustomerFactoryBean被掃描進Spring容器時,實際上它向容器中註冊了兩個bean,一個是CustomerFactoryBean類的單例物件;另外一個就是getObject()方法返回的物件。

三、FactoryBean的原始碼

透過上面的示例程式碼,我們知道了FactoryBean的作用,也知道該如何使用FactoryBean,那麼接下來我們就透過原始碼來看看FactoryBean的工作原理。

在Spring容器啟動階段,會呼叫到refresh()方法,在refresh()中有呼叫了finishBeanFactoryInitialization()方法,最終會呼叫到beanFactory.preInstantiateSingletons()方法。所以我們先看下這個方法的原始碼。

DefaultListableBeanFactory#preInstantiateSingletons方法:

 1 public void preInstantiateSingletons() throws BeansException {
 2     // 從容器中獲取到所有的beanName
 3     List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
 4     for (String beanName : beanNames) {
 5         RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
 6         if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
 7 
 8             // 在此處會根據beanName判斷bean是不是一個FactoryBean,實現了FactoryBean介面的bean,會返回true
 9             // 此時當beanName為customerFactoryBean時,會返回true,會進入到if語句中
10             if (isFactoryBean(beanName)) {
11                 // 然後透過getBean()方法去獲取或者建立單例物件
12                 // 注意:在此處為beanName拼接了一個字首:FACTORY_BEAN_PREFIX
13                 // FACTORY_BEAN_PREFIX是一個常量字串,即:&
14                 // 所以在此時容器啟動階段,對於customerFactoryBean,應該是:getBean("&customerFactoryBean")
15                 Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
16 
17 
18                 // 下面這一段邏輯,是判斷是否需要在容器啟動階段,就去例項化getObject()返回的物件,即是否呼叫FactoryBean的getObject()方法
19                 if (bean instanceof FactoryBean) {
20                     final FactoryBean<?> factory = (FactoryBean<?>) bean;
21                     boolean isEagerInit;
22                     if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
23                         isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>)
24                                         ((SmartFactoryBean<?>) factory)::isEagerInit,
25                                 getAccessControlContext());
26                     }
27                     else {
28                         isEagerInit = (factory instanceof SmartFactoryBean &&
29                                 ((SmartFactoryBean<?>) factory).isEagerInit());
30                     }
31                     if (isEagerInit) {
32                         getBean(beanName);
33                     }
34                 }
35             }
36             else {
37                 getBean(beanName);
38             }
39         }
40     }
41 }

參考連結:https://juejin.cn/post/6844903954615107597

相關文章