Spring?Boot?底層原理基礎(chǔ)深度解析
1. 底層注解@Configuration
@Configuration 注解主要用于給容器添加組件(Bean),下面實(shí)踐其用法:
項(xiàng)目基本結(jié)構(gòu):

兩個(gè)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標(biāo)注在方法上給容器注冊(cè)組件,默認(rèn)也是單實(shí)例的
* 2. 配置類本身也是組件
* 3. proxyBeanMethods: 代理Bean 方法:
* Full(proxyBeanMethods = true): 外部無(wú)論對(duì)配置類中的這個(gè)組件注冊(cè)方法調(diào)用多少次,獲取的都是之前注冊(cè)容器中的單實(shí)例對(duì)象
* Lite(proxyBeanMethods = false): 在容器中不會(huì)保留代理對(duì)象,外部多次調(diào)用這些組件時(shí),每次調(diào)用都會(huì)產(chǎn)生一個(gè)新的對(duì)象
* 用于解決組件依賴場(chǎng)景
*/
@Configuration(proxyBeanMethods = true) //告訴SpringBoot 這是一個(gè)配置類 == 以前的配置文件
public class MyConfig {
/**
* 外部無(wú)論對(duì)配置類中的這個(gè)組件注冊(cè)方法調(diào)用多少次,獲取的都是之前注冊(cè)容器中的單實(shí)例對(duì)象
* @return
*/
@Bean //給容器中添加組件,以方法名作為主鍵id,返回類型就是組件類型,返回值就是組件在容器中的實(shí)例
public User user01(){
return new User("dragon",18);
}
@Bean("tomcatPet")
public Pet pet01(){
return new Pet("dragonPet");
}主類MainApplication.java 中測(cè)試調(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;
/**
* 主程序類
* 這個(gè)注解相當(dāng)于告訴Spring Boot: 這是一個(gè)Spring boot 應(yīng)用
*/
//@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)代理對(duì)象調(diào)用方法, Spring Boot 總會(huì)檢查這個(gè)組件是否在容器中有,如果有則不會(huì)新建,保持組件單實(shí)例。
User user01 = myConfig.user01();
User user02 = myConfig.user01();
System.out.println(user01 == user02);
}
}輸出的部分結(jié)果:

上面的例子,重點(diǎn)落在@Configuration(proxyBeanMethods = true) 注解。 該注解告訴SpringBoot ,被注解的類是一個(gè)配置類, 相當(dāng)于以前的配置文件xml中的“bean配置”。該注解有如下特性:
1. 該注解的配置類里面使用@Bean標(biāo)注在方法上給容器注冊(cè)組件,默認(rèn)也是單實(shí)例的。
2. 被這個(gè)注解的配置類本身也是組件。
3. 該注解的屬性proxyBeanMethods 可以通過(guò)“true” 和 “false” 配置值,來(lái)控制使用的模式:
(1)Full模式(proxyBeanMethods = true): 為true時(shí),外部無(wú)論對(duì)配置類中的組件注冊(cè)方法調(diào)用多少次,獲取的都是之前注冊(cè)容器中的單實(shí)例對(duì)象。
(2)Lite模式(proxyBeanMethods = false): 為false時(shí),在容器中不會(huì)保留代理對(duì)象,外部多次調(diào)用這些組件時(shí),每次調(diào)用都會(huì)產(chǎn)生一個(gè)新的對(duì)象。
這兩種模式的存在主要用于解決組件依賴場(chǎng)景。
1和2 兩點(diǎn)特性上面的例子中都有體現(xiàn), 接下來(lái)重點(diǎn)實(shí)踐第三點(diǎn)特性:
實(shí)踐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對(duì)象對(duì)用pet對(duì)象,同時(shí)使用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標(biāo)注在方法上給容器注冊(cè)組件,默認(rèn)也是單實(shí)例的
* 2. 配置類本身也是組件
* 3. proxyBeanMethods: 代理Bean 方法:
* Full模式(proxyBeanMethods = true): 外部無(wú)論對(duì)配置類中的這個(gè)組件注冊(cè)方法調(diào)用多少次,獲取的都是之前注冊(cè)容器中的單實(shí)例對(duì)象
* Lite模式(proxyBeanMethods = false): 在容器中不會(huì)保留代理對(duì)象,外部多次調(diào)用這些組件時(shí),每次調(diào)用都會(huì)產(chǎn)生一個(gè)新的對(duì)象
* 用于解決組件依賴場(chǎng)景
*/
@Configuration(proxyBeanMethods = true) //告訴SpringBoot 這是一個(gè)配置類 == 以前的配置文件
public class MyConfig {
/**
* 外部無(wú)論對(duì)配置類中的這個(gè)組件注冊(cè)方法調(diào)用多少次,獲取的都是之前注冊(cè)容器中的單實(shí)例對(duì)象
* @return
*/
@Bean //給容器中添加組件,以方法名作為主鍵id,返回類型就是組件類型,返回值就是組件在容器中的實(shí)例
public User user01(){
User dragonUser = new User("dragon",18);
// User 組件依賴了Pet 組件,當(dāng)proxyBeanMethods 為 true 時(shí),這種依賴關(guān)系成立
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;
/**
* 主程序類
* 這個(gè)注解相當(dāng)于告訴Spring Boot: 這是一個(gè)Spring boot 應(yīng)用
*/
//@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)代理對(duì)象調(diào)用方法, Spring Boot 總會(huì)檢查這個(gè)組件是否在容器中有,如果有則不會(huì)新建,保持組件單實(shí)例。
User user01 = myConfig.user01();
User user02 = myConfig.user01();
System.out.println(user01 == user02);
//測(cè)試 @Configuration(proxyBeanMethods = true/false)
User user011 = run.getBean("user01", User.class);
Pet tomcatPet = run.getBean("tomcatPet", Pet.class);
System.out.println("用戶的寵物:" + (user011.getPet() == tomcatPet));
}
}運(yùn)行結(jié)果:

