Spring實現(xiàn)IoC和DI的方法詳解
一.Spring中的IoC
IoC全稱Inversion of Control (控制反轉(zhuǎn)) ,這里的控制其實是控制權(quán)的意思。可以理解為對象的獲取權(quán)力和方式發(fā)生了發(fā)轉(zhuǎn)。也就是說,當(dāng)需要某個對象時,傳統(tǒng)開發(fā)模式中需要??通過 new 創(chuàng)建對象;而IoC的思想則不一樣,IoC旨在把創(chuàng)建對象的任務(wù)交給外部的容器,程序中只需要引入容器里存放的需要的對象即可。這個容器一般被稱為:IoC容器。
其實IoC我們在前?已經(jīng)使?了,我們在前?講到,在類上?添加 @RestController 和 @Controller 注解,就是把這個對象交給Spring管理,Spring 框架啟動時就會加載該類,把對象交給Spring管理,這就是Spring中的IoC思想。Spring對于IoC的實現(xiàn)主要是通過工廠設(shè)計模式+反射來實現(xiàn)的,當(dāng)我們需要某個對象的時候,只需要將創(chuàng)建對象的任務(wù)交給容器,在程序中只需要調(diào)用(注入)即可。
前?我們提到IoC控制反轉(zhuǎn),就是將對象的控制權(quán)交給Spring的IOC容器,由IOC容器創(chuàng)建及管理對象。那么在Spring程序中,我們該如何通過代碼來實現(xiàn)IoC呢?
Spring框架為了更好的服務(wù)應(yīng)用程序,提供了倆類注解來實現(xiàn)將對象集中管理創(chuàng)建:
- 類注解:@Controller、@Service、@Repository、@Component、@Configuration
- 方法注解:@Bean
@Controller
使? @Controller 存儲 bean 的代碼如下所?:
@Controller // 將對象存儲到 Spring 中 public class UserController { public void sayHi(){ System.out.println("hi,UserController..."); } }
如何觀察這個對象已經(jīng)存在Spring容器當(dāng)中了呢? 我們可以通過啟動類中的getBean方法來得到Spring中管理的Bean。
@SpringBootApplication public class IoCdemoApplication { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(IoCdemoApplication.class, args); UserController bean = context.getBean(UserController.class); bean.sayHi(); } }
在控制臺即可觀察到輸出:
但是一旦將@Controller注釋掉,程序就會報錯找不到這個Bean,這就說明了使用@Controller確實是可以將該類對象交給Spring進(jìn)行管理
@Service
@Service public class UserService { public void sayHi(String name) { System.out.println("Hi," + name); } }
還是來獲取一下這個對象,看看結(jié)果如何
ConfigurableApplicationContext context = SpringApplication.run(IoCdemoApplication.class, args); UserService bean = context.getBean(UserService.class); bean.sayHi("CSDN");
如果注釋掉@Service就會報錯,因此可以證實@Service可以將類對象交給Spring進(jìn)行管理
后續(xù)的三個注解如果進(jìn)行相同方式的驗證都會得到一樣的結(jié)果,這里就不再贅述
@Repository
@Repository public class UserRepository { public void sayHi() { System.out.println("Hi, UserRepository~"); } }
@Component
@Component public class UserComponent { public void sayHi() { System.out.println("Hi, UserComponent~"); } }
@Configuration
@Configuration public class UserConfiguration { public void sayHi() { System.out.println("Hi,UserConfiguration~"); } }
那么既然這么多注解干的都是同一件事,為什么還非要分出這么多注解呢?
類注解之間的區(qū)別
這個也是和咱們前?講的應(yīng)?分層是呼應(yīng)的,不同的注解標(biāo)識著不同的信息,這些注解可以讓程序員看到類注解之后,就能直接了解當(dāng)前類的?途。
- @Controller:控制層, 接收請求, 對請求進(jìn)?處理, 并進(jìn)?響應(yīng)
- @Servie:業(yè)務(wù)邏輯層, 處理具體的業(yè)務(wù)邏輯
- @Repository:數(shù)據(jù)訪問層,也稱為持久層. 負(fù)責(zé)數(shù)據(jù)訪問操作
- @Configuration:配置層. 處理項?中的?些配置信息
這和每個省/市都有??的?牌號是?樣的。?牌號都是唯?的,標(biāo)識?個?輛的。但是為什么還需要設(shè)置不同的?牌開頭呢??如陜西的?牌號就是:陜X:XXXXXX,北京的?牌號:京X:XXXXXX,甚??個省不同的縣區(qū)也是不同的,?如西安就是,陜A:XXXXX,咸陽:陜B:XXXXXX,寶雞,陜C:XXXXXX,?樣。
這樣做的好處除了可以節(jié)約號碼之外,更重要的作?是可以直觀的標(biāo)識?輛?的歸屬地.
對于一般的開發(fā)我們通過不同的注解去確認(rèn)它是哪一層的代碼,這樣更方面上下層進(jìn)行調(diào)用
類注解之間的聯(lián)系
細(xì)心的朋友可能發(fā)現(xiàn)了,上述的注解中少了一個@Component注解,我們不妨打開每個注解的源碼看看。
我們會發(fā)現(xiàn)上述4個注解中都有@Component注解,這說明它們本?就是屬于 @Component 的 "?類"。@Component 是?個元注解,也就是說可以注解其他類注解,如 @Controller、@Service 、@Repository 等, 這些注解被稱為 @Component 的衍?注解。
@Controller、@Service 和 @Repository ?于更具體的?例(分別在控制層, 業(yè)務(wù)邏輯層, 持久化層),在開發(fā)過程中,如果你要在業(yè)務(wù)邏輯層使? @Component 或 @Service,顯然@Service是更好的選擇。
諸如@Configuration,見名知意就是用來標(biāo)記配置相關(guān)的,比如我們想用Redis的數(shù)據(jù)庫,在使用Redis之前需要對Redis數(shù)據(jù)庫的密碼或者庫做一些配置,配置好了之后我們需要將這個配置類交給Spring管理方便我們在別的地方直接訪問Redis數(shù)據(jù)庫,這時使用@Configuration就是很好的選擇。
?如杯?有喝?杯、刷?杯等,但是我們更傾向于在?常喝?時使??杯,洗漱時使?刷?杯。
方法注解@Bean
類注解是添加到某個類上的, 但是存在兩個問題
- 使?外部包?的類, 沒辦法添加類注解
- ?個類, 需要多個對象, ?如多個數(shù)據(jù)源
比如我們想通過引入第三方的工具類去操作數(shù)據(jù)庫,我們想將這個類交給Spring管理方便我們進(jìn)行二次開發(fā),但是由于第三方包只能讀取不能寫入的情況,就會陷入進(jìn)退倆難的情況。
@Bean 同上述類注解的功能一樣,都是將標(biāo)記的對象交給Spring進(jìn)行管理,但在 Spring 框架的設(shè)計中,?法注解 @Bean 要配合類注解才能將對象正常的存儲到 Spring 容器中,如下代碼所?:
@Component public class BeanConfig { @Bean public User user(){ User user = new User(); user.setName("zhangsan"); user.setAge(18); return user; } }
我們也可以通過設(shè)置name屬性給Bean對象進(jìn)行重命名
@Bean(name = {"u1","user1"})
二.DI——Dependency Injection(依賴注入)
依賴注?是?個過程,是指IoC容器在創(chuàng)建Bean時, 去提供運?時所依賴的資源,?資源指的就是對象,在之前程序案例中,我們使?了 @Autowired 這個注解,完成了依賴注?的操作。
關(guān)于依賴注?,Spring也給我們提供了三種?式:
- 屬性注?(Field Injection)
- 構(gòu)造?法注?(Constructor Injection)
- Setter 注?(Setter Injection)
屬性注入
屬性注?是使? @Autowired 實現(xiàn)的,將 Service 類注?到 Controller 類中。
Service 類的實現(xiàn)代碼如下:
import org.springframework.stereotype.Service; @Service public class UserService { public void sayHi() { System.out.println("Hi,UserService"); } }
Controller 類的實現(xiàn)代碼如下:
@Controller public class UserController { //注??法1: 屬性注? @Autowired private UserService userService; public void sayHi() { System.out.println("hi,UserController..."); userService.sayHi(); } }
構(gòu)造方法注入
構(gòu)造?法注?是在類的構(gòu)造?法中實現(xiàn)注?,如下代碼所?:
@Controller public class UserController2 { //注??法2: 構(gòu)造?法 private UserService userService; @Autowired public UserController2(UserService userService) { this.userService = userService; } public void sayHi(){ System.out.println("hi,UserController2..."); userService.sayHi(); } }
如果類只有?個構(gòu)造?法,那么 @Autowired 注解可以省略;如果類中有多個構(gòu)造?法,那么需要添加上 @Autowired 來明確指定到底使?哪個構(gòu)造?法。
Setter注入
Setter 注?和屬性的 Setter ?法實現(xiàn)類似,只不過在設(shè)置 set ?法的時候需要加上 @Autowired 注解 ,如下代碼所?:
@Controller public class UserController3 { //注??法3: Setter?法注? private UserService userService; @Autowired public void setUserService(UserService userService) { this.userService = userService; } public void sayHi(){ System.out.println("hi,UserController3..."); userService.sayHi(); } }
三種注入的優(yōu)缺點
屬性注?
優(yōu)點:
- 簡潔,使??便
缺點:
- 只能?于 IoC 容器,如果是? IoC 容器不可?,并且只有在使?的時候才會出現(xiàn) NPE(空指針異常)
- 不能注??個Final修飾的屬性
構(gòu)造函數(shù)注入(Spring 4.X推薦)
優(yōu)點:
- 可以注?final修飾的屬性
- 注?的對象不會被修改
- 依賴對象在使?前?定會被完全初始化,因為依賴是在類的構(gòu)造?法中執(zhí)?的,?構(gòu)造?法是在類加載階段就會執(zhí)?的?法.
- 通?性好, 構(gòu)造?法是JDK?持的, 所以更換任何框架,他都是適?的
缺點:
- 注?多個對象時, 代碼會?較繁瑣
Setter注入(Spring 3.X推薦)
優(yōu)點:
- ?便在類實例之后, 重新對該對象進(jìn)?配置或者注?
缺點:
- 不能注??個Final修飾的屬性
- 注?對象可能會被改變, 因為setter?法可能會被多次調(diào)?, 就有被修改的?險
@Autowired存在問題
當(dāng)同?類型存在多個bean時, 使?@Autowired會存在問題
@Component public class BeanConfig { @Bean("u1") public User user1(){ User user = new User(); user.setName("zhangsan"); user.setAge(18); return user; } @Bean public User user2() { User user = new User(); user.setName("lisi"); user.setAge(19); return user; } }
@Controller public class UserController { @Autowired private UserService userService; //注?user @Autowired private User user; public void sayHi(){ System.out.println("hi,UserController..."); userService.sayHi(); System.out.println(user); } }
程序會出現(xiàn)無法正確找到Bean的報錯
如何解決上述問題呢?Spring提供了以下?種解決?案:
- @Primary
- @Qualifier
- @Resource
使?@Primary注解:當(dāng)存在多個相同類型的Bean注?時,加上@Primary注解,來確定默認(rèn)的實現(xiàn)
@Component public class BeanConfig { @Primary //指定該bean為默認(rèn)bean的實現(xiàn) @Bean("u1") public User user1(){ User user = new User(); user.setName("zhangsan"); user.setAge(18); return user; } @Bean public User user2() { User user = new User(); user.setName("lisi"); user.setAge(19); return user; } }
使?@Qualifier注解:指定當(dāng)前要注?的bean對象。 在@Qualifier的value屬性中,指定注?的bean的名稱(@Qualifier注解不能單獨使?,必須配合@Autowired使?)
@Controller public class UserController { @Qualifier("user2") //指定bean名稱 @Autowired private User user; public void sayHi(){ System.out.println("hi,UserController..."); System.out.println(user); } }
使?@Resource注解:是按照bean的名稱進(jìn)?注?。通過name屬性指定要注?的bean的名稱。并且由于@Resource是由JDK提供的,因此在其他框架下也有可延展性。
@Controller public class UserController { @Resource(name = "user2") private User user; public void sayHi(){ System.out.println("hi,UserController..."); System.out.println(user); } }
@Autowired 是spring框架提供的注解,?@Resource是JDK提供的注解
@Autowired 默認(rèn)是按照類型注?,?@Resource是按照名稱注?。相?于 @Autowired 來說 @Resource ?持更多的參數(shù)設(shè)置,例如 name 設(shè)置,根據(jù)名稱獲取 Bean
以上就是Spring實現(xiàn)IoC和DI的方法詳解的詳細(xì)內(nèi)容,更多關(guān)于Spring實現(xiàn)IoC和DI的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JavaWeb中請求轉(zhuǎn)發(fā)和請求重定向的區(qū)別以及使用
今天帶大家學(xué)習(xí)JavaWeb的相關(guān)知識,文章圍繞著JavaWeb中請求轉(zhuǎn)發(fā)和請求重定向的區(qū)別以及使用展開,文中有非常詳細(xì)的介紹,需要的朋友可以參考下2021-06-06Spring 事務(wù)事件監(jiān)控及實現(xiàn)原理解析
本文首先會使用實例進(jìn)行講解Spring事務(wù)事件是如何使用的,然后會講解這種使用方式的實現(xiàn)原理。感興趣的朋友跟隨小編一起看看吧2018-09-09SpringBoot的@Value注解如何設(shè)置默認(rèn)值
這篇文章主要介紹了SpringBoot的@Value注解如何設(shè)置默認(rèn)值問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-02-02idea配置全局變量Jdk、maven倉庫以及maven詳解(全文圖解)
這篇文章主要給大家介紹了關(guān)于idea配置全局變量Jdk、maven倉庫以及maven的相關(guān)資料,在配置JDK和Maven之前,需要確保已經(jīng)正確安裝了JDK和Maven,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下2024-01-01Java調(diào)用python代碼的五種方式總結(jié)
這篇文章主要給大家介紹了關(guān)于Java調(diào)用python代碼的五種方式,在Java中調(diào)用Python函數(shù)的方法有很多種,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-09-09SpringBoot中使用Servlet三大組件的方法(Servlet、Filter、Listener)
這篇文章主要介紹了SpringBoot中使用Servlet三大組件的方法(Servlet、Filter、Listener),本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-01-01