5. Spring IoC&DI
- 1. IoC & DI ??
- 1.1 Spring 是什么?★ (Spring 是包含了眾多?具?法的 IoC 容器)
- 1.1.1 什么是容器?
- 1.1.2 什么是 IoC?★ (IoC: Inversion of Control (控制反轉))
- 總結:把傳統模式 被依賴對象創建依賴對象 改成 交給容器創建。用的時候注入即可。
- 1.2 IoC 介紹
- 需求: 造?輛?
- 1.2.1 傳統程序開發 ★
- car-framework-bottom-tire
- 1.2.2 問題分析
- 1.2.3 解決?案
- 1.2.4 IoC程序開發 ★
- 1.2.5 IoC 優勢
- Tire -> Bottom -> Framework -> Car
- 第?,資源集中管理,實現資源的可配置和易管理。
- 第?,降低了使?資源雙?的依賴程度,也就是我們說的耦合度。
- 1.3 DI 介紹
- 2. IoC & DI 使?
- 以下是一個基于Spring Boot的IoC和DI的實戰代碼案例:
- ?標: 把BookDao, BookService 交給Spring管理, 完成Controller層, Service層, Dao層的解耦
- 3. IoC 詳解
- 3.1 Bean的存儲
- 3.1.1 @Controller(控制器存儲)
- 如何觀察這個對象已經存在Spring容器當中了呢?
- 接下來我們學習如何從Spring容器中獲取對象 ★
- ApplicationContext.getBean(手動獲取bean對象)
- XML的方式注入的獲取 ClassPathXmlApplicationContext
- 獲取bean對象的其他?式(beanFactory)
- ApplicationContext VS BeanFactory(常??試題)
- 3.1.2 @Service(服務存儲)
- 3.1.3 @Repository(倉庫存儲)
- 3.1.4 @Component(組件存儲)
- 3.1.5 @Configuration(配置存儲)
- 3.2 為什么要這么多類注解?
- 3.3 ?法注解 @Bean
- 3.3.1 ?法注解要配合類注解使?
- 3.3.2 定義多個對象
- 3.3.3 重命名 Bean
- 3.4 掃描路徑
- 4. DI 詳解
- 4.1 屬性注?
- 4.2 構造?法注?
- 4.3 Setter 注?
- 4.4 三種注?優缺點分析
- 4.5 @Autowired存在問題
- 當同?類型存在多個bean時, 使?@Autowired會存在問題
- 使?@Primary注解:當存在多個相同類型的Bean注?時,加上@Primary注解,來確定默認的實現.
- 使?@Qualifier注解:指定當前要注?的bean對象。 在@Qualifier的value屬性中,指定注?的bean的名稱。
- 使?@Resource注解:是按照bean的名稱進?注?。通過name屬性指定要注?的bean的名稱。
- @Autowird 與 @Resource的區別
- 5. 總結
- Spring, Spring Boot 和Spring MVC的關系以及區別
本節?標
- 了解Spring,Spring MVC, Spring Boot 之間的聯系及區別
- 掌握IoC&DI的概念以及寫法
1. IoC & DI ??
在前?的章節中, 我們學習了Spring Boot和Spring MVC的開發, 可以完成?些基本功能的開發了, 但是什么是Spring呢? Spring, Spring Boot 和SpringMVC?有什么關系呢? 咱們還是帶著問題去學習.
我們先看什么是Spring
1.1 Spring 是什么?★ (Spring 是包含了眾多?具?法的 IoC 容器)
通過前?的學習, 我們知道了Spring是?個開源框架, 他讓我們的開發更加簡單. 他?持?泛的應?場景, 有著活躍?龐?的社區, 這也是Spring能夠?久不衰的原因.但是這個概念相對來說, 還是?較抽象.
我們??句更具體的話來概括Spring, 那就是: Spring 是包含了眾多?具?法的 IoC 容器
那問題來了,什么是容器?什么是 IoC 容器?接下來我們?起來看
1.1.1 什么是容器?
容器是?來容納某種物品的(基本)裝置。?來?:百度百科
?活中的?杯, 垃圾桶, 冰箱等等這些都是容器.
我們想想,之前課程我們接觸的容器有哪些?
- List/Map -> 數據存儲容器
- Tomcat -> Web 容器
1.1.2 什么是 IoC?★ (IoC: Inversion of Control (控制反轉))
IoC 是Spring的核?思想, 也是常?的?試題, 那什么是IoC呢?
其實IoC我們在前?已經使?了, 我們在前?講到, 在類上?添加 @RestController 和 @Controller 注解, 就是把這個對象交給Spring管理, Spring 框架啟動時就會加載該類. 把對象交給Spring管理, 就是IoC思想
IoC: Inversion of Control (控制反轉), 也就是說 Spring 是?個"控制反轉"的容器.
什么是控制反轉呢? 也就是控制權反轉. 什么的控制權發?了反轉? 獲得依賴對象的過程被反轉了也就是說, 當需要某個對象時, 傳統開發模式中需要??通過 new 創建對象, 現在不需要再進?創建, 把創建對象的任務交給容器, 程序中只需要依賴注? (Dependency Injection,DI)就可以了.
這個容器稱為:IoC容器. Spring是?個IoC容器, 所以有時Spring 也稱為Spring 容器.
總結:把傳統模式 被依賴對象創建依賴對象 改成 交給容器創建。用的時候注入即可。
1.2 IoC 介紹
接下來我們通過案例來了解?下什么是IoC
需求: 造?輛?
1.2.1 傳統程序開發 ★
我們的實現思路是這樣的:
先設計輪?(Tire),然后根據輪?的??設計底盤(Bottom),接著根據底盤設計??(Framework),最后根據??設計好整個汽?(Car)。這?就出現了?個"依賴"關系:汽?依賴??,??依賴底盤,底盤依賴輪?
car-framework-bottom-tire
最終程序的實現代碼如下
public class NewCarExample {public static void main(String[] args) {ar ar = ew ar );car.run();}/*** 汽?對象*/static class Car {private Framework framework;public Car() {framework = new Framework();System.out.println("Car init....");}public void run(){System.out.println("Car run...");}}/*** ??類*/static class Framework {private Bottom bottom;public Framework() {bottom = new Bottom();System.out.println("Framework init...");}}/*** 底盤類*/static class Bottom {private Tire tire;public Bottom() {this.tire = new Tire();System.out.println("Bottom init...");}}/*** 輪胎類*/static class Tire {// 尺?private int size;public Tire(){this.size = 17;System.out.println("輪胎尺?:" + size);}}
}
1.2.2 問題分析
這樣的設計看起來沒問題,但是可維護性卻很低.
接下來需求有了變更: 隨著對的?的需求量越來越?, 個性化需求也會越來越多,我們需要加?多種尺?的輪胎.
那這個時候就要對上?的程序進?修改了,修改后的代碼如下所?:
修改之后, 其他調?程序也會報錯, 我們需要繼續修改
從以上代碼可以看出,以上程序的問題是:當最底層代碼改動之后,整個調?鏈上的所有代碼都需要修改.
程序的耦合度?常?(修改?處代碼, 影響其他處的代碼修改)
1.2.3 解決?案
在上?的程序中, 我們是根據輪?的尺?設計的底盤,輪?的尺??改,底盤的設計就得修改. 同樣因為我們是根據底盤設計的??,那么??也得改,同理汽?設計也得改, 也就是整個設計?乎都得改
我們嘗試換?種思路, 我們先設計汽?的?概樣?,然后根據汽?的樣?來設計??,根據??來設計底盤,最后根據底盤來設計輪?. 這時候,依賴關系就倒置過來了:輪?依賴底盤, 底盤依賴??,??依賴汽?
這就類似我們打造?輛完整的汽?, 如果所有的配件都是??造,那么當客?需求發?改變的時候,?如輪胎的尺?不再是原來的尺?了,那我們要??動?來改了,但如果我們是把輪胎外包出去,那么即使是輪胎的尺?發?變變了,我們只需要向代理??下訂單就?了,我們??是不需要出?的.
如何來實現呢:
我們可以嘗試不在每個類中??創建下級類,如果??創建下級類就會出現當下級類發?改變操作,??也要跟著修改.
此時,我們只需要將原來由??創建的下級類,改為傳遞的?式(也就是注?的?式),因為我們不需要在當前類中創建下級類了,所以下級類即使發?變化(創建或減少參數),當前類本?也?需修改任何代碼,這樣就完成了程序的解耦.
1.2.4 IoC程序開發 ★
基于以上思路,我們把調?汽?的程序?例改造?下,把創建?類的?式,改為注?傳遞的?式.
具體實現代碼如下:
public class Main {public static void main(String[] args) {Tire tire = new Tire(19,"red");Bottom bottom = new Bottom(tire);Framework framework = new Framework(bottom);Car car = new Car(framework);car.run();}
}
public class Bottom {private Tire tire;public Bottom(Tire tire) {this.tire = tire;System.out.println("bottom init...");}}
public 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...");}
}
public class Framework {private Bottom bottom;public Framework(Bottom bottom) {this.bottom = bottom;System.out.println("framework init...");}
}
public class Tire {private int size;private String color;public Tire(int size,String color) {System.out.println("tire size:"+size+",color:"+color);}
}
代碼經過以上調整,?論底層類如何變化,整個調?鏈是不?做任何改變的,這樣就完成了代碼之間的解耦,從?實現了更加靈活、通?的程序設計了。
1.2.5 IoC 優勢
Tire -> Bottom -> Framework -> Car
在傳統的代碼中對象創建順序是:Car -> Framework -> Bottom -> Tire
改進之后解耦的代碼的對象創建順序是:Tire -> Bottom -> Framework -> Car
我們發現了?個規律,通?程序的實現代碼,類的創建順序是反的,傳統代碼是 Car 控制并創建了Framework,Framework 創建并創建了 Bottom,依次往下,?改進之后的控制權發?的反轉,不再是使??對象創建并控制依賴對象了,?是把依賴對象注?將當前對象中,依賴對象的控制權不再由當前類控制了.
這樣的話, 即使依賴類發?任何改變,當前類都是不受影響的,這就是典型的控制反轉,也就是 IoC 的實現思想。
學到這?, 我們?概就知道了什么是控制反轉了, 那什么是控制反轉容器呢, 也就是IoC容器
這部分代碼, 就是IoC容器做的?作.
從上?也可以看出來, IoC容器具備以下優點:
資源不由使?資源的雙?管理,?由不使?資源的第三?管理,這可以帶來很多好處。
第?,資源集中管理,實現資源的可配置和易管理。
第?,降低了使?資源雙?的依賴程度,也就是我們說的耦合度。
- 資源集中管理: IoC容器會幫我們管理?些資源(對象等), 我們需要使?時, 只需要從IoC容器中去取就可以了
- 我們在創建實例的時候不需要了解其中的細節, 降低了使?資源雙?的依賴程度, 也就是耦合度.Spring 就是?種IoC容器, 幫助我們來做了這些資源管理.
1.3 DI 介紹
上?學習了IoC, 什么是DI呢?
DI: Dependency Injection(依賴注?)
容器在運?期間, 動態的為應?程序提供運?時所依賴的資源,稱之為依賴注?。
程序運?時需要某個資源,此時容器就為其提供這個資源
從這點來看, 依賴注?(DI)和控制反轉(IoC)是從不同的?度的描述的同?件事情,就是指通過引? IoC 容器,利?依賴關系注?的?式,實現對象之間的解耦。
上述代碼中, 是通過構造函數的?式, 把依賴對象注?到需要使?的對象中的
IoC 是?種思想,也是"?標", ?思想只是?種指導原則,最終還是要有可?的落地?案,? DI 就屬于具體的實現。所以也可以說, DI 是IoC的?種實現.
?如說我今天?情?較好,吃?頓好的犒勞犒勞??,那么"吃?頓好的"是思想和?標(是IoC),但最后我是吃海底撈還是楊國福?這就是具體的實現,就是 DI。
2. IoC & DI 使?
對IoC和DI有了初步的了解, 我們接下來具體學習Spring IoC和DI的代碼實現.
依然是先使?, 再學習
既然 Spring 是?個 IoC(控制反轉)容器,作為容器, 那么它就具備兩個最基礎的功能:
? 存
? 取
Spring 容器 管理的主要是對象, 這些對象, 我們稱之為"Bean". 我們把這些對象交由Spring管理, 由Spring來負責對象的創建和銷毀. 我們程序只需要告訴Spring, 哪些需要存, 以及如何從Spring中取出對象
當涉及到Spring Boot的IoC(控制反轉)和DI(依賴注入)時,一個常見的實戰案例是創建一個簡單的RESTful API。
以下是一個基于Spring Boot的IoC和DI的實戰代碼案例:
首先,你需要創建一個簡單的Maven項目,并添加Spring Boot的依賴。然后創建一個Controller類來處理RESTful請求,并創建一個Service類來處理業務邏輯。最后,通過依賴注入將Service類注入到Controller類中。
// Service類
@Service
public class UserService {public String getUserInfo() {return "User information";}
}// Controller類
@RestController
public class UserController {private final UserService userService;@Autowiredpublic UserController(UserService userService) {this.userService = userService;}@GetMapping("/user")public String getUser() {return userService.getUserInfo();}
}// Spring Boot應用入口類
@SpringBootApplication
public class DemoApplication {public static void main(String[] args) {SpringApplication.run(DemoApplication.class, args);}
}
在這個案例中,UserService類使用了@Service注解來告訴Spring它是一個Bean,而UserController類使用了@Autowired注解來告訴Spring需要將UserService注入到它的構造函數中。當Spring Boot應用啟動時,它會自動掃描并創建這些Bean,并且處理它們之間的依賴關系。
通過這個案例,你可以看到IoC和DI是如何在Spring Boot應用中發揮作用的。當你發送GET請求到/user
時,UserController會調用UserService來獲取用戶信息,并返回給客戶端。這展示了IoC和DI如何幫助我們編寫松耦合、可測試的代碼。
?標: 把BookDao, BookService 交給Spring管理, 完成Controller層, Service層, Dao層的解耦
步驟:
- Service層及Dao層的實現類,交給Spring管理: 使?注解: @Component
- 在Controller層 和Service層 注?運?時依賴的對象: 使?注解 @Autowired實現:
- 把BookDao 交給Spring管理, 由Spring來管理對象
3. IoC 詳解
通過上?的案例, 我們已經知道了Spring IoC 和DI的基本操作, 接下來我們來系統的學習Spring IoC和DI的操作.
前?我們提到IoC控制反轉,就是將對象的控制權交給Spring的IOC容器,由IOC容器創建及管理對象。也就是bean的存儲
3.1 Bean的存儲
在之前的??案例中,要把某個對象交給IOC容器管理,需要在類上添加?個注解: @Component ?Spring框架為了更好的服務web應?程序, 提供了更豐富的注解.
共有兩類注解類型可以實現:
- 類注解:@Controller、@Service、@Repository@Component、@Configuration.
- ?法注解:@Bean.
接下來我們分別來看
3.1.1 @Controller(控制器存儲)
使? @Controller 存儲 bean 的代碼如下所?:
@Controller // 將對象存儲到 Spring 中
public class UserController {public void sayHi(){System.out.println("hi,UserController...");}
}
如何觀察這個對象已經存在Spring容器當中了呢?
接下來我們學習如何從Spring容器中獲取對象 ★
在Spring框架中,你可以使用注解來實現對象的注入和獲取。首先,你需要在你的類中使用@Component
或者其他相關的注解來標識這個類是一個Spring容器管理的Bean。然后,你可以使用@Autowired
注解來實現對象的注入,或者使用@Resource
注解來指定注入的對象。
舉個例子,假設你有一個名為UserService
的類,你可以這樣標識它是一個Bean:
@Component
public class UserService {// ...
}
然后,在另一個類中,你可以使用@Autowired
注解來注入UserService
:
@Component
public class UserController {@Autowiredprivate UserService userService;// ...
}
這樣,Spring容器會在啟動時自動將UserService
注入到UserController
中。
如果你想手動從Spring容器中獲取對象,你可以使用@Autowired
或者@Resource
注解來注入ApplicationContext
,然后通過getBean
方法來獲取對象。示例代碼如下:
ApplicationContext.getBean(手動獲取bean對象)
@Component
public class SomeOtherClass {@Autowiredprivate ApplicationContext context;public void doSomething() {UserService userService = context.getBean(UserService.class);// 使用userService對象進行操作}
}
這樣,你就可以通過注解的方式實現對象的注入和獲取。希望這能幫到你!
XML的方式注入的獲取 ClassPathXmlApplicationContext
在Spring容器中獲取對象通常需要使用ApplicationContext接口。以下是一個簡單的示例代碼,演示如何從Spring容器中獲取對象:
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class Main {public static void main(String[] args) {// 加載Spring配置文件ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");// 從容器中獲取對象YourObject yourObject = (YourObject) context.getBean("yourObjectBeanName");// 使用獲取到的對象yourObject.doSomething();}
}
在上面的示例中,假設你有一個名為"YourObject"的類,并且在Spring配置文件"applicationContext.xml"中定義了該類的bean。通過調用context.getBean("yourObjectBeanName")
方法,你可以從Spring容器中獲取到該對象的實例。
獲取bean對象的其他?式(beanFactory)
上述代碼是根據類型來查找對象, 如果Spring容器中, 同?個類型存在多個bean的話, 怎么來獲取呢?
ApplicationContext 也提供了其他獲取bean的?式, ApplicationContext 獲取bean對象的功能, 是?類BeanFactory提供的功能
public interface BeanFactory {//以上省略...// 1. 根據bean名稱獲取beanObject getBean(String var1) throws BeansException;// 2. 根據bean名稱和類型獲取bean<T> T getBean(String var1, Class<T> var2) throws BeansException;// 3. 按bean名稱和構造函數參數動態創建bean,只適?于具有原型(prototype)作?域的beanObject getBean(String var1, Object... var2) throws BeansException;// 4. 根據類型獲取bean<T> T getBean(Class<T> var1) throws BeansException;// 5. 按bean類型和構造函數參數動態創建bean, 只適?于具有原型(prototype)作?域的bean<T> T getBean(Class<T> var1, Object... var2) throws BeansException;//以下省略...
}
常?的是上述1,2,4種, 這三種?式,獲取到的bean是?樣的
其中1,2種都涉及到根據名稱來獲取對象.
bean的名稱是什么呢?
Spring bean是Spring框架在運?時管理的對象, Spring會給管理的對象起?個名字.
?如學校管理學?, 會給每個學?分配?個學號, 根據學號, 就可以找到對應的學?.
Spring也是如此, 給每個對象起?個名字, 根據Bean的名稱(BeanId)就可以獲取到對應的對象.
Bean 命名約定
我們看下官??檔的說明: Bean Overview :: Spring Framework
程序開發?員不需要為bean指定名稱(BeanId), 如果沒有顯式的提供名稱(BeanId),Spring容器將為該bean?成唯?的名稱.
命名約定使?Java標準約定作為實例字段名. 也就是說,bean名稱以?寫字?開頭,然后使?駝峰式??寫
?如
類名: UserController, Bean的名稱為: userController
類名: AccountManager, Bean的名稱為: accountManager
類名: AccountService, Bean的名稱為: accountService
也有?些特殊情況, 當有多個字符并且第?個和第?個字符都是?寫時, 將保留原始的??寫. 這些規則與java.beans.Introspector.decapitalize (Spring在這?使?的)定義的規則相同.
?如
類名: UController, Bean的名稱為: UController
類名: AManager, Bean的名稱為: AManager
根據這個命名規則, 我們來獲取Bean
@SpringBootApplication
public class SpringIocDemoApplication {public static void main(String[] args) {//獲取Spring上下?對象ApplicationContext context = SpringApplication.run(SpringIocDemoApplicatio//從Spring上下?中獲取對象//根據bean類型, 從Spring上下?中獲取對象UserController userController1 = context.getBean(UserController.class);//根據bean名稱, 從Spring上下?中獲取對象UserController userController2 = (UserController) context.getBean("userCon//根據bean類型+名稱, 從Spring上下?中獲取對象UserController userController3 = context.getBean("userController",UserContSystem.out.println(userController1);System.out.println(userController2);System.out.println(userController3);}
}
地址?樣, 說明對象是?個
獲取bean對象, 是?類BeanFactory提供的功能
ApplicationContext VS BeanFactory(常??試題)
- 繼承關系和功能??來說:Spring 容器有兩個頂級的接?:BeanFactory 和 ApplicationContext。其中 BeanFactory 提供了基礎的訪問容器的能?,?ApplicationContext 屬于 BeanFactory 的?類,它除了繼承了 BeanFactory 的所有功能之外,它還擁有獨特的特性,還添加了對國際化?持、資源訪問?持、以及事件傳播等??的?持.
- 從性能??來說:ApplicationContext 是?次性加載并初始化所有的 Bean 對象,?BeanFactory 是需要那個才去加載那個,因此更加輕量. (空間換時間)
3.1.2 @Service(服務存儲)
使? @Service 存儲 bean 的代碼如下所?
@Service
public class UserService {public void sayHi(String name) {System.out.println("Hi," + name);}
}
讀取 bean 的代碼:
@SpringBootApplication
public class SpringIocDemoApplication {public static void main(String[] args) {//獲取Spring上下?對象ApplicationContext context = SpringApplication.run(SpringIocDemoApplicatio//從Spring中獲取UserService對象UserService userService = context.getBean(UserService.class);//使?對象userService.sayHi();}}
3.1.3 @Repository(倉庫存儲)
使? @Repository 存儲 bean 的代碼如下所?
@Repository
public class UserRepository {public void sayHi() {System.out.println("Hi, UserRepository~");}
}
讀取 bean 的代碼
@SpringBootApplication
public class SpringIocDemoApplication {public static void main(String[] args) {//獲取Spring上下?對象ApplicationContext context = SpringApplication.run(SpringIocDemoApplicatio//從Spring上下?中獲取對象UserRepository userRepository = context.getBean(UserRepository.class);//使?對象userRepository.sayHi();}
}
3.1.4 @Component(組件存儲)
使? @Component 存儲 bean 的代碼如下所?:
@Component
public class UserComponent {public void sayHi() {System.out.println("Hi, UserComponent~");}
}
讀取
@SpringBootApplication
public class SpringIocDemoApplication {public static void main(String[] args) {//獲取Spring上下?對象ApplicationContext context = SpringApplication.run(SpringIocDemoApplicatio//從Spring上下?中獲取對象UserComponent userComponent = context.getBean(UserComponent.class);//使?對象userComponent.sayHi();}
}
3.1.5 @Configuration(配置存儲)
使? @Configuration 存儲 bean 的代碼如下所?:
@Configuration
public class UserConfiguration {public void sayHi() {System.out.println("Hi,UserConfiguration~");}
}
讀取
@SpringBootApplication
public class SpringIocDemoApplication {public static void main(String[] args) {//獲取Spring上下?對象ApplicationContext context = SpringApplication.run(SpringIocDemoApplicatio//從Spring上下?中獲取對象UserConfiguration userConfiguration = context.getBean(UserConfiguration.cl//使?對象userConfiguration.sayHi();}
}
3.2 為什么要這么多類注解?
這個也是和咱們前?講的應?分層是呼應的. 讓程序員看到類注解之后,就能直接了解當前類的?途.
? @Controller:控制層, 接收請求, 對請求進?處理, 并進?響應.
? @Servie:業務邏輯層, 處理具體的業務邏輯.
? @Repository:數據訪問層,也稱為持久層. 負責數據訪問操作
? @Configuration:配置層. 處理項?中的?些配置信息.
這和每個省/市都有??的?牌號是?樣的.
?牌號都是唯?的, 標識?個?輛的. 但是為什么還需要設置不同的?牌開頭呢.
?如陜西的?牌號就是:陜X:XXXXXX,北京的?牌號:京X:XXXXXX,甚??個省不同的縣區也是不同的,?如西安就是,陜A:XXXXX,咸陽:陜B:XXXXXX,寶雞,陜C:XXXXXX,?樣.這樣做的好處除了可以節約號碼之外,更重要的作?是可以直觀的標識?輛?的歸屬地.
程序的應?分層,調?流程如下:
類注解之間的關系
查看 @Controller / @Service / @Repository / @Configuration 等注解的源碼發現:
其實這些注解??都有?個注解 @Component ,說明它們本?就是屬于 @Component 的"?類".
@Component 是?個元注解,也就是說可以注解其他類注解,如 @Controller , @Service ,@Repository 等. 這些注解被稱為 @Component 的衍?注解.
@Controller , @Service 和 @Repository ?于更具體的?例(分別在控制層, 業務邏輯層, 持久化層), 在開發過程中, 如果你要在業務邏輯層使? @Component 或@Service,顯然@Service是更好的選擇
?如杯?有喝?杯, 刷?杯等, 但是我們更傾向于在?常喝?時使??杯, 洗漱時使?刷?杯.
更多資料參考:
https://docs.spring.io/spring-framework/reference/core/beans/classpathscanning.html#beans-stereotype-annotations
3.3 ?法注解 @Bean
類注解是添加到某個類上的, 但是存在兩個問題:
- 使?外部包?的類, 沒辦法添加類注解
- ?個類, 需要多個對象, ?如多個數據源
這種場景, 我們就需要使??法注解 @Bean
我們先來看看?法注解如何使?:
public class BeanConfig {@Beanpublic User user(){User user = new User();user.setName("zhangsan");user.setAge(18);return user;}
}
然?,當我們寫完以上代碼,嘗試獲取 bean 對象中的 user 時卻發現,根本獲取不到:
3.3.1 ?法注解要配合類注解使?
在 Spring 框架的設計中,?法注解 @Bean 要配合類注解才能將對象正常的存儲到 Spring 容器中,如下代碼所?
@Component
public class BeanConfig {@Beanpublic User user(){User user = new User();user.setName("zhangsan");user.setAge(18);return user;}
}
3.3.2 定義多個對象
對于同?個類, 如何定義多個對象呢?
?如多數據源的場景, 類是同?個, 但是配置不同, 指向不同的數據源.
我們看下@Bean的使?
@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;}
}
定義了多個對象的話, 我們根據類型獲取對象, 獲取的是哪個對象呢?
@SpringBootApplication
public class SpringIocDemoApplication {public static void main(String[] args) {//獲取Spring上下?對象ApplicationContext context = SpringApplication.run(SpringIocDemoApplicatio//從Spring上下?中獲取對象User user = context.getBean(User.class);//使?對象System.out.println(user);}
}
報錯信息顯?: 期望只有?個匹配, 結果發現了兩個, user1, user2
從報錯信息中, 可以看出來, @Bean 注解的bean, bean的名稱就是它的?法名
接下來我們根據名稱來獲取bean對象
@SpringBootApplication
public class SpringIocDemoApplication {public static void main(String[] args) {//獲取Spring上下?對象ApplicationContext context = SpringApplication.run(SpringIocDemoApplicatio//根據bean名稱, 從Spring上下?中獲取對象User user1 = (User) context.getBean("user1");User user2 = (User) context.getBean("user2");System.out.println(user1);System.out.println(user2);}
}
運?結果:
可以看到, @Bean 可以針對同?個類, 定義多個對象.
3.3.3 重命名 Bean
可以通過設置 name 屬性給 Bean 對象進?重命名操作,如下代碼所?:
@Bean(name = {"u1","user1"})
public User user1(){User user = new User();user.setName("zhangsan");user.setAge(18);return user;
}
此時我們使? u1 就可以獲取到 User 對象了,如下代碼所?:
@SpringBootApplication
public class SpringIocDemoApplication {public static void main(String[] args) {//獲取Spring上下?對象ApplicationContext context = SpringApplication.run(SpringIocDemoApplicatio//從Spring上下?中獲取對象User u1 = (User) context.getBean("u1");//使?對象System.out.println(u1);}
}
name={} 可以省略,如下代碼所?
@Bean({"u1","user1"})
public User user1(){User user = new User();user.setName("zhangsan");user.setAge(18);return user;
}
只有?個名稱時, {}也可以省略, 如
@Bean("u1")
public User user1(){User user = new User();user.setName("zhangsan");user.setAge(18);return user;
}
3.4 掃描路徑
Q: 使?前?學習的四個注解聲明的bean,?定會?效嗎?
A: 不?定(原因:bean想要?效,還需要被Spring掃描)
下?我們通過修改項??程的?錄結構,來測試bean對象是否?效:
再運?代碼:
@SpringBootApplication
public class SpringIocDemoApplication {public static void main(String[] args) {//獲取Spring上下?對象ApplicationContext context = SpringApplication.run(SpringIocDemoApplicatio//從Spring上下?中獲取對象User u1 = (User) context.getBean("u1");//使?對象System.out.println(u1);}
}
解釋: 沒有bean的名稱為u1
為什么沒有找到bean對象呢?
使?五?注解聲明的bean,要想?效, 還需要配置掃描路徑, 讓Spring掃描到這些注解
也就是通過 @ComponentScan 來配置掃描路徑.
@ComponentScan({"com.example.demo"})
@SpringBootApplication
public class SpringIocDemoApplication {public static void main(String[] args) {//獲取Spring上下?對象ApplicationContext context = SpringApplication.run(SpringIocDemoApplicatio//從Spring上下?中獲取對象User u1 = (User) context.getBean("u1");//使?對象System.out.println(u1);}
}
{} ?可以配置多個包路徑
這種做法僅做了解, 不做推薦使?
那為什么前?沒有配置 @ComponentScan注解也可以呢?
@ComponentScan 注解雖然沒有顯式配置,但是實際上已經包含在了啟動類聲明注解@SpringBootApplication 中了
默認掃描的范圍是SpringBoot啟動類所在包及其?包
在配置類上添加 @ComponentScan 注解, 該注解默認會掃描該類所在的包下所有的配置類
推薦做法:
把啟動類放在我們希望掃描的包的路徑下, 這樣我們定義的bean就都可以被掃描到
4. DI 詳解
上?我們講解了控制反轉IoC的細節,接下來呢,我們學習依賴注?DI的細節。
依賴注?是?個過程,是指IoC容器在創建Bean時, 去提供運?時所依賴的資源,?資源指的就是對象.在上?程序案例中,我們使?了 @Autowired 這個注解,完成了依賴注?的操作.
簡單來說, 就是把對象取出來放到某個類的屬性中.
在?些?章中, 依賴注?也被稱之為 “對象注?”, “屬性裝配”, 具體含義需要結合?章的上下?來理解
關于依賴注?, Spring也給我們提供了三種?式:
- 屬性注?(Field Injection)
- 構造?法注?(Constructor Injection)
- Setter 注?(Setter Injection)
接下來,我們分別來看。
下?我們按照實際開發中的模式,將 Service 類注?到Controller 類中。
4.1 屬性注?
屬性注?是使? @Autowired 實現的,將 Service 類注?到 Controller 類中.
Service 類的實現代碼如下:
import org.springframework.stereotype.Service;
@Service
public class UserService {public void sayHi() {System.out.println("Hi,UserService");}
}
Controller 類的實現代碼如下:
@Controller
public class UserController {//注??法1: 屬性注?@Autowiredprivate UserService userService;public void sayHi(){System.out.println("hi,UserController...");userService.sayHi();}
}
獲取 Controller 中的 sayHi?法:
@SpringBootApplication
public class SpringIocDemoApplication {public static void main(String[] args) {//獲取Spring上下?對象ApplicationContext context = SpringApplication.run(SpringIocDemoApplicatio//從Spring上下?中獲取對象UserController userController = (UserController) context.getBean("userCont//使?對象userController.sayHi();}
}
去掉@Autowired , 再運??下程序看看結果
4.2 構造?法注?
構造?法注?是在類的構造?法中實現注?,如下代碼所?:
@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();}
}
注意事項:如果類只有?個構造?法,那么 @Autowired 注解可以省略;如果類中有多個構造?法,
那么需要添加上 @Autowired 來明確指定到底使?哪個構造?法。
4.3 Setter 注?
Setter 注?和屬性的 Setter ?法實現類似,只不過在設置 set ?法的時候需要加上 @Autowired 注解 ,如下代碼所?
@Controller
public class UserController3 {//注??法3: Setter?法注?private UserService userService;@Autowiredpublic void setUserService(UserService userService) {this.userService = userService;}public void sayHi(){System.out.println("hi,UserController3...");userService.sayHi();}
}
練習?下:嘗試?下 set ?法如果不加 @Autowired 注解能注?成功嗎?
4.4 三種注?優缺點分析
-
屬性注?
- 優點: 簡潔,使??便;
- 缺點:
- 只能?于 IoC 容器,如果是? IoC 容器不可?,并且只有在使?的時候才會出現 NPE(空指針異常)
- 不能注??個Final修飾的屬性
-
構造函數注?(Spring 4.X推薦)
- 優點:
- 可以注?final修飾的屬性
- 注?的對象不會被修改
- 依賴對象在使?前?定會被完全初始化,因為依賴是在類的構造?法中執?的,?構造?法是在類加載階段就會執?的?法.
- 通?性好, 構造?法是JDK?持的, 所以更換任何框架,他都是適?的
- 缺點:
- 注?多個對象時, 代碼會?較繁瑣
- 優點:
-
Setter注?(Spring 3.X推薦)
- 優點: ?便在類實例之后, 重新對該對象進?配置或者注?
- 缺點:
- 不能注??個Final修飾的屬性
- 注?對象可能會被改變, 因為setter?法可能會被多次調?, 就有被修改的?險.
4.5 @Autowired存在問題
當同?類型存在多個bean時, 使?@Autowired會存在問題
@Component
public class BeanConfig {@Bean("u1")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 UserService userService;//注?user@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注解,來確定默認的實現.
@Component
public class BeanConfig {@Primary //指定該bean為默認bean的實現@Bean("u1")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的名稱。
- @Qualifier注解不能單獨使?,必須配合@Autowired使?
@Controller
public class UserController {@Qualifier("user2") //指定bean名稱@Autowiredprivate User user;public void sayHi(){System.out.println("hi,UserController...");System.out.println(user);}
}
使?@Resource注解:是按照bean的名稱進?注?。通過name屬性指定要注?的bean的名稱。
@Controller
public class UserController {@Resource(name = "user2")private User user;public void sayHi(){System.out.println("hi,UserController...");System.out.println(user);}
}
常??試題:
@Autowird 與 @Resource的區別
? @Autowired 是spring框架提供的注解,?@Resource是JDK提供的注解
? @Autowired 默認是按照類型注?,?@Resource是按照名稱注?. 相?于 @Autowired 來說,@Resource ?持更多的參數設置,例如 name 設置,根據名稱獲取 Bean
5. 總結
Spring, Spring Boot 和Spring MVC的關系以及區別
Spring: 簡單來說,== Spring 是?個開發應?框架==,什么樣的框架呢,有這么?個標簽:輕量級、?站式、模塊化,其?的是?于簡化企業級應?程序開發.
Spring的主要功能: 管理對象,以及對象之間的依賴關系, ?向切?編程, 數據庫事務管理, 數據訪問, web框架?持等.
但是Spring具備?度可開放性, 并不強制依賴Spring, 開發者可以?由選擇Spring的部分或者全部, Spring可以?縫繼承第三?框架, ?如數據訪問框架(Hibernate 、JPA), web框架(如Struts、JSF)
Spring MVC: Spring MVC是Spring的?個?框架, Spring誕?之后, ?家覺得很好?, 于是按照MVC模式設計了?個 MVC框架(?些?Spring 解耦的組件), 主要?于開發WEB應?和?絡接?,所以,Spring MVC 是?個Web框架.
Spring MVC基于Spring進?開發的, 天?的與Spring框架集成. 可以讓我們更簡潔的進?Web層開發, ?持靈活的 URL 到??控制器的映射, 提供了強?的約定?于配置的契約式編程?持, ?常容易與其他視圖框架集成,如 Velocity、FreeMarker等
Spring Boot: Spring Boot是對Spring的?個封裝, 為了簡化Spring應?的開發?出現的,中?型企業,沒有成本研究??的框架, 使?Spring Boot 可以更加快速的搭建框架, 降級開發成本, 讓開發?員更加專注于Spring應?的開發,??需過多關注XML的配置和?些底層的實現.
Spring Boot 是個腳?架, 插拔式搭建項?, 可以快速的集成其他框架進來?如想使?SpringBoot開發Web項?, 只需要引?Spring MVC框架即可, Web開發的?作是pringMVC完成的, ?不是SpringBoot, 想完成數據訪問, 只需要引?Mybatis框架即可.Spring Boot只是輔助簡化項?開發的, 讓開發變得更加簡單, 甚?不需要額外的web服務器, 直接?成jar包執?即可.
最后?句話總結: Spring MVC和Spring Boot都屬于Spring,Spring MVC 是基于Spring的?個MVC 框架,?Spring Boot 是基于Spring的?套快速開發整合包.