可以看出,F(xiàn)ull模式(proxyBeanMethods = true)時(shí),輸出true,說(shuō)明是從容器中獲取的同一個(gè)組件(用戶的寵物就是容器中的寵物)。
接下來(lái),改用Lite模式(proxyBeanMethods = false):即基于上面實(shí)例,將配置類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標(biāo)注在方法上給容器注冊(cè)組件,默認(rèn)也是單實(shí)例的
* 2. 配置類本身也是組件
* 3. proxyBeanMethods: 代理Bean 方法:
* Full模式(proxyBeanMethods = true): 外部無(wú)論對(duì)配置類中的這個(gè)組件注冊(cè)方法調(diào)用多少次,獲取的都是之前注冊(cè)容器中的單實(shí)例對(duì)象
* Lite模式(proxyBeanMethods = false): 在容器中不會(huì)保留代理對(duì)象,外部多次調(diào)用這些組件時(shí),每次調(diào)用都會(huì)產(chǎn)生一個(gè)新的對(duì)象
* 用于解決組件依賴場(chǎng)景
*/
@Configuration(proxyBeanMethods = false) //告訴SpringBoot 這是一個(gè)配置類 == 以前的配置文件
public class MyConfig {
/**
* 外部無(wú)論對(duì)配置類中的這個(gè)組件注冊(cè)方法調(diào)用多少次,獲取的都是之前注冊(cè)容器中的單實(shí)例對(duì)象
* @return
*/
@Bean //給容器中添加組件,以方法名作為主鍵id,返回類型就是組件類型,返回值就是組件在容器中的實(shí)例
public User user01(){
User dragonUser = new User("dragon",18);
// User 組件依賴了Pet 組件,當(dāng)proxyBeanMethods 為 true 時(shí),這種依賴關(guān)系成立
dragonUser.setPet(pet01());
return dragonUser;
}
@Bean("tomcatPet")
public Pet pet01(){
return new Pet("dragonPet");
}運(yùn)行結(jié)果:

可以看出,Lite模式(proxyBeanMethods = false)時(shí),輸出false,說(shuō)明是從容器中獲取的不是同一個(gè)組件(用戶的寵物不是容器中的寵物, 相當(dāng)于new 了另一個(gè)對(duì)象)。
總結(jié):配置類包括了全模式(Full)和輕量級(jí)模式(Lite)兩種。當(dāng)proxyBeanMethods 是true時(shí),Spring Boot 每次都會(huì)檢查容器中是否有相應(yīng)的組件,如果proxyBeanMethods 是false, 則不檢查容器中是否有沒(méi)有相應(yīng)的組件,而是直接new一個(gè)。這也是Spring Boot 新增的一個(gè)很重要的特性。
最佳實(shí)戰(zhàn):如果只是向容器中增加組件,別的地方也不會(huì)調(diào)用這個(gè)組件,我們可以將其調(diào)為false 模式,這樣Spring Boot 啟動(dòng)起來(lái)非常快,加載起來(lái)也非常快。 如果別的地方明顯要用,要依賴,我們就把其調(diào)成true,保證依賴的組件就是容器中的組件。
注: 前面的例子中,在配置類中用到@Been 注解來(lái)指定組件, 其實(shí)Spring Boot 底層還用到了其他一些以前常用的注解來(lái)指定組件,包括@Component、@Controller、@Service、@Repository。這些類似于@Been 原理,也是用于向容器中注冊(cè)組件。
除此之外,底層還用到@ComponentScan 注解來(lái)說(shuō)明容器的包掃描,還有@Import 和@Conditional 來(lái)向容器添加組件。很多注解是以前常用的,接下來(lái)主要說(shuō)明@Import 和@Conditional 注解。
2. 底層注解@Import
首先,從@Import 注解類中可以看到該注解的定義,以及知道其屬性是一個(gè)Class類型的數(shù)組,說(shuō)明這個(gè)注解的作用是向容器中導(dǎo)入一批組件:

接下來(lái),實(shí)踐一下:
首先在配置類上加入@Import 注解,并向容器中導(dǎo)入兩個(gè)組件,一個(gè)是自己定義的類,一個(gè)是從第三方Jar 包中任意的一個(gè)類:

主類加入如下測(cè)試:

運(yùn)行結(jié)果:

結(jié)果說(shuō)明:
“com.menergy.boot.bean.User” 是通過(guò)@Import 導(dǎo)入的組件。(默認(rèn)的組件名稱是全類名)
“user01” 是之前用@Bean 方法添加進(jìn)去的
“org.apache.logging.log4j.util.StringBuilders@4482469c” 也是通過(guò)@Import 導(dǎo)入的組件。
3. 底層注解@Conditional
@Conditional 是條件裝配:當(dāng)滿足@Conditional指定的條件時(shí), 才向容器中注入組件。
在全局Jar包中搜索@Conditional 類:雙擊Shift鍵,選擇Classes,輸入@Conditional搜索。

注:如果調(diào)不出這個(gè)窗口,請(qǐng)參考:IDEA 操作與設(shè)置筆記
http://www.dhdzp.com/article/208232.htm
打開(kāi)Conditional 類后,“Ctrl + H” 鍵調(diào)出這個(gè)類的繼承樹(shù):

注:如果快捷鍵失效,請(qǐng)確定如下快捷鍵設(shè)置:

從前面的@Conditional 的繼承樹(shù)可以看出,@Conditional 有非常多的派生注解,每個(gè)注解都代表不同的功能,從派生注解的注解名稱可以大概知道其功能用意,例如@ConditionalOnBean 注解代表當(dāng)容器中存在某個(gè)Bean時(shí)才干某些事情, @ConditionalOnMissingBean 注解代表當(dāng)容器中不存在某個(gè)Bean時(shí)才干某些事情。
接下來(lái),以@ConditionalOnBean 為例,進(jìn)行實(shí)踐:
到此這篇關(guān)于Spring Boot 底層原理基礎(chǔ)的文章就介紹到這了,更多相關(guān)Spring Boot 底層原理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Retrofit+Rxjava下載文件進(jìn)度的實(shí)現(xiàn)
這篇文章主要介紹了Retrofit+Rxjava下載文件進(jìn)度的實(shí)現(xiàn),非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2017-11-11
Java中ShardingSphere分庫(kù)分表實(shí)戰(zhàn)
我們做項(xiàng)目的時(shí)候,數(shù)據(jù)量比較大,單表千萬(wàn)級(jí)別的,需要分庫(kù)分表,本文主要介紹了Java中ShardingSphere分庫(kù)分表實(shí)戰(zhàn),感興趣的可以了解一下2021-09-09
JAVA浮點(diǎn)數(shù)計(jì)算精度損失底層原理與解決方案
本文主要介紹了JAVA浮點(diǎn)數(shù)計(jì)算精度損失底層原理與解決方案。具有很好的參考價(jià)值,下面跟著小編一起來(lái)看下吧2017-02-02
javaweb圖書商城設(shè)計(jì)之圖書模塊(4)
這篇文章主要介紹了javaweb圖書商城設(shè)計(jì)之圖書模塊的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-11-11

