Spring?Boot?底層原理基礎深度解析
1. 底層注解@Configuration
@Configuration 注解主要用于給容器添加組件(Bean),下面實踐其用法:
項目基本結構:
兩個Bean組件:
User.java
package com.menergy.boot.bean; /** * 用戶 */ public class User { private String name; private Integer age; public User() { } public User(String name, Integer age) { this.name = name; this.age = age; public String getName() { return name; public void setName(String name) { public Integer getAge() { return age; public void setAge(Integer age) { @Override public String toString() { return "User{" + "name='" + name + '\'' + ", age=" + age + '}'; }
Pet.java
package com.menergy.boot.bean; /** * 寵物 */ public class Pet { private String name; public Pet() { } public Pet(String name) { this.name = name; public String getName() { return name; public void setName(String name) { @Override public String toString() { return "Pet{" + "name='" + name + '\'' + '}'; }
以前Spring 配置文件方式是這樣給容器添加組件的:
<beans> <bean id="user01" class="com.menergy.boot.bean.User"> <property name="name" value="dragon"></property> <property name="age" value="18"></property> </bean> <bean id="pet01" class="com.menergy.boot.bean.Pet"> <property name="name" value="dragonPet"></property> </beans>
現(xiàn)在Spring Boot 已經(jīng)不寫上面的xml配置了,在Spring Boot 底層可以用@Configuration 注解給容器中添加組件。如下:
注解類MyConfig.java
package com.menergy.boot.config; import com.menergy.boot.bean.Pet; import com.menergy.boot.bean.User; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * 1. 配置類里面使用@Bean標注在方法上給容器注冊組件,默認也是單實例的 * 2. 配置類本身也是組件 * 3. proxyBeanMethods: 代理Bean 方法: * Full(proxyBeanMethods = true): 外部無論對配置類中的這個組件注冊方法調(diào)用多少次,獲取的都是之前注冊容器中的單實例對象 * Lite(proxyBeanMethods = false): 在容器中不會保留代理對象,外部多次調(diào)用這些組件時,每次調(diào)用都會產(chǎn)生一個新的對象 * 用于解決組件依賴場景 */ @Configuration(proxyBeanMethods = true) //告訴SpringBoot 這是一個配置類 == 以前的配置文件 public class MyConfig { /** * 外部無論對配置類中的這個組件注冊方法調(diào)用多少次,獲取的都是之前注冊容器中的單實例對象 * @return */ @Bean //給容器中添加組件,以方法名作為主鍵id,返回類型就是組件類型,返回值就是組件在容器中的實例 public User user01(){ return new User("dragon",18); } @Bean("tomcatPet") public Pet pet01(){ return new Pet("dragonPet"); }
主類MainApplication.java 中測試調(diào)用:
package com.menergy.boot; import com.menergy.boot.bean.Pet; import com.menergy.boot.bean.User; import com.menergy.boot.config.MyConfig; import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.ComponentScan; import sun.awt.geom.AreaOp; /** * 主程序類 * 這個注解相當于告訴Spring Boot: 這是一個Spring boot 應用 */ //@SpringBootApplication @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan("com.menergy.boot") public class MainApplication { public static void main(String[] args) { // SpringApplication.run(MainApplication.class, args); // 1.返回IOC容器 ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args); // 2.查看容器里面的容器 String[] names = run.getBeanDefinitionNames(); for (String name : names) { System.out.println(name); } // 3. 從容器中獲取組件 Pet pet1 = run.getBean("tomcatPet", Pet.class); Pet pet2 = run.getBean("tomcatPet", Pet.class); System.out.println("組件: " + (pet1 == pet2)); // 4. com.menergy.boot.config.MyConfig$$EnhancerBySpringCGLIB$$3779496a@67a056f1 MyConfig myConfig = run.getBean(MyConfig.class); System.out.println(myConfig); //如果@Configuration(proxyBeanMethods = true)代理對象調(diào)用方法, Spring Boot 總會檢查這個組件是否在容器中有,如果有則不會新建,保持組件單實例。 User user01 = myConfig.user01(); User user02 = myConfig.user01(); System.out.println(user01 == user02); } }
輸出的部分結果:
上面的例子,重點落在@Configuration(proxyBeanMethods = true) 注解。 該注解告訴SpringBoot ,被注解的類是一個配置類, 相當于以前的配置文件xml中的“bean配置”。該注解有如下特性:
1. 該注解的配置類里面使用@Bean標注在方法上給容器注冊組件,默認也是單實例的。
2. 被這個注解的配置類本身也是組件。
3. 該注解的屬性proxyBeanMethods 可以通過“true” 和 “false” 配置值,來控制使用的模式:
(1)Full模式(proxyBeanMethods = true): 為true時,外部無論對配置類中的組件注冊方法調(diào)用多少次,獲取的都是之前注冊容器中的單實例對象。
(2)Lite模式(proxyBeanMethods = false): 為false時,在容器中不會保留代理對象,外部多次調(diào)用這些組件時,每次調(diào)用都會產(chǎn)生一個新的對象。
這兩種模式的存在主要用于解決組件依賴場景。
1和2 兩點特性上面的例子中都有體現(xiàn), 接下來重點實踐第三點特性:
實踐proxyBeanMethods:
基于上面的例子,首先修改User.java類,加上寵物Pet的依賴:
package com.menergy.boot.bean; /** * 用戶 */ public class User { private String name; private Integer age; private Pet pet; public User() { } public User(String name, Integer age) { this.name = name; this.age = age; public User(String name, Integer age, Pet pet) { this.pet = pet; public String getName() { return name; public void setName(String name) { public Integer getAge() { return age; public void setAge(Integer age) { public Pet getPet() { return pet; public void setPet(Pet pet) { @Override public String toString() { return "User{" + "name='" + name + '\'' + ", age=" + age + ", pet=" + pet + '}'; }
在配置類MyConfig.java 中加入user01對象對用pet對象,同時使用Full模式(proxyBeanMethods = true):
package com.menergy.boot.config; import com.menergy.boot.bean.Pet; import com.menergy.boot.bean.User; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * 1. 配置類里面使用@Bean標注在方法上給容器注冊組件,默認也是單實例的 * 2. 配置類本身也是組件 * 3. proxyBeanMethods: 代理Bean 方法: * Full模式(proxyBeanMethods = true): 外部無論對配置類中的這個組件注冊方法調(diào)用多少次,獲取的都是之前注冊容器中的單實例對象 * Lite模式(proxyBeanMethods = false): 在容器中不會保留代理對象,外部多次調(diào)用這些組件時,每次調(diào)用都會產(chǎn)生一個新的對象 * 用于解決組件依賴場景 */ @Configuration(proxyBeanMethods = true) //告訴SpringBoot 這是一個配置類 == 以前的配置文件 public class MyConfig { /** * 外部無論對配置類中的這個組件注冊方法調(diào)用多少次,獲取的都是之前注冊容器中的單實例對象 * @return */ @Bean //給容器中添加組件,以方法名作為主鍵id,返回類型就是組件類型,返回值就是組件在容器中的實例 public User user01(){ User dragonUser = new User("dragon",18); // User 組件依賴了Pet 組件,當proxyBeanMethods 為 true 時,這種依賴關系成立 dragonUser.setPet(pet01()); return dragonUser; } @Bean("tomcatPet") public Pet pet01(){ return new Pet("dragonPet"); }
主類MainApplication.java:
package com.menergy.boot; import com.menergy.boot.bean.Pet; import com.menergy.boot.bean.User; import com.menergy.boot.config.MyConfig; import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.ComponentScan; import sun.awt.geom.AreaOp; /** * 主程序類 * 這個注解相當于告訴Spring Boot: 這是一個Spring boot 應用 */ //@SpringBootApplication @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan("com.menergy.boot") public class MainApplication { public static void main(String[] args) { // SpringApplication.run(MainApplication.class, args); // 1.返回IOC容器 ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args); // 2.查看容器里面的容器 String[] names = run.getBeanDefinitionNames(); for (String name : names) { System.out.println(name); } // 3. 從容器中獲取組件 Pet pet1 = run.getBean("tomcatPet", Pet.class); Pet pet2 = run.getBean("tomcatPet", Pet.class); System.out.println("組件: " + (pet1 == pet2)); // 4. com.menergy.boot.config.MyConfig$$EnhancerBySpringCGLIB$$3779496a@67a056f1 MyConfig myConfig = run.getBean(MyConfig.class); System.out.println(myConfig); //如果@Configuration(proxyBeanMethods = true)代理對象調(diào)用方法, Spring Boot 總會檢查這個組件是否在容器中有,如果有則不會新建,保持組件單實例。 User user01 = myConfig.user01(); User user02 = myConfig.user01(); System.out.println(user01 == user02); //測試 @Configuration(proxyBeanMethods = true/false) User user011 = run.getBean("user01", User.class); Pet tomcatPet = run.getBean("tomcatPet", Pet.class); System.out.println("用戶的寵物:" + (user011.getPet() == tomcatPet)); } }
運行結果:
可以看出,F(xiàn)ull模式(proxyBeanMethods = true)時,輸出true,說明是從容器中獲取的同一個組件(用戶的寵物就是容器中的寵物)。
接下來,改用Lite模式(proxyBeanMethods = false):即基于上面實例,將配置類MyConfig.java 中的注解的屬性proxyBeanMethods 改成false值,如下:
MyConfig.java:
package com.menergy.boot.config; import com.menergy.boot.bean.Pet; import com.menergy.boot.bean.User; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * 1. 配置類里面使用@Bean標注在方法上給容器注冊組件,默認也是單實例的 * 2. 配置類本身也是組件 * 3. proxyBeanMethods: 代理Bean 方法: * Full模式(proxyBeanMethods = true): 外部無論對配置類中的這個組件注冊方法調(diào)用多少次,獲取的都是之前注冊容器中的單實例對象 * Lite模式(proxyBeanMethods = false): 在容器中不會保留代理對象,外部多次調(diào)用這些組件時,每次調(diào)用都會產(chǎn)生一個新的對象 * 用于解決組件依賴場景 */ @Configuration(proxyBeanMethods = false) //告訴SpringBoot 這是一個配置類 == 以前的配置文件 public class MyConfig { /** * 外部無論對配置類中的這個組件注冊方法調(diào)用多少次,獲取的都是之前注冊容器中的單實例對象 * @return */ @Bean //給容器中添加組件,以方法名作為主鍵id,返回類型就是組件類型,返回值就是組件在容器中的實例 public User user01(){ User dragonUser = new User("dragon",18); // User 組件依賴了Pet 組件,當proxyBeanMethods 為 true 時,這種依賴關系成立 dragonUser.setPet(pet01()); return dragonUser; } @Bean("tomcatPet") public Pet pet01(){ return new Pet("dragonPet"); }
運行結果:
可以看出,Lite模式(proxyBeanMethods = false)時,輸出false,說明是從容器中獲取的不是同一個組件(用戶的寵物不是容器中的寵物, 相當于new 了另一個對象)。
總結:配置類包括了全模式(Full)和輕量級模式(Lite)兩種。當proxyBeanMethods 是true時,Spring Boot 每次都會檢查容器中是否有相應的組件,如果proxyBeanMethods 是false, 則不檢查容器中是否有沒有相應的組件,而是直接new一個。這也是Spring Boot 新增的一個很重要的特性。
最佳實戰(zhàn):如果只是向容器中增加組件,別的地方也不會調(diào)用這個組件,我們可以將其調(diào)為false 模式,這樣Spring Boot 啟動起來非??欤虞d起來也非???。 如果別的地方明顯要用,要依賴,我們就把其調(diào)成true,保證依賴的組件就是容器中的組件。
注: 前面的例子中,在配置類中用到@Been 注解來指定組件, 其實Spring Boot 底層還用到了其他一些以前常用的注解來指定組件,包括@Component、@Controller、@Service、@Repository。這些類似于@Been 原理,也是用于向容器中注冊組件。
除此之外,底層還用到@ComponentScan 注解來說明容器的包掃描,還有@Import 和@Conditional 來向容器添加組件。很多注解是以前常用的,接下來主要說明@Import 和@Conditional 注解。
2. 底層注解@Import
首先,從@Import 注解類中可以看到該注解的定義,以及知道其屬性是一個Class類型的數(shù)組,說明這個注解的作用是向容器中導入一批組件:
接下來,實踐一下:
首先在配置類上加入@Import 注解,并向容器中導入兩個組件,一個是自己定義的類,一個是從第三方Jar 包中任意的一個類:
主類加入如下測試:
運行結果:
結果說明:
“com.menergy.boot.bean.User” 是通過@Import 導入的組件。(默認的組件名稱是全類名)
“user01” 是之前用@Bean 方法添加進去的
“org.apache.logging.log4j.util.StringBuilders@4482469c” 也是通過@Import 導入的組件。
3. 底層注解@Conditional
@Conditional 是條件裝配:當滿足@Conditional指定的條件時, 才向容器中注入組件。
在全局Jar包中搜索@Conditional 類:雙擊Shift鍵,選擇Classes,輸入@Conditional搜索。
注:如果調(diào)不出這個窗口,請參考:IDEA 操作與設置筆記
http://www.dbjr.com.cn/article/208232.htm
打開Conditional 類后,“Ctrl + H” 鍵調(diào)出這個類的繼承樹:
注:如果快捷鍵失效,請確定如下快捷鍵設置:
從前面的@Conditional 的繼承樹可以看出,@Conditional 有非常多的派生注解,每個注解都代表不同的功能,從派生注解的注解名稱可以大概知道其功能用意,例如@ConditionalOnBean 注解代表當容器中存在某個Bean時才干某些事情, @ConditionalOnMissingBean 注解代表當容器中不存在某個Bean時才干某些事情。
接下來,以@ConditionalOnBean 為例,進行實踐:
到此這篇關于Spring Boot 底層原理基礎的文章就介紹到這了,更多相關Spring Boot 底層原理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
SpringBoot多數(shù)據(jù)源配置詳細教程(JdbcTemplate、mybatis)
這篇文章主要介紹了SpringBoot多數(shù)據(jù)源配置詳細教程(JdbcTemplate、mybatis),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2021-03-03解決IDEA?2022?Translation?翻譯文檔失敗:?未知錯誤的問題
這篇文章主要介紹了IDEA?2022?Translation?翻譯文檔失敗:?未知錯誤,本文較詳細的給大家介紹了IDEA?2022?Translation未知錯誤翻譯文檔失敗的解決方法,需要的朋友可以參考下2022-04-04Java SpringMVC數(shù)據(jù)響應超詳細講解
Spring?MVC?是?Spring?提供的一個基于?MVC?設計模式的輕量級?Web?開發(fā)框架,本質(zhì)上相當于?Servlet,Spring?MVC?角色劃分清晰,分工明細,本章來講解SpringMVC數(shù)據(jù)響應2022-04-04Spring Boot 2和Redis例子實現(xiàn)過程解析
這篇文章主要介紹了Spring Boot2發(fā)布與調(diào)用REST服務過程解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2019-11-11