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