關於spring中單例Bean引用原型Bean產生的問題及解決

spring單例Bean引用原型Bean產生的問題及解決

問題描述

spring裡Bean都有一個作用域,用註解@Scope表示,容器默認使用的是單例,即“singleton”顧名思義就是指容器每次隻為我們創建一次Bean,以後使用都是拿的同一個。

雖然平時開發的時候基本都是使用單例的,但不免有時候會使用到另一種類型,即原型模式,這時候就會產生一個問題:當我們單例Bean註入瞭原型Bean,並且調用瞭原型Bean的某個方法,我們希望的是單例Bean隻初始化一次,而原型Bean每次調用應該都是重新生成的,然而,結果確與我們想的並不一樣,無論是單例還是原型都隻會初始化一次。

為瞭更直觀的發現問題,下面我們用代碼演示一遍

首先新建一個SpringConfig配置類:

@Configuration
@ComponentScan("com.whb")
public class AppConfig {
}

這個隻是一個配置類,並且指定瞭容器掃描“com.whb”下面的類。

接著我們在com.whb下新建2個類:

@Component("dao")
@Scope("prototype")
public class IndexDao {
}
@Component
public  class IndexService  {
    @Autowired
    IndexDao indexDao;
    public void test() {
        System.out.println("service:"+this.hashCode());
        System.out.println("dao:"+indexDao.hashCode());
    }
}

IndexDao 指定原型模式,而IndexService則是默認的單例模式,我們往IndexSerevice裡註入IndexDao,並且在test方法裡打印出2個類的哈希值,來驗證是否一致。

新建一個Test來測試

public class Test {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext an = new AnnotationConfigApplicationContext(AppConfig.class);
        IndexService indexService = an.getBean(IndexService.class);
        indexService.test();
        IndexService indexService1 = an.getBean(IndexService.class);
        indexService1.test();
        IndexService indexService2 = an.getBean(IndexService.class);
        indexService2.test();
    }
}

這裡我們直接從容器中取3次IndexService ,同時調用3次test()方法,結果如下:

問題來瞭,IndexService是單例哈希值不變可以理解,那為什麼設置瞭原型的IndexDao也不變呢?

問題分析

其實仔細想一下,這問題很好理解。當我們第一次從容器中拿IndexService時,他會為我們創建一個實例,創建過程中發現IndexService還依賴著另一個屬性,那麼此時容器便會先去生成IndexDao的實例並且註入到IndexService中,然後創建出IndexService實例並且返回。

此時容器中有著一個IndexService和一個IndexDao。當我們第二次去拿IndexService時,容器發現已經有瞭一個實例,並且IndexService是單例的所有它直接就返回瞭那個存在著的IndexService實例。雖然IndexDao設置瞭原型,但由於IndexService隻有一次機會設置屬性所以從到尾容器並沒有生成第二個IndexDao實例。這也就解釋瞭為什麼哈希值每次都是一樣的。

真實情況下我們肯定不希望是這個結果,不然的話我們設置原型還有個毛線作用。

其實spring官方文檔給出瞭2中解決方案,點擊我查看文檔說明 

下面我直接貼出代碼演示一遍:

方法一:實現ApplicationContextAware

我們來修改下IndexService的內容:

