有贊可以做獨(dú)立網(wǎng)站嗎seo網(wǎng)絡(luò)排名優(yōu)化技巧
Spring IOC 控制反轉(zhuǎn)
文章目錄
- Spring IOC 控制反轉(zhuǎn)
- 一、前言
- 什么是控制反轉(zhuǎn)(IOC)
- 什么是依賴注入(DI)
- 二、介紹 IOC
- 2.1 傳統(tǒng)思想代碼
- 2.2 解決方案
- 2.3 IOC思想代碼
- 2.4 IOC 使用(@Autowired依賴注入)
- 2.5 IOC 優(yōu)勢(shì)
- 三、IOC詳解
- 3.1 從Spring容器中獲取對(duì)象:Spring上下文
- 3.2 Bean的存儲(chǔ)
- 3.2.1 為什么要這么多類注解?
- 3.3 方法注解@Bean
- 3.3.1 @Bean方法注解一定要配合類注解使用
- 3.3.2 @Bean方法定義多個(gè)對(duì)象
- 3.3.3 重命名@Bean
- 3.4 掃描路徑
- 四、三種依賴注入方式(DI)
- 4.1 屬性注入
- 4.1.1 @Autowired存在問題
- 4.2 構(gòu)造方法注入
- 4.3 Setter注入
- 五、常見面試題
- 5.1 常見面試題:@Autowired與@Resource的區(qū)別
- 5.2 常見面試題:三種注入方式的優(yōu)缺點(diǎn)
- 六、附加總結(jié)
一、前言
Spring 框架中的核心概念之一就是控制反轉(zhuǎn)(Inversion of Control,IoC)。
IOC就是一種思想,而依賴注入(Dependency Injection, DI) 是控制反轉(zhuǎn)的一種實(shí)現(xiàn)方式。
Spring本身是一個(gè)容器,存的是對(duì)象。對(duì)象這個(gè)詞,在 Spring的范圍內(nèi),稱之為 Bean。
什么是控制反轉(zhuǎn)(IOC)
控制反轉(zhuǎn)(Inversion of Control,IoC)是一種設(shè)計(jì)原則,它將對(duì)象的創(chuàng)建和依賴關(guān)系的管理從程序代碼中解耦出來,交由框架或容器進(jìn)行處理。傳統(tǒng)的編程方式中,應(yīng)用程序代碼主動(dòng)創(chuàng)建和管理對(duì)象,而通過IoC,框架或容器負(fù)責(zé)對(duì)象的創(chuàng)建和管理,應(yīng)用程序代碼只需要聲明依賴關(guān)系。轉(zhuǎn)換對(duì)象控制權(quán),讓Spring幫我們管理或創(chuàng)建 bean。
什么是依賴注入(DI)
依賴注入(Dependency Injection,DI)是一種設(shè)計(jì)模式,它將對(duì)象所依賴的其他對(duì)象的創(chuàng)建和管理職責(zé)從對(duì)象自身剝離出來,通過外部容器(如Spring IoC容器)將所需的依賴對(duì)象注入到目標(biāo)對(duì)象中,從而實(shí)現(xiàn)對(duì)象之間的解耦和提高代碼的可維護(hù)性和可測(cè)試性。
二、介紹 IOC
下面將通過案例來分析什么是IOC
需求:造一輛車
2.1 傳統(tǒng)思想代碼
public class NewCarExample {public static void main(String[] args) {Car car = new Car(20);car.run();}/*** 汽車對(duì)象*/static class Car {private Framework framework;public Car(int size) {framework = new Framework(size);System.out.println("Car init....");}public void run(){System.out.println("Car run...");}}/***車身類*/static class Framework {private Bottom bottom;public Framework(int size) {bottom = new Bottom(size);System.out.println("Framework init...");}}/*** 底盤類*/static class Bottom {private Tire tire;public Bottom(int size) {this.tire = new Tire(size);System.out.println("Bottom init...");}}/*** 輪胎類*/static class Tire {// 尺寸private int size;public Tire(int size){this.size = size;System.out.println("輪胎尺寸:" + size);}}
}
從上述代碼中可以看到,以上程序的問題是代碼耦合性過高,導(dǎo)致修改底層代碼后,需要調(diào)整整體的代碼。
2.2 解決方案
利用IOC思想,控制反轉(zhuǎn)。
具體操作是將原來由我們自己創(chuàng)建的下極類,改為傳遞的方式(也就是注入的方式)。
因?yàn)槲覀儾恍枰诋?dāng)前類中創(chuàng)建下極類了,所以下極類及時(shí)發(fā)生變化,當(dāng)前類本身也無需修改任何代碼,這樣就完成了解耦合。
2.3 IOC思想代碼
public class IocCarExample {public static void main(String[] args) {Tire tire = new Tire(20);Bottom bottom = new Bottom(tire);Framework framework = new Framework(bottom);Car car = new Car(framework);car.run();}static class Car {private Framework framework;public Car(Framework framework) {this.framework = framework;System.out.println("Car init....");}public void run() {System.out.println("Car run...");}}static class Framework {private Bottom bottom;public Framework(Bottom bottom) {this.bottom = bottom;System.out.println("Framework init...");}}static class Bottom {private Tire tire;public Bottom(Tire tire) {this.tire = tire;System.out.println("Bottom init...");}}static class Tire {private int size;public Tire(int size) {this.size = size;System.out.println("輪胎尺寸:" + size);}}
}
2.4 IOC 使用(@Autowired依賴注入)
Spring 作為一個(gè)IOC容器幫我們管理對(duì)象,其主要功能就是 存 和 取 。
存:存的是對(duì)象bean,可以使用 @Component或者其他注解(下文中會(huì)講到)
取:告訴 Spring ,從容器中取出這個(gè)對(duì)象,賦值給當(dāng)前對(duì)象的屬性。也就是依賴注入 使用注解 @Autowired
2.5 IOC 優(yōu)勢(shì)
傳統(tǒng)開發(fā)中,對(duì)象創(chuàng)建的順序是:Car -> Framework -> Bottom -> Tire
改進(jìn)之后解耦代碼的創(chuàng)建順序是:Tire -> Bottom -> Framework -> Car
我們發(fā)現(xiàn)了一個(gè)規(guī)律,通用程序的實(shí)現(xiàn)代碼,類的創(chuàng)建順序是反的,傳統(tǒng)代碼是 Car 控制并創(chuàng)建了Framework,Framework 創(chuàng)建并創(chuàng)建了 Bottom,依次往下,而改進(jìn)之后的控制權(quán)發(fā)生的反轉(zhuǎn),不再是使用方對(duì)象創(chuàng)建并控制依賴對(duì)象了 ,而是把依賴對(duì)象注入將當(dāng)前對(duì)象中,依賴對(duì)象的控制權(quán)不再由當(dāng)前類控制了。
這樣的話,即使依賴類發(fā)生任何改變,當(dāng)前類都是不受影響的,這就是典型的控制反轉(zhuǎn),也就是 IoC 的實(shí)現(xiàn)思想。
上述改進(jìn)后的程序main中的代碼就是IOC容器需要存儲(chǔ)的數(shù)據(jù)。
從上面也可以看出來, IoC容器具備以下優(yōu)點(diǎn):
資源不由使用資源的雙方管理,而由不使用資源的第三方管理。
- 資源的集中管理:IOC會(huì)幫我們管理一些資源(對(duì)象等),需要的時(shí)候,直接去IOC中取即可。
- 在創(chuàng)建實(shí)例的時(shí)候不需要了解其中的細(xì)節(jié),降低了使用資源雙方的依賴程度,降低耦合度。
三、IOC詳解
前面提到的IOC控制反轉(zhuǎn),就是將對(duì)象的控制權(quán)交給Spring的IOC容器,由IOC容器創(chuàng)建及管理對(duì)象。也就是存儲(chǔ)bean。
3.1 從Spring容器中獲取對(duì)象:Spring上下文
在學(xué)習(xí)如何存儲(chǔ)對(duì)象之前,先來看如何從Spring容器中獲取對(duì)象?
@SpringBootApplication
public class DemoApplication {public static void main(String[] args) {//String上下文ApplicationContext context = SpringApplication.run(DemoApplication.class, args);//獲取到這個(gè)類的對(duì)象context.getBean(Class<T> aClass);//根據(jù)bean名稱獲取beancontext.getBean(String s);}
}
獲取Bean的三種常用方法
通過Bean名稱來獲取Bean,如果沒有顯式的提供名稱(BeanId),Spring容器將為該bean生成唯一的名稱。
Bean的命名約定:查看官方文檔
其大致意思是,bean名稱以小寫字母開頭,然后使用駝峰式大小寫
比如:
類名:UserController,Bean的名稱為:userController;
類名:AccountManager,Bean的名稱為:accountManage;
也有特殊情況:
比如 :
類名:UController,Bean的名稱為:UController;
類名:AController,Bean的名稱為:AController;
@SpringBootApplication
public class DemoApplication {public static void main(String[] args) {//上下文ApplicationContext context = SpringApplication.run(DemoApplication.class, args);//通過類名獲取beanUserController usercontroller1 = context.getBean(UserContorller.class);//通過Bean名獲取beanUserController usercontroller2 = context.getBean(userController);system.out.println(usercontroller1);system.out.println(usercontroller2);}
}
3.2 Bean的存儲(chǔ)
在Spring中,要把某個(gè)對(duì)象交給IOC容器管理,需要在類上添加注解,下文中就會(huì)講到Spring框架為服務(wù)web應(yīng)用程序,提供了豐富的注解。
共有兩類注解類型可以實(shí)現(xiàn):
- 類注解:@Controller、@Service、@Repository、@Component、@Configuration
- 方法注解:@Bean
觀察下面類注解的源代碼,都是@component的衍生類,因此@Component的作用范圍更廣。
@Controller控制存儲(chǔ)器
@Service服務(wù)存儲(chǔ)
@Repository倉庫存儲(chǔ)
@Component組件存儲(chǔ)
@Configuration配置存儲(chǔ)
3.2.1 為什么要這么多類注解?
最直接的一個(gè)原因就是,可以讓程序員看到類注解之后,就能直接了解當(dāng)前類的用途。
- @Controller:控制層, 接收請(qǐng)求, 對(duì)請(qǐng)求進(jìn)行處理, 并進(jìn)行響應(yīng).
- @Servie:業(yè)務(wù)邏輯層, 處理具體的業(yè)務(wù)邏輯.
- @Repository:數(shù)據(jù)訪問層,也稱為持久層. 負(fù)責(zé)數(shù)據(jù)訪問操作
- @Configuration:配置層. 處理項(xiàng)目中的一些配置信息
3.3 方法注解@Bean
五大注解只能加在類上,并且只能加在自己的代碼上,如果我引入了一個(gè)第三方j(luò)ar包,也希望交給Spring管理,是沒有辦法加五大注解。比如說:數(shù)據(jù)庫操作,定義多個(gè)數(shù)據(jù)源
。
@Bean方法一定要配合類注解使用
使用@Bean注解時(shí),bean的名稱是方法名。
3.3.1 @Bean方法注解一定要配合類注解使用
在 Spring 框架的設(shè)計(jì)中,方法注解 要配合類注解才能將對(duì)象正常的存儲(chǔ)到 Spring 容器中,下代碼所示:
@Component
public class BeanConfig {@Beanpublic User user(){User user = new User();user.setName("zhangsan");user.setAge(18);return user;}
}
3.3.2 @Bean方法定義多個(gè)對(duì)象
對(duì)于同一個(gè)類,如何定義多個(gè)對(duì)象呢?
比如說,多數(shù)據(jù)源的場(chǎng)景,類是同一個(gè),但是配置不同,指向的數(shù)據(jù)源也不同。
@Component
public class BeanConfig {@Beanpublic User user1(){User user = new User();user.setName("zhangsan");user.setAge(18);return user;}@Beanpublic User user2(){User user = new User();user.setName("lisi");user.setAge(19);return user;}
}
此時(shí),如果通過類型獲取對(duì)象的話,Spring就會(huì)給我們報(bào)錯(cuò),因?yàn)橛袃蓚€(gè)對(duì)象,Spring不知道取哪個(gè)。接下來根據(jù)名稱來獲取bean對(duì)象。
@SpringBootApplication
public class DemoApplication {public static void main(String[] args) {//獲取Spring上下文對(duì)象ApplicationContext context = SpringApplication.run(DemoApplication.class, args);//根據(jù)bean名稱, 從Spring上下文中獲取對(duì)象// User user1 = (User) context.getBean("user1");User user2 = (User) context.getBean("user2");System.out.println(user1);System.out.println(user2);}
}
3.3.3 重命名@Bean
@Bean(name = {"u1","user1"})
public User user1(){User user = new User();user.setName("zhangsan");user.setAge(18);return user;
}
3.4 掃描路徑
SpringBoot 特點(diǎn)就是約定大于配置。其中之一的體現(xiàn)就是掃描路徑。
默認(rèn)掃描路徑:啟動(dòng)類所在的目錄及其子孫目錄
如果更改啟動(dòng)類所在目錄,而未進(jìn)行路徑的標(biāo)注就會(huì)出現(xiàn)報(bào)錯(cuò)。
通過@ComponentScan()這個(gè)注解可以指定掃描路徑。
@ComponentScan({"com.example.demo"})
@SpringBootApplication
public class DemoApplication {public static void main(String[] args) {//獲取Spring上下文對(duì)象ApplicationContext context = SpringApplication.run(DemoApplication.class, args);//從Spring上下文中獲取對(duì)象User u1 = (User) context.getBean("u1");//使用對(duì)象System.out.println(u1);}
}
四、三種依賴注入方式(DI)
上面我們講解了控制反轉(zhuǎn)IoC的細(xì)節(jié),接下來呢,我們學(xué)習(xí)依賴注入DI的細(xì)節(jié)。依賴注入是一個(gè)過程,是指IoC容器在創(chuàng)建Bean時(shí),去提供運(yùn)行時(shí)所依賴的資源,而資源指的就是對(duì)象。在上面程序案例中,我們使用了@Autowired
這個(gè)注解,完成了依賴注入的操作。
關(guān)于依賴注入,Spring也給我們提供了三種方式:
- 屬性注入
- 構(gòu)造方法注入
- Setter注入
4.1 屬性注入
屬性注入是使用@Autowired
實(shí)現(xiàn)的。
下面是將Service類注入到Controller類中。
//Service類
import org.springframework.stereotype.Service;
@Service
public class UserService {public void sayHi() {System.out.println("Hi,UserService");}
}
@Controller
public class UserController {//注入方法1: 屬性注入@Autowiredprivate UserService userService;public void sayHi(){System.out.println("hi,UserController...");userService.sayHi();}
}
4.1.1 @Autowired存在問題
當(dāng)一個(gè)類存在多個(gè)bean時(shí),使用@Autowored會(huì)存在問題
@Component
public class BeanConfig {@Bean()public User user1() {User user = new User();user.setName("zhangsan");user.setAge(18);return user;}@Beanpublic User user2() {User user = new User();user.setName("lisi");user.setAge(19);return user;}
}
@Controller
public class UserController {@Autowiredprivate UserService11 userService;//此時(shí)注入的user,Spring不知道是user1還是user2@Autowiredprivate User user;public void sayHi() {System.out.println("hi,UserController...");userService.sayHi();System.out.println(user);}
}
運(yùn)行程序就會(huì)報(bào)錯(cuò)
報(bào)錯(cuò)原因是非唯一的Bean對(duì)象。
Spring提供了以下幾種解決方案:
- Primary
- Qualifier
- Resource
使用@Primary注解: 當(dāng)存在多個(gè)相同類型的Bean注入時(shí),加上@Primary注解,來確定默認(rèn)的實(shí)現(xiàn).
@Component
public class BeanConfig {//此時(shí)Spring默認(rèn)的就是user1()@Primary@Bean()public User user1() {User user = new User();user.setName("zhangsan");user.setAge(18);return user;}@Beanpublic User user2() {User user = new User();user.setName("lisi");user.setAge(19);return user;}
}
使用@Qualifier注解:指定當(dāng)前要注入的bean對(duì)象。 在@Qualifier的value屬性中,指定注入的bean的名稱。
@Controller
public class UserController @Autowiredprivate UserService11 userService;@Qualifier("user2") //指定bean名稱@Autowiredprivate User user;public void sayHi() {System.out.println("hi,UserController...");userService.sayHi();System.out.println(user);}
}
使用@Resource注解:是按照bean的名稱進(jìn)行注入。通過name屬性指定要注入的bean的名稱。
@Controller
public class UserController @Autowiredprivate UserService11 userService;@Resource(name = "user2")private User user;public void sayHi() {System.out.println("hi,UserController...");userService.sayHi();System.out.println(user);}
}
4.2 構(gòu)造方法注入
構(gòu)造方法注入是在類的構(gòu)造方法中實(shí)現(xiàn)注入,代碼如下:
@Controller
public class UserController2 {//注入方法2: 構(gòu)造方法private UserService userService;@Autowiredpublic UserController2(UserService userService) {this.userService = userService;}public void sayHi(){System.out.println("hi,UserController2...");userService.sayHi();}
}
4.3 Setter注入
Setter注入和屬性的Setter方法實(shí)現(xiàn)類似,只不過在設(shè)置 set 方法的時(shí)候需要加上@Autowired注解,代碼如下:
@Controller
public class UserController3 {//注入方法3: Setter方法注入private UserService us;@Autowiredpublic void setUS(UserService us) {this.us = us;}public void sayHi(){System.out.println("hi,UserController3...");userService.sayHi();}
}
五、常見面試題
5.1 常見面試題:@Autowired與@Resource的區(qū)別
@Autowired 是spring框架提供的注解,而@Resource是JDK提供的注解
@Autowired 默認(rèn)是按照類型注入,而@Resource是按照名稱注入. 相比于@Autowired 來說,@Resource 支持更多的參數(shù)設(shè)置,例如 name 設(shè)置,根據(jù)名稱獲取 Bean。
5.2 常見面試題:三種注入方式的優(yōu)缺點(diǎn)
- 屬性注入(大部分情況下使用)
- 優(yōu)點(diǎn):簡(jiǎn)潔、使用方便。
- 缺點(diǎn):
- 不能注入一個(gè)Final修飾的屬性。
- 只能用于IOC容器,如果是非IOC容器則不可用。并且在使用的時(shí)候才會(huì)出現(xiàn) NPE(空指針異常)
- 構(gòu)造函數(shù)注入(Spring 4.x推薦)
- 優(yōu)點(diǎn):
- 可以注入Final修飾的屬性。
- 注入的對(duì)象不會(huì)被修改。
- 通用性比較好,構(gòu)造方法是JDK支持的,因此更換框架也是適用的。
- 缺點(diǎn):注入多個(gè)對(duì)象時(shí),代碼比較復(fù)雜。
- 優(yōu)點(diǎn):
- Setter注入(Spring 3.x推薦)
- 優(yōu)點(diǎn):方便在類實(shí)例之后,重新對(duì)該對(duì)象進(jìn)行配置或注入。
- 缺點(diǎn):
- 不能注入一個(gè)Final修飾的對(duì)象。
- 注入對(duì)象有被修改的風(fēng)險(xiǎn)。