configuration configuration文件
推薦學習
- 肝了十天半月,獻上純手繪“Spring/Cloud/Boot/MVC”全家桶腦圖
- 一個SpringBoot問題就干趴下了?我卻憑著這份PDF文檔吊打面試官.
為了能深入地掌握Spring Boot的自動配置原理,我們來看一下Spring Boot的一些底層注解,要知道它們是如何完成相關功能的。首先,我們來看一下怎么給容器里面添加組件。
我在這兒準備了兩個組件,它們分別是:
- 用戶,即User類
package com.meimeixia.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) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return &34;User{&34; +
&34;name=&39;&34; + name + &39;&39;&39; +
&34;, age=&34; + age +
&39;}&39;;
}
}
- 寵物,即Pet類
package com.meimeixia.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) {
this.name = name;
}
@Override
public String toString() {
return &34;Pet{&34; +
&34;name=&39;&34; + name + &39;&39;&39; +
&39;}&39;;
}
}
如果我們是用以前原生的Spring來把以上這兩個組件添加到容器中,那么我們應該是要這么來做的,即首先來創建一個Spring的配置文件,例如beans.xml,記住該文件得在src > main > resources目錄下喲,然后使用<bean>標簽來向容器中添加組件,如下所示。
<?xml version=&34;1.0&34; encoding=&34;UTF-8&34;?>
<beans xmlns=&34;http://www.springframework.org/schema/beans&34;
xmlns:xsi=&34;http://www.w3.org/2001/XMLSchema-instance&34;
xsi:schemaLocation=&34;http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd&34;>
<bean id=&34;user01&34; class=&34;com.meimeixia.boot.bean.User&34;>
<property name=&34;name&34; value=&34;zhangsan&34;></property>
<property name=&34;age&34; value=&34;18&34;></property>
</bean>
<bean id=&34;cat&34; class=&34;com.meimeixia.boot.bean.Pet&34;>
<property name=&34;name&34; value=&34;tomcat&34;></property>
</bean>
</beans>
現在,容器中就會有兩個組件了,一個是User組件,一個是Pet組件。當然了,這是我們使用以前Spring XML配置文件的方式來向容器中注冊組件的。
但是,我們現在使用的是Spring Boot,它已經不推薦我們使用Spring XML配置文件的這種方式了,既然不用Spring XML配置文件,那么它是怎么給容器中添加組件的呢?笨蛋,當然是使用注解唄,而且Spring Boot還有好幾種辦法向容器中注冊組件呢!這里,我們先來看第一種辦法吧!
由于我們使用了Spring Boot之后,已經不再寫配置文件了,所以要向容器中注冊組件,我們得使用Spring [email protected],[email protected]?一個類,例如MyConfig,[email protected],如下所示。
package com.meimeixia.boot.config;
import org.springframework.context.annotation.Configuration;
@Configuration // 告訴Spring Boot這是一個配置類 == 配置文件
public class MyConfig {
}
其實,我們這樣的做法(即創建一個類,[email protected])就是類似于創建了一個配置文件,[email protected],就相當于告訴Spring Boot這個文件是一個Spring XML配置文件。你現在明白了吧!@Configuration注解就是來告訴Spring Boot它標注的類是一個配置類的。
以前,我們能在Spring XML配置文件里面能做什么,我們現在也能做什么。以前我們是在配置文件里面使用<bean>標簽向容器中添加組件的,現在,在配置類里面我們就不能寫標簽了,而是要寫一個方法,比如現在想要向容器中添加一個User組件,我們就得在MyConfig配置類里面編寫如下這樣一個方法。
package com.meimeixia.boot.config;
import com.meimeixia.boot.bean.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration // 告訴Spring Boot這是一個配置類 == 配置文件
public class MyConfig {
@Bean // @Bean注解是給容器中添加組件的。添加什么組件呢?以方法名作為組件的id,返回類型就是組件類型,返回的值就是組件在容器中的實例
public User user01() {
User zhangsan = new User(&34;zhangsan&34;, 18);
return zhangsan;
}
}
可以看到,[email protected],[email protected]?件呢?就拿以上user01方法來說,注冊的組件是這樣子的:
- 方法名(即user01)作為組件的id
- 方法的返回類型(即com.meimeixia.boot.bean.User)作為組件的類型
- 方法返回的值(即User對象)作為組件在容器中的實例
同樣的,我們也可以再向容器中注冊一個Pet組件,如下所示。
package com.meimeixia.boot.config;
import com.meimeixia.boot.bean.Pet;
import com.meimeixia.boot.bean.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration // 告訴Spring Boot這是一個配置類 == 配置文件
public class MyConfig {
@Bean // @Bean注解是給容器中添加組件的。添加什么組件呢?以方法名作為組件的id,返回類型就是組件類型,返回的值就是組件在容器中的實例
public User user01() {
User zhangsan = new User(&34;zhangsan&34;, 18);
return zhangsan;
}
@Bean
public Pet tomcatPet() {
return new Pet(&34;tomcat&34;);
}
}
這時,我們容器中就會有兩個組件了,那怎么來驗證容器中有兩個組件呢?很簡單,來到咱們的主程序類中,因為在主程序類中直接給我們返回了容器,所以我們就可以從容器中來獲取組件了。當然了,我們也可以先不獲取,而是先將容器中所有組件的名字全部打印出來,看打印出的這些名字里面有沒有我們想要的兩個組件的名字。
package com.meimeixia.boot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;
[email protected]
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(&34;com.meimeixia.boot&34;)
public class MainApplication {
public static void main(String[] args) {
// 1. 返回IoC容器,IoC容器里面就包含了當前應用的所有組件
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args); // 這是固定寫法喲
// 2. 我們可以來查看下IoC容器里面所有的組件,只要能查找到某一個組件,就說明這個組件是能工作的,至于怎么工作,這就是我們后來要闡述的原理了
String[] names = run.getBeanDefinitionNames(); // 獲取所有組件定義的名字
for (String name : names) {
System.out.println(name);
}
// 3. 從容器中獲取組件
}
}
我們在IDEA控制臺中以user01關鍵字來搜一下,發現確實能搜到我們注冊到容器中的組件,如下圖所示,而且還可以看到組件的名字默認就是方法名。
當然,你不想讓方法名作為組件名字,而是想給它一個自定義的名字,也是可以的喲!要知道,我們在Spring XML配置文件里面也是可以使用<bean>標簽為要注冊的組件起一個自定義的名字的。例如,想要為注冊的Pet組件起名為tom,[email protected]@Bean(“tom”)即可,如下所示。
package com.meimeixia.boot.config;
import com.meimeixia.boot.bean.Pet;
import com.meimeixia.boot.bean.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration // 告訴Spring Boot這是一個配置類 == 配置文件
public class MyConfig {
@Bean // @Bean注解是給容器中添加組件的。添加什么組件呢?以方法名作為組件的id,返回類型就是組件類型,返回的值就是組件在容器中的實例
public User user01() {
User zhangsan = new User(&34;zhangsan&34;, 18);
return zhangsan;
}
@Bean(&34;tom&34;)
public Pet tomcatPet() {
return new Pet(&34;tomcat&34;);
}
}
這樣,要注冊的Pet組件的名字就不再是方法名,而是我們自己定義的名字了。這時,不妨再來重新運行一下主程序類,然后我們再來IDEA控制臺中搜索一下,相信你很快就能找到名字為tom的組件了,如下圖所示。
而且,我們給容器中注冊的這兩個組件,它們默認都是單實例的喲,也就是說,我們無論從容器中獲取多少次,獲取的都是同一個實例。這兒,不妨我們來驗證一下吧!即我從容器中多次獲取名字為tom的寵物類型的組件,看看它們是不是同一個實例。
package com.meimeixia.boot;
import com.meimeixia.boot.bean.Pet;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;
[email protected]
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(&34;com.meimeixia.boot&34;)
public class MainApplication {
public static void main(String[] args) {
// 1. 返回IoC容器,IoC容器里面就包含了當前應用的所有組件
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args); // 這是固定寫法喲
// 2. 我們可以來查看下IoC容器里面所有的組件,只要能查找到某一個組件,就說明這個組件是能工作的,至于怎么工作,這就是我們后來要闡述的原理了
String[] names = run.getBeanDefinitionNames(); // 獲取所有組件定義的名字
for (String name : names) {
System.out.println(name);
}
// 3. 從容器中獲取組件
Pet tom01 = run.getBean(&34;tom&34;, Pet.class);
Pet tom02 = run.getBean(&34;tom&34;, Pet.class);
System.out.println(&34;組件是否為單實例:&34; + (tom01 == tom02));
}
}
再次重新運行主程序類,發現IDEA控制臺打印結果如下。
這已然說明了,我們注冊的組件默認就是單實例的。也就是說,你就算獲取無限次組件,獲取到的也是一樣的。
至此,我們來總結一下,[email protected],就是為了告訴Spring Boot該類是一個配置類,然后,[email protected]法上向容器中注冊組件了,而且,注冊的組件默認還是單實例的喲!
[email protected]個組件喲,也就是說配置類本身也是容器中的一個組件。不妨來驗證一下,即我們從容器中能不能獲取到配置類。
package com.meimeixia.boot;
import com.meimeixia.boot.bean.Pet;
import com.meimeixia.boot.config.MyConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;
[email protected]
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(&34;com.meimeixia.boot&34;)
public class MainApplication {
public static void main(String[] args) {
// 1. 返回IoC容器,IoC容器里面就包含了當前應用的所有組件
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args); // 這是固定寫法喲
// 2. 我們可以來查看下IoC容器里面所有的組件,只要能查找到某一個組件,就說明這個組件是能工作的,至于怎么工作,這就是我們后來要闡述的原理了
String[] names = run.getBeanDefinitionNames(); // 獲取所有組件定義的名字
for (String name : names) {
System.out.println(name);
}
// 3. 從容器中獲取組件
Pet tom01 = run.getBean(&34;tom&34;, Pet.class);
Pet tom02 = run.getBean(&34;tom&34;, Pet.class);
System.out.println(&34;組件是否為單實例:&34; + (tom01 == tom02));
// 配置類打印:com.meimeixia.boot.config.MyConfig$EnhancerBySpringCGLIB$4559f04d@49096b06
MyConfig bean = run.getBean(MyConfig.class);
System.out.println(bean);
}
}
再次重新運行主程序類,發現IDEA控制臺打印結果如下。
可以看到能打印出MyConfig配置類這個組件,所以,我們才說配置類本身也是組件。下面,[email protected],大家可一定要睜大眼睛看好喲?
在Spring Boot 2.0這個版本以后,@Configuration注解里面多了一個屬性,[email protected]看,如下圖所示。
可以看到,確實多了一個叫proxyBeanMethods的屬性,而且其默認值還是true。注意,該屬性是Spring Boot基于Spring 5.2而來的喲?
也就是說,[email protected]??解,默認其里面的proxyBeanMethods屬性是為true的,那proxyBeanMethods屬性有什么作用呢?它可有大作用了,而且它也是Spring Boot 2.0與Spring Boot 1.0的不同之處。proxyBeanMethods翻譯過來的話,應該就是代理bean的方法的意思了,這說的啥啊?要不我現在先來提出這樣一個大膽的猜想吧!就是拿到配置類這個組件之后,我們不妨來多次調用其里面的方法,例如user01方法,這時,會不會得到的是不一樣的對象呢?
package com.meimeixia.boot;
import com.meimeixia.boot.bean.Pet;
import com.meimeixia.boot.bean.User;
import com.meimeixia.boot.config.MyConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;
[email protected]
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(&34;com.meimeixia.boot&34;)
public class MainApplication {
public static void main(String[] args) {
// 1. 返回IoC容器,IoC容器里面就包含了當前應用的所有組件
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args); // 這是固定寫法喲
// 2. 我們可以來查看下IoC容器里面所有的組件,只要能查找到某一個組件,就說明這個組件是能工作的,至于怎么工作,這就是我們后來要闡述的原理了
String[] names = run.getBeanDefinitionNames(); // 獲取所有組件定義的名字
for (String name : names) {
System.out.println(name);
}
// 3. 從容器中獲取組件
Pet tom01 = run.getBean(&34;tom&34;, Pet.class);
Pet tom02 = run.getBean(&34;tom&34;, Pet.class);
System.out.println(&34;組件是否為單實例:&34; + (tom01 == tom02));
// 配置類打印:com.meimeixia.boot.config.MyConfig$EnhancerBySpringCGLIB$4559f04d@49096b06
MyConfig bean = run.getBean(MyConfig.class);
System.out.println(bean);
User user = bean.user01();
User user1 = bean.user01();
System.out.println(&34;(user == user1) = &34; + (user == user1));
}
}
我們知道配置類里面的user01方法就是給容器中注冊組件的,[email protected]類外面把user01這個方法多調了兩遍,那么該方法返回的User對象是從容器中拿的嗎?還是說這尼瑪就是一個普通方法的調用。不管了,咱們就直接運行主程序類來驗證一下,發現IDEA控制臺打印的結果如下圖所示。
也就是說,配置類里面組件注冊的方法,無論你在外面調多少遍,獲取到的都是容器中的單實例對象。
至此,我們再來總結一下,我們在外部無論對配置類中的組件注冊方法調用多少次,獲取到的都是之前注冊到容器中的單實例對象。所以,在IDEA控制臺這一塊,我們才能看到兩次調用user01方法獲取到的兩個User對象是同一個對象。
那么其原因何在呢?[email protected]?我們知道配置類本身也是組件,從IDEA控制臺打印出的結果中我們還能知道它并不是一個普通對象,而是一個被Spring CGLIB增強了的代理對象,所以,可以這樣說,我們獲取到的配置類組件本身就是一個代理對象。當我們在外部通過該代理對象來調用其方法時,那么Spring Boot里面的默認邏輯就是這樣子的,即Spring Boot默認會檢查容器中是否有該方法已經返回了的組件,若有則不會新創,而是直接拿,簡簡單單一句話,就是要保持組件單實例;若沒有則再來調用該方法創建一個新的,總之,Spring Boot總會檢查方法返回的組件是否在容器中存在。當然了,[email protected]s屬性的值為true(@Configuration(proxyBeanMethods = true)),即在配置類中的方法被代理的情況下(proxyBeanMethods翻譯過來就是代理bean的方法嘛),我們才能看到上述這種現象。
但是,[email protected]?的值置為false,[email protected]nfiguration(proxyBeanMethods = false),那么又是一種什么現象呢?我們不妨重新運行一下主程序類,給大家看看此時的效果。
可以看到,此時我們從容器中拿到的MyConfig類型的組件就不再是代理對象了,而是一個普普通通的對象,而且,還能看到兩次調用user01方法獲取到的兩個User對象不再是同一個對象了。
以上就是我們要說的proxyBeanMethods屬性的作用。而且,該屬性還另外引申出了這樣一個概念,Spring [email protected],它們分別是:
- Full:[email protected]nfiguration(proxyBeanMethods = true),一般稱為全配置
- Lite:[email protected]nfiguration(proxyBeanMethods = false),一般稱為輕量級配置
也就是說,以后我們想要給容器中添加組件的時候,不是會編寫一個配置類嘛,然后,[email protected]?true,那么配置類里面每一個向容器中注冊組件的方法在外面都能隨便調用,而且該方法都會去容器中找組件,這就是所謂的Full模式;[email protected]?false,那么配置類組件本身在容器中就再也不會被保存為代理對象了,這樣,當你在外面無限次調用其中的方法時,你的每一次方法調用就都會產生一個新的對象了,這就是所謂的Lite模式。
說了這么多,那么它適用于什么場景呢?我就直說了吧!組件依賴必須使用默認的Full模式,其他則默認使用Lite模式。還是舉一個例子來說吧,假設用戶要養一個寵物,那么在User類中是不是得加上一個Pet類型的屬性啊?
package com.meimeixia.boot.bean;
public class User {
private String name;
private Integer age;
private Pet pet;
public Pet getPet() {
return pet;
}
public void setPet(Pet pet) {
this.pet = pet;
}
public User() {
}
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return &34;User{&34; +
&34;name=&39;&34; + name + &39;&39;&39; +
&34;, age=&34; + age +
&34;, pet=&34; + pet +
&39;}&39;;
}
}
可以看到,我們又重寫了User類的toString方法。此時,相當于我們現在給容器中注冊了一個User類型的組件,但這個組件還想要用之前容器中注冊的寵物類型的組件。
我們不妨先使用一下默認的Full模式,[email protected]?為true,在這種模式下,用戶想要養寵物,是不是就得這樣啊?
package com.meimeixia.boot.config;
import com.meimeixia.boot.bean.Pet;
import com.meimeixia.boot.bean.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = true) // 告訴Spring Boot這是一個配置類 == 配置文件
public class MyConfig {
@Bean // @Bean注解是給容器中添加組件的。添加什么組件呢?以方法名作為組件的id,返回類型就是組件類型,返回的值就是組件在容器中的實例
public User user01() {
User zhangsan = new User(&34;zhangsan&34;, 18);
// User類型的組件依賴了Pet類型的組件
zhangsan.setPet(tomcatPet());
return zhangsan;
}
@Bean(&34;tom&34;)
public Pet tomcatPet() {
return new Pet(&34;tomcat&34;);
}
}
從上可以看到,用戶養的寵物就是容器中的寵物,這說明了User類型的組件依賴了Pet類型的組件。而且,在默認的Full模式下,這種組件依賴是成立的,對此,我們不妨來確認一下。
package com.meimeixia.boot;
import com.meimeixia.boot.bean.Pet;
import com.meimeixia.boot.bean.User;
import com.meimeixia.boot.config.MyConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;
[email protected]
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(&34;com.meimeixia.boot&34;)
public class MainApplication {
public static void main(String[] args) {
// 1. 返回IoC容器,IoC容器里面就包含了當前應用的所有組件
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args); // 這是固定寫法喲
// 2. 我們可以來查看下IoC容器里面所有的組件,只要能查找到某一個組件,就說明這個組件是能工作的,至于怎么工作,這就是我們后來要闡述的原理了
String[] names = run.getBeanDefinitionNames(); // 獲取所有組件定義的名字
for (String name : names) {
System.out.println(name);
}
// 3. 從容器中獲取組件
Pet tom01 = run.getBean(&34;tom&34;, Pet.class);
Pet tom02 = run.getBean(&34;tom&34;, Pet.class);
System.out.println(&34;組件是否為單實例:&34; + (tom01 == tom02));
// 配置類打印:com.meimeixia.boot.config.MyConfig$EnhancerBySpringCGLIB$4559f04d@49096b06
MyConfig bean = run.getBean(MyConfig.class);
System.out.println(bean);
User user = bean.user01();
User user1 = bean.user01();
System.out.println(&34;(user == user1) = &34; + (user == user1));
User user01 = run.getBean(&34;user01&34;, User.class);
Pet tom = run.getBean(&34;tom&34;, Pet.class);
System.out.println(&34;用戶的寵物:&34; + (user01.getPet() == tom));
}
}
再次重新運行主程序類,發現IDEA控制臺打印出了如下結果。
可以看到,用戶養的寵物就是容器中的寵物。你現在該知道,使用默認的Full模式,就能很方便地來解決組件依賴的問題了吧!
但是,[email protected]?的值置為false,那么會導致什么現象出現呢?你會發現連編譯都通不過去,在調用tomcatPet方法時,發現它下面有一個紅色的波浪線,如下圖所示。
代碼編譯都通不過,接下來就不用測試了吧!
以上就是我們Spring Boot 2.0最大的一個更新,即我們可以將配置類編寫為輕量級模式和全模式這兩種配置模式。
那么,將配置類編寫為輕量級模式有什么優點呢?優點就是Spring Boot不會來檢查組件注冊的方法(例如user01方法)返回的東東在容器中有沒有,相當于就跳過了檢查,這樣,我們整個Spring Boot應用啟動并運行起來就非常快了。而如果是將配置類編寫為全模式,那么外界對它里面的方法的每一次調用,Spring Boot都會檢查容器中是不是有了該方法已經返回了的組件。
雖然Spring Boot給我們帶來了Full和Lite這兩種配置模式,但是在Spring Boot的最佳實踐中,我們推薦的做法是這樣子的——如果我們只是向容器中單單注冊組件,而且也不存在組件依賴,那么一般使用Lite模式,因為這樣的話,我們Spring Boot應用整個的啟動速度就會非常快,加載起來也會非常快喲;如果存在組件依賴,那么一般使用Full模式,因為這樣能保證某一個組件它所依賴的組件就是容器中的組件。
這就是Spring Boot 2.0里面,@[email protected]?用方法,而且我們也看到了這跟以前是有大大的不同喲,大家未來會在Spring Boot底層見到非常多這樣的寫法。
作者:李阿昀
原文鏈接:http://blog.csdn.net/yerenyuan_pku/article/details/116201120