public  class IndexService implements ApplicationContextAware{
    private ApplicationContext applicationContext;
    public void print() {
        System.out.println("service:"+this.hashCode());
        System.out.println("dao:"+applicationContext.getBean("dao").hashCode());
    }
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

我們通過實現ApplicationContextAware這個接口並重寫setApplicationContext()這個方法可以拿到ApplicationContext對象,從而用這個對象獲取容器中的Bean.

結果如下:

可以看到service還是每次都一樣,符合單例設置,但是dao確每次都改變瞭,說明原型設置也生效瞭。

上面雖然解決瞭這個問題,但是這種方法還是有一定的弊端:

ApplicationContextAware是spring提供給我們的接口,IndexService是我們業務的類,我們直接實現可以說是增大瞭和spring框架的耦合程度。因此spring還提供瞭第二種方法:

方法二:使用@LookUp註解

我們繼續修改IndexService的內容:

@Component
public  abstract class IndexService {
    public void print() {
        System.out.println("service:"+this.hashCode());
        System.out.println("dao:"+getIndexDao().hashCode());
    }
    @Lookup("dao")
    public abstract IndexDao getIndexDao();
}

可以看到使用這種方式代碼簡潔瞭很多,我們隻需要聲明一個抽象方法,並在該方法上面添加@Lookup(“dao”)註解,“dao”表示要註入的類名,spring容器就會自動幫我們註入IndexDao實例。

結果如下:

總結:在spring開發中可能會遇到各種各樣的問題,但其實最好的解決辦法就是查閱文檔,畢竟這可是第一手資料!!

spring Bean的幾個相關問題

1.Spring Bean 作用域

Spring 容器中的 bean 可以分為 5 個范圍(scope配置項)。 

1)singleton(單例模式) :這個模式是默認的,使用該屬性定義Bean時,IOC容器僅創建一個Bean實例,IOC容器每次返回的是同一個Bean實例。 

2)prototype(原型模式) :使用該屬性定義Bean時,IOC容器可以創建多個Bean實例,每次返回的都是一個新的實例。 

3)request(HTTP請求) :該屬性僅對HTTP請求產生作用,每次HTTP請求都會創建一個新的Bean,在請求完成以後,bean 會失效並被垃圾回收器回收。 

4)Session(會話) :該屬性僅用於HTTP Session,同一個Session共享一個Bean實例。不同Session使用不同的實例。在 session 過期後,bean 會隨之失效。 

5)global-session(全局會話) :該屬性僅用於HTTP Session,所有的Session共享一個Bean實例。

2.什麼是Spring inner beans

將這一個bean聲明為另一個Bean的內部bean。內部bean可以用setter註入“屬性”和構造方法註入“構造參數”的方式來實現。無論何時此內部bean被使用,僅僅作為被作為一個屬性被調用。

3.什麼是有狀態、無狀態

  • 單例:某個類系統范圍內隻有一個實例
  • 多例:某個類在系統范圍內同時有多個實例
  • 無狀態類:類中沒有狀態信息,一般是無成員變量或成員變量的值是不變的。
  • 有狀態類:類中有狀態信息,一般表現成員變量的值可變,在某一時該被調用而改變狀態,之後再調用時獲取其正確的狀態。

4.Spring框架中的單例Bean是線程安全的麼

單例模式確保某一個類隻有一個實例,當多用戶同時請求一個服務時,容器會給每一個請求分配一個線程,這是多個線程會並發執行該請求多對應的成員方法,如果這個單例是無狀態的,那麼就是線程安全的,如果這個單例是有狀態的就不是線程安全的。 

解決有狀態單例線程安全的措施: 對於有狀態的單例可以實現全局共享,狀態的修改最好加鎖,保證線程的安全性。

5.Spring Bean 的自動裝配

1)首先用@Component註解類

@Component
Class public Student{

2)在啟動類上添加@ComponentScan註解的類, spring才能自動裝配bean

@ComponentScan 
Class public Application{

3)開啟默認掃描,spring將掃描由@Component註解的類,並且創建個實力

6.各種自動裝配模式的區別

1)no: 這是 Spring 框架的默認設置,在缺省情況下,自動配置是通過“ref”屬性手動設定。 

2)byName: 可以根據 bean 名稱設置依賴關系。當向一個 bean 中自動裝配一個屬性時,容器將根據 bean 的名稱自動在在配置文件中查詢一個匹配的 bean。 

3)byType: 可以根據 bean 類型設置依賴關系。當向一個 bean 中自動裝配一個屬性時,容器將根據 bean 的類型自動在在配置文件中查詢一個匹配的 bean。 

4)constructor: 在構造函數參數的byType方式。**5)、autodetect:**如果找到默認的構造函數,使用“自動裝配用構造”; 否則,使用“按類型自動裝配”(在Spring3.0以後的版本此模式已被廢棄,已經不再合法瞭)。

7.在Spring中可以註入null或空字符串嗎

沒問題

8.Spring框架中有哪些不同類型的事件(都繼承自ApplicationContextEvent)

1)上下文更新事件(ContextRefreshedEvent): 當ApplicationContext初始化或刷新完成後觸發的事件。 

2)上下文開始事件(ContextStartedEvent): 當ApplicationContext啟動後觸發的事件 

3)上下文停止事件(ContextStoppedEvent): 當ApplicationContext停止後觸發的事件。 

4)上下文關閉事件(ContextClosedEvent): 當ApplicationContext被關閉時觸發該事件。當容器被關閉時,其管理的所有單例Bean都被銷毀。 

5)請求處理事件(RequestHandledEvent): 在Web應用中,當一個http請求(request)結束觸發該事件。

9.Spring框架中都用到瞭哪些設計模型

1)代理模式: AOP就是基於動態代理的,把公共的代碼抽象出來,封裝到一個模塊中用於代理,便於減少系統的重復代碼,降低模塊間的耦合度,並有利於未來的可拓展性和可維護性。 

2)單例模式: 在spring配置文件中定義的bean默認為單例模式。

使用單例模式的好處:

(1)對於頻繁使用的對象,可以省略創建對象所花費的時間,減少系統的開銷;

(2)減少new操作的次數,系統內存使用率就會降低,這將減輕GC壓力,縮短GC停頓時間。 

3)模板方法: 它定義瞭一系列操作的模型,子類繼承之後可以在模型 不變的情況下去實現自定義的操作。它可以用來解決代碼重復的問題。 Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 結尾的對數據庫操作的類,它們就使用到瞭模板模式。 

4)適配器模式: 將一個接口轉換成客戶希望的另一個接口,適配器模式使接口不兼容的那些類可以一起工作。Spring AOP 的實現是基於代理模式,但是 Spring AOP 的增強或通知(Advice)使用到瞭適配器模式,與之相關的接口是AdvisorAdapter。Advice 常用的類型有:BeforeAdvice(目標方法調用前,前置通知)、AfterAdvice(目標方法調用後,後置通知)、AfterReturningAdvice(目標方法執行結束後,return之前)。 

5)裝飾器模式: 裝飾者模式可以動態地給對象添加一些額外的屬性或行為。當我們需要修改原有的功能,但我們又不願直接去修改原有的代碼時,設計一個Decorator套在原有代碼外面。對於InputStream,ileInputStream 、BufferedInputStream都是對InputStream功能的擴展。 

6)觀察者模式: 它表示的是一種對象與對象之間具有依賴關系,當一個對象發生改變的時候,這個對象所依賴的對象也會做出反應。Spring 事件驅動模型就是觀察者模式很經典的一個應用。 

7)工廠模式: BeanFactory用來創建對象的實例。 

8)委派模式: Spring 提供瞭 DispatcherServlet 來對請求進行分發。

10.FileSystemResource、 ClassPathResource 有何區別

ClassPathResource 在環境變量中讀取配置文件,FileSystemResource 在配置文件中讀取配置文件。

11.使用Spring框架的好處是什麼

1)輕量: Spring 是輕量的。 

2)控制反轉: 在Spring中對象有 IOC容器創建,並且通過配置註入到配置變量中。Spring通過控制反轉實現瞭低耦合。 

3)面向切面的編程(AOP): Spring支持面向切面的編程,並且把應用業務邏輯和系統服務分開。 

4)容器(IOC): IOC可以創建、管理應用中對象、對象生命周期和對象之間的關系。**5)MVC框架:**提供MVC框架,將控制邏輯代碼、數據存儲、試圖展示分層。 

6)事務管理: Spring 提供一個持續的事務管理接口,封裝事物操作代碼。 

7)異常處理: Spring可以全局捕捉異常。

12.Spring5 新特性

1) 支持JDK 8+和Java EE7+以上版本 

2) 運行時兼容JDK9 

3) 運行時兼容Java EE8 API 

4) 反應式編程模型 

5) 使用註解進行編程

6) 函數式編程

7) 提供專門的 HTTP/2 特性支持

8) 使用 Spring WebFlux 執行集成測試

9) 使用 JUnit 5 執行條件和並發測試

10) 支持Kotlin

以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。

推薦閱讀: