Spring是如何解決循環依賴的?


Get Started

首先我們新建了 Maven 項目,並且在 pom.xml 文件中新增了依賴

<!-- https://mvnrepository.com/artifact/org.springframework/spring-beans -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-beans</artifactId>
    <version>${spring.version}</version>
</dependency>

這個依賴是 2020年7月的最新引用,你可以從 中央倉庫 獲取最新的依賴。

由於我們選擇引用了 spring-beans 沒有引用 spring-context 依賴,自然也就沒有 ApplicationContext 接口,沒有 @Serivce@Component 注解,因此我們更加專注地分析 Spring 究竟是怎樣解決循環依賴的。

循壞依賴

我們在用戶服務中引用商品服務:

public class UserService {
    private GoodsService goodsService;
}

我們在商品服務中引用用戶服務:

public class GoodsService {
    private UserService userService;
}

兩者相互依賴,就像下圖所示:

探究如何 getBean

Object ApplicationContext.getBean(Class cls) 是我們在項目中常用的獲取 Bean 對象的方法,其本質是調用 beanFactory.getBean(),而 beanFactory 的常用對象就是 DefaultListableBeanFactory

下面我們就來研究一下怎么用 DefaultListableBeanFactory 來獲取 Bean 對象。

出於對 getBean() 方法的熟悉,我們“熟練”地寫出了下面的代碼:

// 注意:這是一個會報錯的常見錯誤示例
@Test
public void test1() {
      DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
      UserService bean = factory.getBean(UserService.class);
      System.out.println(bean);
}

遺憾的是,代碼運行時拋出了 NoSuchBeanDefinitionException,這個時候我注意到 BeanDefinitionRegistry 接口,這是 DefaultListableBeanFactory 實現的一個接口。
這個接口提供了注冊 BeanDefinition 的方法-void registerBeanDefinition(String name, BeanDefinition beanDefinition), 於是我們改寫代碼:

// 這個例子中 userService 對象屬性 goodsService 為 null,不算正確
@Test
public void createBeanTest() {
    DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
    RootBeanDefinition def = new RootBeanDefinition(UserService.class);// 添加 BeanDefinition
    factory.registerBeanDefinition("userService", def); // 為工廠類注冊 BeanDefinition
    UserService bean = factory.getBean(UserService.class);
    System.out.println(bean);
}

我這次用 debug 模式,斷點斷在最后一行的輸出語句上,然后 Evaluate 得到如下圖的結果:

很不幸,這次沒能正常填充屬性 goodsService,於是我們再次調用 RootBeanDefinition.setPropertyValues(MutablePropertyValues propValues) 改寫代碼:

// 還是一段錯誤的代碼示例
@Test
public void createBeanTest() {
    DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
    RootBeanDefinition def = new RootBeanDefinition(UserService.class);
    // 新增代碼:填充屬性 goodsService
    def.setPropertyValues(new MutablePropertyValues().add("goodsService", new RuntimeBeanReference(GoodsService.class)));
    factory.registerBeanDefinition("userService", def);
    UserService bean = factory.getBean(UserService.class);
    System.out.println(bean);
}

控制台情況下:

如圖所示,這次拋出了 BeanCreationException,該異常發生在 UserService 填充屬性 goodsService 的時候。嵌套的異常還是 NoSuchBeanDefinitionException,主要原因是沒有 GoodsService 對應的 BeanDefinition,我們又又又改寫一次:

// 這是一段正確的代碼
@Test
public void test() {
    DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
    {
        RootBeanDefinition userServiceDef = new RootBeanDefinition(UserService.class);
        userServiceDef.setPropertyValues(new MutablePropertyValues()
                .add("goodsService", new RuntimeBeanReference("goodsService")));
        factory.registerBeanDefinition("userService", userServiceDef);
    }
    {
        RootBeanDefinition goodsServiceDef = new RootBeanDefinition(); // 為商品類GoodsService補充定義
        goodsServiceDef.setBeanClass(GoodsService.class);
        goodsServiceDef.setPropertyValues(new MutablePropertyValues()
                .add("userService", new RuntimeBeanReference("userService")));
        factory.registerBeanDefinition("goodsService", goodsServiceDef);
    }
    factory.getBean(UserService.class);
}

如圖所示,再拋異常:

錯誤原因:缺少 setter 方法,我們需要修正一下 UserService 和 GoodsService 類,給他們增加 setter 方法,修正如下:

public class UserService {
    private GoodsService goodsService;
    // 修正:增加 setter 方法
    public void setGoodsService(GoodsService goodsService) {
        this.goodsService = goodsService;
    }
}
public class GoodsService {
    private UserService userService;
    // 修正:增加 setter 方法
    public void setUserService(UserService userService) {
        this.userService = userService;
    }
}

我們再次debug,把斷點打在最后一行,然后 Evaluate。我們最終得到了期望結果——“無限套娃”(即循環依賴):

Bean 的創建過程

經過上面一系列的操作,想必大家已經看出了一些端倪,創建 Bean 好像是下面這個過程:

上面這張圖結合了我們先前探究的過程,我們有了一個初步的印象,Bean 的創建過程似乎可以簡化為如下過程:

  • 實例化 Bean,簡單理解就是 UserService bean = new UserService()
  • 填充屬性,即 bean.setGoodsService(new GoodsService())
  • 后置代理,就是在 Bean 外面再包裝一層 BeanProxy proxy = new BeanProxy(bean); proxy.doSomething();
  • 添加到實例池: Map<String, Object> instances = new HashMap<>(); instances.put("userService", bean);

Spring 創建 Bean 的簡化流程

首先我們來看一下 DefaultListableBeanFactory 類繼承結構圖:

Spring 創建Bean的簡化過程如下圖所示:

  • 首先調用公開方法 DefaultListableBeanFactory.getBean(Class cls) 來獲取 Bean 對象,通過一系列操作轉化 class 對象為 beanName。
  • 得到 beanName 之后,調用 AbstractBeanFactory.getBean(String name, Class cls) , 在這個方法中又會調用受保護的 AbstractBeanFactory.doGetBean 方法
  • doGetBean 方法中,又會調用 DefaultSingletonBeanFactory.getSingleton(String beanName, ObjectFactory factory)
  • getSingleton 方法中,首先在單例池 DefaultSingletonBeanFactory.singletonObjects 查找是否已經存在 beanName 對應的單例對象。如果找不到,那會調用函數式接口 ObjectFactory 來創建一個對象,而這個對象工廠調用的函數是一個受保護的抽象方法 AbstractBeanFactory.crateBean(String beanName, RootBeanDefinition def, Object[] args)
  • AbstractAutowireCapableBeanFactory 實現了超類的 createBean 方法, 並在 createBean 方法中調用了受保護的方法 AbstractAutowireCapableBeanFactory.doCreateBean
  • doCreateBean方法中,還會調用 AbstractAutowireCapableBeanFactory.populateBean 來填充屬性。
  • populateBean 填充屬性時,還可能遇上沒有創建過的對象,因此還可能遞歸調用 AbstractBeanFactory.getBean(String beanName)

小結一下:
AbstractAutowireCapableBeanFactory 負責了實例化 Bean,填充屬性,后置處理的任務,DefaultSingletonBeanRegistry 負責了單例池的維護工作。

Spring 解決循環依賴


解決循環依賴,關鍵還得看注冊中心中三個方法:添加單例工廠、獲取未完成的單例以及添加到單例池。首先我們需要了解一下注冊中心三個重要的成員變量:

我們把斷點打在 DefaultSingletonBeanRegistry.getSingleton 方法內:

protected Object getSingleton(String beanName, boolean allowEarlyReferenc
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && this.isSingletonCurrentlyInCreation(be
        synchronized(this.singletonObjects) {
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                ObjectFactory<?> singletonFactory = (ObjectFactory)this.s
                if (singletonFactory != null) {
                    singletonObject = singletonFactory.getObject();
                    this.earlySingletonObjects.put(beanName, singletonObj
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}
  • 第一次調用發生 doGetBean 中,查詢單例池中是否已經存在 UserService bean 對象,即調用 getSingleton("userService" /*beanName*/, true /*allowEarlyReference*/),方法返回 null。
  • 沒有查詢到單例,因此先調用 DefaultSingletonBeanRegistry.beforeSingletonCreation("userService" /*beanName*/),調用后正在創建的單例對象新增了 "userService"
  • 然后調用 getSingleton("userService" /*beanName*/, ObjectFactory<?> singletonFactory) 獲取並創建 UserService bean 對象。
  • 緊接着調用 doCreateBean 方法,並在該方法中調用 addSingletonFactory("userService" /*beanName*/, ObjectFactory<?> singletonFactory) 需要警惕的是,這里的 ObjectFactory 匿名類調用的方法是 AbstractAutowireBeanFactory.getEarlyBeanReference,通過該工廠創建的是未完成的 bean 對象。
  • 再接着就該調用 populateBean 方法,並且在該方法中調用 applyPropertyValues ,欲為 UserService bean 對象設置 goodsService 屬性
  • 再次調用 doGetBean,查詢單例池中是否已經存在 GoodsService bean 對象,即調用 getSingleton("goodsService" /*beanName*/, true /*allowEarlyReference*/),方法返回 null。
  • 執行 DefaultSingletonBeanRegistry.beforeSingletonCreation("goodsService" /*beanName*/) 后,正在創建的單例對象新增了 "goodsService"
  • 接着就該獲取和創建 GoodsService bean 對象了,和創建 UserService bean 對象類似,在調用 addSingletonFactory 方法后,單例工廠集發生了變化,注冊單例集也發生了變化:
  • 在為 GoodsService bean 對象設置 userService 屬性時,再一次進入了 getSingleton("userService" /*beanName*/, true /*allowEarlyReference*/),但是這次,從單例工廠集合中取出了 "userService" 對應的 ObjectFactory 對象,創建了一個 未完成的 UserService Bean對象,放到了未完成單例集中,同時從單例工廠中移除了"userService"對應的 ObjectFactory 工廠。
  • 緊接着 GoodsService Bean 對象先調用了 afterSingletonCreation("goodsService" /*beanName*/),移出了正在創建單例集
  • 然后就移除工廠集中的 ObjectFactory 對象,並將 GoodsService Bean 放入單例池:
  • 然后 UserService Bean 對象也成功設置了 goodsService 屬性,完成創建,緊接着調用 afterSingletonCreationaddSingleton 方法

Spring 如何解決循環依賴的?


總結,Spring 就是靠獲取未完成的Bean對象來填充屬性,解決循環依賴的。

為什么不直接把對象放入 earlySingletonObjects,而是先放入 singletonFactories 中?
創建對象是需要消耗性能的,假如創建沒有循環依賴的對象,是不需要創建未完成的Bean對象的,所以不是直接創建未完成對象放入未完成單例集 earlySingletonObjects,而是把未完成單例工廠放入 singletonFactories
比如以下這段代碼,在運行時就不會調用 getSingleton 中的 ObjectFactory.getObject() 方法來創建 TestService 未完成 Bean 對象。

// Bean 對象類
public class TestService {
    String name;

    public void setName(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
}
public class CreateBeanTest {
    @Test
    public void testCreateTestService() {
        DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
        RootBeanDefinition definition = new RootBeanDefinition(TestService.class);
        definition.setPropertyValues(new MutablePropertyValues().add("name", "admin"));
        factory.registerBeanDefinition("testService", definition);
        TestService service = factory.getBean(TestService.class);
        System.out.println(service.getName());
    }
}

* 超類是如何調用子類的方法的?

補充一個問題:超類 DefaultSingletonBeanRegistry 是如何調用子類 AbstractAutowireCapableBeanFactory 中的 doGetBean 方法的?

// AbstractBeanFactroy.doGetBean 方法中的一段代碼
sharedInstance = this.getSingleton(beanName, () -> {
    try {
        return this.createBean(beanName, mbd, args);
    } catch (BeansException exp) {
        this.destroySingleton(beanName);
        throw exp;
    }
});

對於 AbstractBeanFactroy 而言, createBean 是一個需要子類實現的抽象方法

// AbstractBeanFactory 類中的抽象方法 createBean
protected abstract Object createBean(String var1, RootBeanDefinition var2, @Nullable Object[] var3) throws BeanCreationException;

然后我們來看 DefaultSingletonBeanRegistry 獲取並創建單例的方法

// 節選 DefaultSingletonBeanRegistry.getSingleton(String beanName, ObjectFactory<?> singletonFactory) 方法中一段代碼
try {
    singletonObject = singletonFactory.getObject();
    newSingleton = true;
} catch (IllegalStateException exp1) {
    // ... (省略)
} catch (BeanCreationException exp2) {
    // ... (省略)
}

因此 AbstractBeanFactroy 通過 ObjectFactory.getObject() 間接調用了 createBean 方法,而子類 AbstractAutowireCapableBeanFactory實現了 createBean 方法

// AbstractAutowireCapableBeanFactory.createBean 方法中的一段代碼
beanInstance = this.doCreateBean(beanName, mbdToUse, args);

小結一下:
超類 DefaultSingletonBeanRegistry 是在函數式接口 ObjectFactory 的匿名實現類中,調用抽象方法 createBean
再由子類 AbstractAutowireCapableBeanFactory 實現抽象方法,來達到超類調用子類方法的目的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM