天天看點

難得可貴的Spring依賴注入實戰經驗,是程式員就該吸收一下!

作者:程式員進階碼農II

Spring依賴注入實戰經驗

介紹完Spring架構所具備的三種依賴注入類型,我們發現使用這些類型并不複雜。但不複雜并不代表開發人員能夠用得好。

接下來,我将和大家分享使用依賴注入的一些實戰經驗。

把握Bean的作用域

在介紹Setter方法注入時,我們已經提到了Spring中的Bean作用域的概念。作用域描述了Bean在Spring IoC容器上下文中的生命周期和可見性。在這裡,我們将讨論Spring架構中不同類型的Bean作用域及其在使用上的指導規則。

如果想要通過注解來設定Bean的作用域,可以使用如代碼清單2-26所示的示例代碼。

代碼清單2-26 設定Bean作用域示例代碼

@Configuration

public class AppConfig {

@Bean

@Scope("singleton")

public HealthRecordService createHealthRecordService() {

return new HealthRecordServiceImpl();

}

}

可以看到這裡使用了一個@Scope注解來指定Bean的作用域為單例的singleton。在Spring中,除了單例作用域之外,還有一個prototype,即原型作用域,也可以稱之為多例作用域,以與單例作用域進行差別。使用方式上,我們同樣可以使用如代碼清單2-27所示的枚舉值來對它們進行設定。

代碼清單2-27 通過枚舉值設定作用域代碼

@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)

@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)

在Spring IoC容器中,Bean的預設作用域是單例,也就是說不管有多少個對Bean的引用,容器隻會建立一個執行個體。而原型作用域則不同,每次請求Bean時,Spring IoC容器都會建立一個新的對象執行個體。

從兩種作用域的效果而言,我們總結出一條開發上的經驗,即對于有狀态的Bean,我們應該使用原型作用域,反之則應該使用單例作用域。

那麼,什麼樣的Bean是有狀态的呢?結合Web應用程式,我們可以明确對于每次HTTP請求而言,我們都應該建立一個Bean來代表這一次的請求對象。

同樣,對于會話而言,我們也需要針對每個會話建立一個會話狀态對象。這些都是常見的有狀态的Bean。為了更好地管理這些Bean的生命周期,Spring還專門針對Web開發場景提供了對應的request和session作用域。

靈活使用注解配置

在使用Spring依賴注入類型時,通常可以使用XML配置、Java代碼配置以及注解配置這三種方式。随着Spring Boot架構的流行,使用注解配置已經成為目前最主流的開發方式。除了前面已經給出的最常見的@Autowired注解,Spring Boot架構還提供了一組非常有用的注解幫助我們更好地管理所注入的對象,包括@Primary注解和@Qualifier注解。

在Spring IoC容器中,針對HealthRecordService這樣一種接口類型,原則上容器隻允許注入一個實作類。如果存在該類型的多個對象執行個體,那麼容器就會報NoUniqueBean-DefinitionException,意味着容器無法決定選擇哪一個執行個體來進行注入。這時候就可以使用@Primary注解來幫助容器做出選擇,該注解的使用方式如代碼清單2-28所示。

代碼清單2-28 @Primary注解示例代碼

@Component

public class HealthRecordServiceImplA implements HealthRecordService {

...

}

@Component

@Primary

public class HealthRecordServiceImplB implements HealthRecordService {

...

}

這時候,Spring IoC容器隻會注入HealthRecordServiceImplB這個執行個體類,這在管理針對某種類型的多個執行個體時非常有用。

和@Primary注解的應用場景類似,@Qualifier注解為我們選擇執行個體類進行注入提供了更加靈活的實作方式,如代碼清單2-29所示。

代碼清單2-29 @Qualifier注解示例代碼

@Component

@Qualifier("healthRecordServiceA")

public class HealthRecordServiceImplA implements HealthRecordService {

}

@Component

@Qualifier("healthRecordServiceB")

public class HealthRecordServiceImplB implements HealthRecordService {

}

可以看到,這裡對不同的實作類,我們通過@Qualifier注解設定了不同的名稱,這樣在使用時就可以通過該名稱擷取不同的執行個體,如代碼清單2-30所示。

代碼清單2-30 通過@Qualifier注解指定不同執行個體名稱的示例代碼

@Autowired

@Qualifier("healthRecordServiceB")

private HealthRecordService healthRecordService;

設定元件掃描範圍

在Spring中,我們可以通過設定元件掃描範圍來簡化Bean的注入配置。

因為任何類都位于某一個包結構之下,是以Spring提供了一個@ComponentScan注解,該注解在需要大規模對象注入的場景下非常有用,其基本用法如代碼清單2-31所示。

代碼清單2-31 @ComponentScan注解示例代碼

@Configuration

@ComponentScan(basePackages="com.spring.bestpractice")

public class AppConfig { }

在這個示例中,Spring會掃描由basePackages指定的包路徑com.spring.bestpractice及其子路徑下的所有Bean,并把它們注入到容器中。當然,我們首先需要在這些類上添加@Component注解以及由該注解衍生的@Service、@Repository、@Controller等注解。

不同配置的性能分析

在本小節中,我們将讨論不同類型的Bean配置如何影響應用程式性能,

并且我們還将讨論Bean配置的一些最佳實踐。

首先要讨論的是前面介紹的@ComponentScan注解。因為該注解會掃描basePackages指定的包中的所有元件,是以如果所指定包中的元件并不需要在應用程式啟動時就全部加載到容器中,那麼對包路徑進行精細化設計是一個實踐技巧。例如,我們可以通過設定一個清單來細化具體的包結構路徑,如代碼清單2-32所示。

代碼清單2-32 在@ComponentScan注解中指定的包結構路徑示例代碼

@Configuration

@ComponentScan(basePackages="com.spring.bestpractice.service","com.spr

ing.bestpractice.controller")

public class AppConfig { }

然後要讨論的是單例模式和原型模式對性能的影響。在Spring中,當把Bean範圍設定為prototype時,每次請求Bean時,Spring IoC容器都會建立一個新的對象執行個體。是以,使用原型模式在建立過程中會對性能産生影響,對那些初始化過程需要消耗巨大資源的對象而言尤其如此,這些對象常見的有網絡連接配接對象、資料庫連接配接對象等。是以,對這些對象,應該完全避免使用原型模式。或者,我們應該在使用前仔細設計并對性能進行充分測試。

最後一個值得讨論的性能分析點在于Spring IoC容器的延遲加載(LazyLoading)和預加載(Preloading)機制。通過@Autowired注入的Bean都是在Spring IoC容器啟動時被建立和初始化的,這個過程被稱為預加載。但有時候,我們希望能夠延遲Bean的加載時機,這時候就可以使用@Lazy注解,使用方法如代碼清單2-33所示。

代碼清單2-33 @Lazy注解示例代碼

@Component

@Lazy

public class HealthRecordServiceImpl implements HealthRecordService {

}

添加了@Lazy注解的效果是隻有在使用到這個Bean時它才會去初始化,而不是在Spring IoC容器啟動時直接初始化,這樣就可以節省容器資源。

延遲加載確定在請求時動态加載Bean,預加載確定在使用Bean之前加載Bean。Spring IoC容器預設使用預加載。然而,在容器啟動時就加載所有類(即使它們沒有被使用)并不是一個明智的決定,因為有些Bean執行個體會非常消耗資源。我們應該根據實際情況選擇具體的加載方法。如果需要盡快地加載應用程式,那麼就采用延遲加載;如果需要應用程式盡快地運作并更快地為請求提供服務,那麼就執行預加載。

Spring依賴注入面試題分析

面試題1:Spring架構提供了哪幾種依賴注入類型,推薦使用哪種注入類型?

答案:Spring架構提供了分别基于字段、構造器和Setter方法的三種依賴注入類型,其中,Spring官方推薦使用的是構造器注入類型。

面試題2:Spring中Bean的作用域有哪些,如何正确選擇作用域?

答案:Spring中Bean的作用域常見的有單例和原型兩種,預設的是單例。在選擇作用域時,基本原則就是對有狀态的Bean,我們應該使用原型作用域,反之則應該使用單例作用域。

面試題3:如果針對某個接口你需要提供多個實作類,但又希望它們都能夠被注入到Spring IoC容器中,你有什麼辦法?

答案:預設情況下,Spring IoC容器在啟動時不允許某個接口存在多個實作類,但我們可以通過@Primary注解來設定主實作類。另外,我們還可以使用@Qualifier注解來為不同的實作類命名,進而根據不同的名稱來注入目标對象。

面試題4:如果想要縮短Spring IoC容器的啟動時間,你有什麼辦法?

答案:針對這個問題,解決的基本思想就是減少容器啟動時所需要初始化的Bean的數量。我們可以從三個方面來回答這個問題,首先通過@ComponentScan注解來控制元件掃描的範圍,其次通過合理設定Bean的作用域來降低大對象的建立成本,最後還可以使用延遲加載機制來控制Bean的初始化時機。

面試題5:Spring Bean的注冊流程是怎麼樣的?

答案:Bean的注冊流程主要圍繞BeanDefinition對象展開,包含建構BeanDefinition、設定BeanDefinition屬性以及注冊BeanDefinition等步驟。這個流程涉及ApplicationContext和BeanFactory這兩個Spring IoC容器的核心元件之間的協作和互動。

面試題6:Spring中Bean的執行個體化過程包含哪些核心步驟?

答案:Spring中Bean的執行個體化過程包含三大核心步驟,即基于構造器的反射方法建立Bean,通過屬性注入執行個體化Bean,以及通過回調機制擴充Bean。Bean的分階段執行個體化過程與解決循環依賴問題也有緊密關聯。

面試題7:Spring如何解決循環依賴問題?

答案:三級緩存機制是Spring用來解決循環依賴問題的基本方法。結合Bean執行個體化的生命周期,需要了解這種方法無法消除基于構造器注入的循環依賴,而隻能應用于Setter方法注入的場景。

面試題8:如果業務代碼出現了循環依賴,有哪些應對的方案?

答案:消除循環依賴的政策有很多,其中通過調整類與類之間的協作關系可以很好地把循環依賴調整為間接依賴。在日常開發過程中,提取中介者、轉移業務邏輯以及引入回調機制都是非常常見的解決方案。通常,這些方案能夠運作的前提是合理地提取業務接口,并通過Spring的依賴注入完成對這些類的有效管理。

難得可貴的Spring依賴注入實戰經驗,是程式員就該吸收一下!

本文給大家講解的内容是spring核心容器依賴注入類型和原理:Spring依賴注入實戰經驗

  • 下文給大家講解的是spring核心容器面向切面概念和實踐:面向切面與Spring AOP