《系列二》-- 2、bean 的作用域: Scope 有哪些

閱讀之前要注意的東西:本文就是主打流水賬式的源碼閱讀,主導的是一個參考,主要內容需要看官自己去源碼中驗證。全系列文章基於 spring 源碼 5.x 版本。

寫在開始前的話:

閱讀spring 源碼實在是一件龐大的工作,不說全部內容,單就最基本核心部分包含的東西就需要很長時間去消化了:

  • beans
  • core
  • context

實際上我在博客裏貼出來的還只是一部分內容,更多的內容,我放在了個人,fork自 spring 官方源碼倉了; 而且對源碼的學習,必須是要跟着實際代碼層層遞進的,不然只是乾巴巴的文字味同嚼蠟。

https://gitee.com/bokerr/spring-framework-5.0.x-study

這個倉設置的公共倉,可以直接拉取。



Spring源碼閱讀系列--全局目錄.md



作用域 Scope 特性概述

Spring 容器支持以下6種 scopes

常規作用域

在常規的 spring IOC 場景下使用

  • prototype: 原型模式
    跟單例模式相反,每次使用beanName 向spring 容器申請bean的時候,必須重新new一個bean對象。

如下案例,向spring 容器中注入一個 "原型模式" 作用域的bean, 每次當需要注入 WorkerService 時,都會創建一個全新的bean 對象。

@Component
@Scope("prototype")
public class WorkerService {
    public void handler(String arg) {
      this.workerDo();
    }
    public void workerDo() {
        // xx 業務邏輯
    }
}
  • singleton:單例模式<Spring默認的bean作用域>
    當用戶通過getBean(beanName) 向容器主動申請bean時、或者由 spring 容器自身進行依賴注入操作時;必須保證被當作依賴,注入的單例bean全局唯一。

如下案例:通過註解的方式向容器中注入一個單例bean, spring 容器在第一次創建 UserService 這個bean後會將其緩存下來。
後續通過同樣的beanName 來獲取這個bean的時候,Spring 容器會將之前緩存的bean引用返回,從而保證 UserService 的bean 全局唯一。

@Component
@Scope("singleton")
public class UserService {
    @Autowired
    private WorkerService workerService;
    
    public void handler(String arg) {
      workerService.handler(arg);
      System.out.println("workerService實例對象內存地址:" + workerService);
    }
}

web 場景作用域

  • request: 該作用域的bean,在每次HTTP 請求發生時被創建,它生命週期被,該HTTP請求的生命週期包含。(只有一個bean實例)

  • session: session作用域的bean,在session的生命週期內有效。(只有一個bean實例)

  • application: 該作用域的bean 在 ServletContext 的生命週期內有效。(只有一個bean實例)

  • webSocket: 在webSocket生命週期內有效。(只有一個bean實例)

實際上當下 Spring Web MVC 已經顯現頹勢,大勢已經轉向了前後端分離,所以 Web 場景的 bean 作用域的使用場景已經越來越少,本文也不再詳細展開。

經典問題

單例作用域的 UserService 中注入,原型作用域的 WorkerService;必須保證從 spring 容器中多次獲取 UserService 時,其中注入的 WorkerService 每次都必須不一樣。

模擬場景

結合上邊講解單例和原型作用域時,給的僞代碼,已知:

  • UserService 是單例作用域

    • WorkerService 將作爲依賴注入到 UserService 中
  • WorkerService 是原型作用域

看如下的程序1和程序2,如果我們從不同的地方,多次使用 UserService.handler(arg), 你猜猜打印出來的 WorkerService 對象地址會是怎樣的?

程序1:

@Controller
public class BusinessOld {
    @Autowired
    private UserService userService;
    
    public void execute(String arg) {
      userService.handler("arg");
    }
}

程序2:

@Controller
public class BusinessNew {
    @Autowired
    private UserService userService;
    
    public void execute(String arg) {
      userService.handler("arg");
    }
}

實際上運行上述的兩段代碼,輸出的 workerService 對象內存地址將會一樣. 前邊也講過了spring 對單例bean的管理模式:

  • 單例bean 在第一次被加載後,會被直接緩存在容器中,後續再向容器請求這個單例 bean時,會將之前加載好的bean 直接返回

  • 再說直白點,在UserService 類的單例bean 在第一次被初始化時,其上注入的 WorkerService 類對象就已經注入成功了,由於在UserService 是通過單例的形式注入容器的。
    所以它只會被初始化一次,所以 WorkerService 也只會被注入 UserService 中一次。自然就會打印一模一樣的 WorkerService 地址了

所以,上述的 程序1 和 程序2 將會輸出同樣的 WorkerService 對象地址。

解決辦法

方法一

藉助前邊講過的 "lookup-method"實現

img.png

簡單迴歸下這個標籤的作用,lookup-method.name 屬性配置一個方法名, lookup-method.bean 配置一個beanId;
當從容器中加載 id="lookUpTest" 的bean 時,就會通過 lookup-method.name 配置的方法,動態的返回一個 userBean。

這裏我們就不用xml的方式書寫案例了,基於上述的原理,我們可以簡單改一下 UserService 類的代碼:

這裏在 UserService.handler() 方法上加了一個註解:@Lookup 這代表着,UserService.handler() 方法每次執行時都會重新向容器獲取 WorkerService 類的bean。

因爲其以原型模式作用域注入容器,故此可以保證每次都能獲取到一個新new出來的 WorkerService 類對象。

@Component
@Scope("singleton")
public class UserService {
    @Autowired
    private WorkerService workerService;
    
    @Lookup("workerService")
    public void handler(String arg) {
      workerService.handler(arg);
      System.out.println("workerService實例對象內存地址:" + workerService);
    }
}

方法二 實現接口 BeanFactoryAware

我們首先看看這個接口的結構:

  • 很簡單,這裏只有一個簡單的Setter 方法,從這裏可以接收到的參數是 BeanFactory 對象,也就是spring容器工廠本身,意思就是說我把整個車牀鬥給你了,你還車不出來一個珠子?

BeanFactoryAware

既然都拿到工廠了,你要什麼bean 還不是隨你喜歡,還要啥自行車。下邊直接看修改好的代碼:

@Component
@Scope("singleton")
public class UserService implements BeanFactoryAware {
    private BeanFactory beanFactory; // 接收Setter 注入的容器對象工廠
    @Override
    public void setBeanFactory(Beanfactory beanFactory) {
        this.beanFactory = beanFactory;
    }
    
    // @Autowired
    // private WorkerService workerService;
    
    @Lookup("workerService")
    public void handler(String arg) {
      // workerService.handler(arg);
      WorkerService workerService = beanFactory.getBean("workerService");
      workerService.handler(arg);
      
      System.out.println("workerService實例對象內存地址:" + workerService);
    }
}

這裏相當於我們會在運行時動態從 BeanFactory 中獲取bean: "workerService"
由於我們在bean 定義時把它的作用域定義爲了 "原型模式", 故此這裏當然可以從容器中撈出單例的 WorkerService

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章