目錄
讀取配置文件屬性
前不久剛優化了一個關於文件上傳這塊的代碼,這裏面就涉及到圖片的路徑問題,我將某些屬性配置到了配置文件,但是在優化過程中,讓我對bean有了新的認識,既然牽扯到配置文件屬性的讀取,那麼我們先來看看怎麼獲取配置文件的屬性。
@Value註解
在配置文件中編寫一些屬性(application.yml)
server:
port: 9199
file:
server-name: oss.file.com/ #服務器的域名
mapper: file/ #映射的名字
images: images/ #存儲圖片的文件夾
video: video/ #存放視頻的文件夾
FileConfig類
package com.ymy.config;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
@Data
@Configuration
public class FileConfig {
/**
* 服務器名稱
*/
@Value("${file.server-name}")
private String serverName;
/**
* 映射
*/
@Value("${file.mapper}")
private String mapper;
/**
* 存放圖片的文件夾
*/
@Value("${file.images}")
private String images;
/**
* 存放視頻的文件夾
*/
@Value("${file.video}")
private String video;
}
@Data:lombok依賴中的註解,他會生成對應的get、Set、toString方法,簡便了我們的代碼量。
@Configuration 裝配,被它裝配的bean,回被spring的上下文掃描,這個有點類似springmvc的xml配中的bean,這個註解一定要加,否者讀取不到配置文件,當然也可以替換成@Component。
@Value:用於讀取配置文件的屬性,格式 ${對應配置文件中的屬性}。
單元測試
package com.ymy;
import com.ymy.config.FileConfig;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
@Slf4j
class StaticConfigVarApplicationTests {
@Autowired
private FileConfig fileConfig;
@Test
void valueTest() {
//比如images目錄下存放着一張123.jpg的圖片;video目錄下存放着123.mp4
log.info("服務名:{},映射:{},存儲圖片的文件夾:{},存放視頻的文件夾:{}",fileConfig.getServerName(),fileConfig.getMapper(),fileConfig.getImages(),fileConfig.getVideo());
log.info("圖片目錄:{}",fileConfig.getServerName()+fileConfig.getMapper()+fileConfig.getImages()+"123.jpg");
log.info("視頻目錄:{}",fileConfig.getServerName()+fileConfig.getMapper()+fileConfig.getVideo()+"123.mp4");
}
}
測試結果如下:
2020-03-19 15:56:42.227 INFO 19884 --- [ main] com.ymy.StaticConfigVarApplicationTests : 服務名:oss.file.com/,映射:file/,存儲圖片的文件夾:images/,存放視頻的文件夾:video/
2020-03-19 15:56:42.229 INFO 19884 --- [ main] com.ymy.StaticConfigVarApplicationTests : 圖片目錄:oss.file.com/file/images/123.jpg
2020-03-19 15:56:42.230 INFO 19884 --- [ main] com.ymy.StaticConfigVarApplicationTests : 視頻目錄:oss.file.com/file/video/123.mp4
看到這樣的結果,說明讀取配之文件已經成功了,但是這個還有一點讓我很不爽,那就是FileConfig對象還需要注入,能不能不通過注入就能拿到配置文件的屬性呢?這個我等會再說,我們來看看還有沒有其他讀取配置文件的方式呢?答案肯定是有的,接着往下看。
@ConfigurationProperties
它的實現方式很簡單,只需要改動一個地方即可,請看代碼:
package com.ymy.config;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Data
@Slf4j
@Configuration
@ConfigurationProperties(prefix = "file")
public class FileConfig {
/**
* 服務器名稱
*/
//@Value("${file.server-name}")
private String serverName;
/**
* 映射
*/
//@Value("${file.mapper}")
private String mapper;
/**
* 存放圖片的文件夾
*/
//@Value("${file.images}")
private String images;
/**
* 存放視頻的文件夾
*/
// @Value("${file.video}")
private String video;
}
我們在屬性上去掉了@Value註解,在bean中加入了:@ConfigurationProperties(prefix = “file”)。
@ConfigurationProperties():這個註解可以讓bean的屬性與配置文件的屬性一一對應,prefix:配置文件的前綴,格式要求:bean屬性要與配置文件的屬性名相同。
其他代碼都不需要改動,我們直接運行單元測試:
2020-03-19 16:21:00.271 INFO 1456 --- [ main] com.ymy.StaticConfigVarApplicationTests : 服務名:oss.file.com/,映射:file/,存儲圖片的文件夾:images/,存放視頻的文件夾:video/
2020-03-19 16:21:00.273 INFO 1456 --- [ main] com.ymy.StaticConfigVarApplicationTests : 圖片目錄:oss.file.com/file/images/123.jpg
2020-03-19 16:21:00.273 INFO 1456 --- [ main] com.ymy.StaticConfigVarApplicationTests : 視頻目錄:oss.file.com/file/video/123.mp4
我們發現同樣讀取到了配置文件的信息,這種方式要求bean的屬性名與配置文件的屬性名相同,否者將讀取不到,不知道你們有沒有發現一個問題,那就是FileConfig定義的服務器名稱是:serverName,配置文件配置的是:server-name,很神奇的是他居然找到了,爲什麼呢?
@ConfigurationProperties會默認去掉屬性名中間的特殊符號,並且不區分大小寫
Environment對象
上面的兩種讀取方式可能很多人都知道,但是有人瞭解過Environment這個對象嗎?
Environment是集成在容器中的抽象,它爲 application 環境的兩個 key 方面建模:profiles和properties。
profile 是 bean 定義的命名邏輯 group,僅當給定的 profile 爲 active 時才向容器註冊。 Beans 可以分配給 profile,無論是用 XML 定義還是通過 annotations 定義。與 profiles 相關的Environment object 的作用是確定哪些 profiles(如果有)當前是 active,以及哪些 profiles(如果有)默認情況下應該 active。
Properties 在幾乎所有 applications 中都發揮着重要作用,可能源自各種來源:properties files,JVM 系統 properties,系統環境變量,JNDI,servlet context 參數,ad-hoc Properties objects,Maps 等。與 properties 相關的Environment object 的作用是爲用戶提供方便的服務接口,用於配置 property 源和從中解析 properties。
這是官網的介紹,看着是否很暈?我們只需要抓中重點即可,那就是Environment管理着所有的配置文件。
我們一起來看看Environment是如何獲取配置文件屬性的,上代碼:
package com.ymy;
import com.ymy.config.FileConfig;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.env.Environment;
@SpringBootTest
@Slf4j
class StaticConfigVarApplicationTests {
// @Autowired
// private FileConfig fileConfig;
@Autowired
private Environment environment;
// @Test
// void valueTest() {
// //比如images目錄下存放着一張123.jpg的圖片;video目錄下存放着123.mp4
// log.info("服務名:{},映射:{},存儲圖片的文件夾:{},存放視頻的文件夾:{}",fileConfig.getSERVERNAME(),fileConfig.getMapper(),fileConfig.getImages(),fileConfig.getVideo());
// log.info("圖片目錄:{}",fileConfig.getSERVERNAME()+fileConfig.getMapper()+fileConfig.getImages()+"123.jpg");
// log.info("視頻目錄:{}",fileConfig.getSERVERNAME()+fileConfig.getMapper()+fileConfig.getVideo()+"123.mp4");
// }
@Test
void environmentTest() {
log.info("服務名:{}",environment.getProperty("file.server-name"));
log.info("映射:{}",environment.getProperty("file.mapper"));
log.info("存放圖片的文件夾:{}",environment.getProperty("file.imagese"));
log.info("存放視頻的文件夾:{}",environment.getProperty("file.video"));
}
}
我們運行單元測試查看結果
2020-03-19 21:20:19.855 INFO 1680 --- [ main] com.ymy.StaticConfigVarApplicationTests : 服務名:oss.file.com/
2020-03-19 21:20:19.857 INFO 1680 --- [ main] com.ymy.StaticConfigVarApplicationTests : 映射:file/
2020-03-19 21:20:19.857 INFO 1680 --- [ main] com.ymy.StaticConfigVarApplicationTests : 存放圖片的文件夾:images/
2020-03-19 21:20:19.857 INFO 1680 --- [ main] com.ymy.StaticConfigVarApplicationTests : 存放視頻的文件夾:video/
沒錯,就是這麼簡單,是不是感覺到上面的三種獲取配置文件的方式很絲滑?其實還有幾種實現方式,我這裏做一下代碼的展示,具體的測試已經結果就不做展示了,爲了開篇說的問題能儘快和我們見面,下面我直接進行重點的代碼展示。
Properties讀取
package com.ymy.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import java.io.IOException;
import java.util.Properties;
@Slf4j
public class MyPropertiesConfig {
/**
* 讀取配置文件
* @param relpath 相對路徑 classpath目錄下
* @return
*/
public static Properties get(String relpath){
Properties p = null;
try {
p = PropertiesLoaderUtils.loadProperties(new EncodedResource(new ClassPathResource(relpath),"utf-8"));
} catch (IOException e) {
e.printStackTrace();
}
return p;
}
}
@Test
void PeopertiesTest() {
Properties properties = MyPropertiesConfig.get("application.yml");
System.out.println(properties.getProperty("server-name"));
}
Properties讀取的配置文件屬性和之前的幾種有點不一樣,它不需要前綴,因爲Properties是將配置文件的每一行做處理,比如file下面的server-name被拆分成了兩個鍵值對,file="" servername=oss.file.com/,所以使用這種方式讀取配置文件的時候需要注意。
@PropertySource
這種讀取方式會有一種弊端,那就是會將註釋一起讀取出來,所以再使用這種方式讀取配置文件信息的時候一定要注意,話不多說,直接上代碼
package com.ymy.config;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
@Data
@Slf4j
@Configuration
@ConfigurationProperties
@PropertySource(value = "classpath:application.yml",encoding = "utf-8")
public class FileConfigByPropertisSource {
/**
* 服務器名稱
*/
//@Value("${file.server-name}")
private String servername;
/**
* 映射
*/
//@Value("${file.mapper}")
private String mapper;
/**
* 存放圖片的文件夾
*/
//@Value("${file.images}")
private String images;
/**
* 存放視頻的文件夾
*/
// @Value("${file.video}")
private String video;
}
@Test
void fileConfigByPropertisSourceTest() {
log.info("服務名:{}",fileConfigByPropertisSource.getServername());
}
這種讀取方式還是藉助了@ConfigurationProperties,比較好的一點是它可以加載指定的配置文件,這裏我沒有指定前綴,所以這裏肯定是讀取不到的,要想得到我們在配置文件中配置的那幾個屬性,我們還是需要@ConfigurationProperties(prefix = “file”)這個玩意,@ConfigurationProperties配合@PropertySource的優勢可以讀取指定的配置文件,如果沒有@PropertySource,那麼只能讀取到application/yml/application.properties文件。
讀取配置文件的方式就先介紹到這裏,下面我們一起來看看我遇到的是什麼問題?是一個很詭異的問題。
靜態變量讀取配置文件屬性
沒錯,我是在使用靜態變量讀取配置文件屬性的時候發生的詭異問題,我先說說是怎麼發生的,然後在用代碼走一遍。
我在優化文件上傳所以來的配置文件的時候發現使用@Value註解獲取的配置文件屬性,需要在使用的地方注入管理配置文件的Bean,這樣我看着很不爽,所以我想到使用靜態變量去讀取這些配置屬性,請看代碼。
package com.ymy.config;
import com.ymy.enums.FileTypeEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
@Slf4j
@Configuration
public class FileConfig {
/**
* 服務器名稱
*/
private static String servername;
/**
* 映射
*/
private static String mapper;
/**
* 存放圖片的文件夾
*/
private static String images;
/**
* 存放視頻的文件夾
*/
private static String video;
/**
* 存放apk的文件夾
*/
private static String apk;
@Value("${file.server-name}")
public void setServername(String servername) {
this.servername = servername;
}
@Value("${file.mapper}")
public void setMapper(String mapper) {
this.mapper = mapper;
}
@Value("${file.images}")
public void setImages(String images) {
this.images = images;
}
@Value("${file.video}")
public void setVideo(String video) {
this.video = video;
}
@Value("${file.apk}")
public void setApk(String apk) {
FileConfig.apk = apk;
}
public static String getImages() {
return images;
}
public static String getVideo() {
return video;
}
public static String getApk() {
return apk;
}
/**
* 根據圖片的相對路徑查詢展示的絕對路徑
* @param relPath
* @return
*/
public static String getImagePath(String relPath){
return getlocation(relPath,FileTypeEnum.IMG.value());
}
/**
* 根據視頻的相對路徑查詢展示的絕對路徑
* @param relPath
* @return
*/
public static String getVideoPath(String relPath){
return getlocation(relPath,FileTypeEnum.VIDEO.value());
}
/**
* 根據apk的相對路徑查詢展示的絕對路徑
* @param relPath
* @return
*/
public static String getApkPath(String relPath){
return getlocation(relPath,FileTypeEnum.APK.value());
}
/**
* 根據相對路徑與類型獲取到對應的絕對路徑
* @param relPath 相對路徑
* @param type 類型
* @return
*/
private static String getlocation(String relPath, Integer type) {
return servername+mapper+FileTypeFactory.map.get(type)+relPath;
}
}
解釋一下上面的代碼
1.定義靜態變量接收配置文件屬性
2.分別給上set方法(set方法必須是靜態的)。
3.在set方法上使用@Value註解。
4.裝配bean,在類中加上@Configuration註解
這樣,就能讓靜態變量獲取到配置文件的屬性了,而我有繼續做了一個騷操作,那就是使用一個枚舉類維護文件的類型,比如:1=圖片;2=視頻;3=apk,然後又創建了一個工廠,用於管理這三種類型,代碼如下:
枚舉類
package com.ymy.enums;
public enum FileTypeEnum {
/**
* 圖片
*/
IMG(1),
/**
* 視頻
*/
VIDEO(2),
/**
* apk
*/
APK(3);
private Integer index;
private FileTypeEnum(Integer value) { // 必須是private的,否則編譯錯誤
this.index = value;
}
public Integer value() {
return this.index;
}
}
工廠類
package com.ymy.config;
import com.ymy.enums.FileTypeEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Configuration
@Slf4j
public class FileTypeFactory {
public static final Map<Integer,String> map = new HashMap<Integer, String>();
static {
map.put(FileTypeEnum.IMG.value(), FileConfig.getImages());
map.put(FileTypeEnum.VIDEO.value(),FileConfig.getVideo());
map.put(FileTypeEnum.APK.value(),FileConfig.getApk());
}
}
然而問題也就隨着到來了,請看單元測試
@Test
void pathTest() {
log.info("圖片文件夾:{},視頻文件夾:{},apk文件夾:{}",FileConfig.getImages(),FileConfig.getVideo(),FileConfig.getApk());
String relImgPath = "123.png";
String relVideoPath = "123.mp4";
String relApkPath = "123.apk";
log.info("圖片的絕對路徑:{}",FileConfig.getImagePath(relImgPath));
log.info("視頻的絕對路徑:{}",FileConfig.getVideoPath(relVideoPath));
log.info("apk的絕對路徑:{}",FileConfig.getApkPath(relApkPath));
}
這個時候我們會發現一個神奇的問題,那就是獲取文件絕對路徑中有null值,但是分別獲取他們的文件夾名稱卻有值,請看結果:
2020-03-20 15:19:44.545 INFO 16956 --- [ main] com.ymy.StaticConfigVarApplicationTests : 圖片文件夾:images/,視頻文件夾:video/,apk文件夾:apk/
2020-03-20 15:19:44.547 INFO 16956 --- [ main] com.ymy.StaticConfigVarApplicationTests : 圖片的絕對路徑:oss.file.com/file/null123.png
2020-03-20 15:19:44.547 INFO 16956 --- [ main] com.ymy.StaticConfigVarApplicationTests : 視頻的絕對路徑:oss.file.com/file/null123.mp4
2020-03-20 15:19:44.547 INFO 16956 --- [ main] com.ymy.StaticConfigVarApplicationTests : apk的絕對路徑:oss.file.com/file/null123.apk
我就是用工廠代理了一下,就找不到目錄了?
FileConfig.getImages()能拿到值說明靜態變量images已經獲取到了配置文件的屬性,但爲什麼被工廠代理一下這個值就沒了呢?
這心態崩了啊,這能忍?我決定要深挖到底,我要搞明白是什麼原因讓我拿不到這三個值,如何分析問題?那我們就需要從bean的執行順序講起了。
Bean內部代碼加載順序
bean內部有靜態代碼塊、代碼塊、構造函數,還有一些方法,但是隻有前三個在實例化Bean的時候都會被執行,那麼他們的執行順序是什麼樣的?
我們直接用代碼接證明它們的執行順序,我們就改造一下FileTypeFactory這個工廠
package com.ymy.config;
import com.ymy.enums.FileTypeEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Slf4j
public class FileTypeFactory {
public FileTypeFactory(){
log.info("我是文件類型工廠的構造函數,我被加載了");
}
public static final Map<Integer,String> map = new HashMap<Integer, String>();
static {
log.info("我是文件類型工廠的靜態代碼塊,我被加載了");
map.put(FileTypeEnum.IMG.value(), FileConfig.getImages());
map.put(FileTypeEnum.VIDEO.value(),FileConfig.getVideo());
map.put(FileTypeEnum.APK.value(),FileConfig.getApk());
}
{
log.info("我是文件類型工廠的代碼塊,我被加載了");
}
}
單元測試
@Test
void loadTest() {
FileTypeFactory f1 = new FileTypeFactory();
FileTypeFactory f2 = new FileTypeFactory();
}
FileTypeFactory對象被我實例化了兩次,至於爲什麼要實例化兩次,一次不就能看到它們的執行順序了嗎?我們先來看執行結果
2020-03-20 15:52:02.700 INFO 12940 --- [ main] com.ymy.config.FileTypeFactory : 我是文件類型工廠的靜態代碼塊,我被加載了
2020-03-20 15:52:02.701 INFO 12940 --- [ main] com.ymy.config.FileTypeFactory : 我是文件類型工廠的代碼塊,我被加載了
2020-03-20 15:52:02.701 INFO 12940 --- [ main] com.ymy.config.FileTypeFactory : 我是文件類型工廠的構造函數,我被加載了
2020-03-20 15:52:02.701 INFO 12940 --- [ main] com.ymy.config.FileTypeFactory : 我是文件類型工廠的代碼塊,我被加載了
2020-03-20 15:52:02.701 INFO 12940 --- [ main] com.ymy.config.FileTypeFactory : 我是文件類型工廠的構造函數,我被加載了
初始化第一個實例:靜態代碼塊最先加載 -->代碼塊被加載 —>構造函數被加載
所以我們基本上可以得出一個結論:靜態代碼塊 >代碼塊>構造函數
現在請看初始化第二個實例,我們發現居然只執行了代碼塊與構造函數,所以在這裏我們又可以得到一個結論,那就是:靜態代碼塊只會在第一次實例化bean的時候被加載。
Bean與Bean之前的執行順序
我現在想知道bean與bean之前的執行順序,我新建了三個bean:user1、user2、user3,並且分別在它們內部寫上了靜態代碼塊、代碼塊以及構造函數,然後我們運行主程序看看這三個bean的加載順序。
package com.ymy.test;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
@Slf4j
@Configuration
public class User1 {
public User1(){
log.info("我是User1的構造函數");
}
{
log.info("我是User1的代碼塊");
}
static {
log.info("我是User1的靜態代碼塊");
}
}
package com.ymy.test;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
@Slf4j
@Configuration
public class User2 {
public User2(){
log.info("我是User2的構造函數");
}
{
log.info("我是User2的代碼塊");
}
static {
log.info("我是User2的靜態代碼塊");
}
}
package com.ymy.test;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
@Slf4j
@Configuration
public class User3 {
public User3(){
log.info("我是User3的構造函數");
}
{
log.info("我是User3的代碼塊");
}
static {
log.info("我是User3的靜態代碼塊");
}
}
請注意,我這裏使用了@Configuration註解,由於我這裏新建的是springboot項目,所以我直接啓動mian方法,我們一起來看執行結果:
2020-03-20 16:11:40.885 INFO 8932 --- [ main] com.ymy.StaticConfigVarApplication : Starting StaticConfigVarApplication on LAPTOP-3GLHJRE9 with PID 8932 (D:\springboot\static-config-var\target\classes started by admin in D:\springboot)
2020-03-20 16:11:40.887 INFO 8932 --- [ main] com.ymy.StaticConfigVarApplication : No active profile set, falling back to default profiles: default
2020-03-20 16:11:41.447 INFO 8932 --- [ main] com.ymy.test.User1 : 我是User1的靜態代碼塊
2020-03-20 16:11:41.448 INFO 8932 --- [ main] com.ymy.test.User2 : 我是User2的靜態代碼塊
2020-03-20 16:11:41.451 INFO 8932 --- [ main] com.ymy.test.User3 : 我是User3的靜態代碼塊
2020-03-20 16:11:41.692 INFO 8932 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 9199 (http)
2020-03-20 16:11:41.699 INFO 8932 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2020-03-20 16:11:41.699 INFO 8932 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.31]
2020-03-20 16:11:41.778 INFO 8932 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2020-03-20 16:11:41.779 INFO 8932 --- [ main] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 858 ms
2020-03-20 16:11:41.831 INFO 8932 --- [ main] com.ymy.test.User1 : 我是User1的代碼塊
2020-03-20 16:11:41.831 INFO 8932 --- [ main] com.ymy.test.User1 : 我是User1的構造函數
2020-03-20 16:11:41.831 INFO 8932 --- [ main] com.ymy.test.User2 : 我是User2的代碼塊
2020-03-20 16:11:41.831 INFO 8932 --- [ main] com.ymy.test.User2 : 我是User2的構造函數
2020-03-20 16:11:41.832 INFO 8932 --- [ main] com.ymy.test.User3 : 我是User3的代碼塊
2020-03-20 16:11:41.832 INFO 8932 --- [ main] com.ymy.test.User3 : 我是User3的構造函數
2020-03-20 16:11:41.958 INFO 8932 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2020-03-20 16:11:42.172 INFO 8932 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 9199 (http) with context path ''
2020-03-20 16:11:42.176 INFO 8932 --- [ main] com.ymy.StaticConfigVarApplication : Started StaticConfigVarApplication in 1.764 seconds (JVM running for 2.908)
程序加載完成之後,我們發現user1、user2、user3的靜態代碼塊被最先執行,在分別執行了他們各自的代碼塊以及構造函數,我們還發現這三個bean的執行順序是從上往下,所以在多個bean之間使用@Configuration裝配的bean之間的執行順序:
user1靜態代碼塊 > user2靜態代碼塊 > user3靜態代碼塊 > user1代碼塊 > user1構造函數 >
user2代碼塊 > user2構造函數 > user3代碼塊 > user3構造函數
看到這種執行順序我好像想通了爲什麼工廠拿不到FileConfig的屬性,我們一起來看
我在FileConfig bean中添加了構造函數,因爲只有被實例化了屬性纔會被賦值
FileConfig(){
log.info("這是文件配置類的構造函數");
}
FileTypeFactory工廠沒有做修改,我們再此啓動項目查看結果
2020-03-20 16:30:43.965 INFO 11920 --- [ main] com.ymy.StaticConfigVarApplicationTests : Starting StaticConfigVarApplicationTests on LAPTOP-3GLHJRE9 with PID 11920 (started by admin in D:\springboot\static-config-var)
2020-03-20 16:30:43.966 INFO 11920 --- [ main] com.ymy.StaticConfigVarApplicationTests : No active profile set, falling back to default profiles: default
2020-03-20 16:30:44.731 INFO 11920 --- [ main] com.ymy.config.FileTypeFactory : 我是文件類型工廠的靜態代碼塊,我被加載了
2020-03-20 16:30:44.909 INFO 11920 --- [ main] com.ymy.config.FileConfig : 這是文件配置類的構造函數
2020-03-20 16:30:44.939 INFO 11920 --- [ main] com.ymy.config.FileTypeFactory : 我是文件類型工廠的代碼塊,我被加載了
2020-03-20 16:30:44.940 INFO 11920 --- [ main] com.ymy.config.FileTypeFactory : 我是文件類型工廠的構造函數,我被加載了
看到這個證實了我的想法,工廠類的靜態代碼塊被先加載,配置類的構造被後加載,所以導致了讀取屬性爲空的情況,由於靜態代碼塊是對象被初始化的時候執行,那我只需要將他放到配置類後執行就可以了啊,最簡單有效的方式就是去掉FileTypeFactory工廠類的@Configuration註解,這樣,程序啓動的時候就不會加載工廠,而是在被調用的時候加載,這個時候配置類早就加載完成了,之前爲啥要手賤去加一個註解呢?可能是腦子進水了,不過幸虧加了這個註解讓我學習了一波@Configuration註解與@Component註解的區別。
@Configuration註解與@Component註解的區別
剛剛分析的代碼中我們並沒有牽扯到@Component註解,爲什麼會學習了一波他們的區別呢?我之前一直認爲他們的作用是一致的,知道我測試的時候使用了他們兩個之後,我發現我錯了。
從表面看@Configuration屬於@Component的派生類,所以他們兩個@Component能幹的事情@Configuration應該也能幹,這樣的認知雖然沒錯,但是不嚴謹,我們還是使用剛剛新建的user1、2、3來說明他們的區別,我們將他們的裝配註解替換成@Component
其他都不需要修改,我們直接來看他們的執行順序
2020-03-20 16:42:40.274 INFO 21048 --- [ main] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 725 ms
2020-03-20 16:42:40.311 INFO 21048 --- [ main] com.ymy.test.User1 : 我是User1的靜態代碼塊
2020-03-20 16:42:40.311 INFO 21048 --- [ main] com.ymy.test.User1 : 我是User1的代碼塊
2020-03-20 16:42:40.311 INFO 21048 --- [ main] com.ymy.test.User1 : 我是User1的構造函數
2020-03-20 16:42:40.311 INFO 21048 --- [ main] com.ymy.test.User2 : 我是User的靜態代碼塊
2020-03-20 16:42:40.311 INFO 21048 --- [ main] com.ymy.test.User2 : 我是User的代碼塊
2020-03-20 16:42:40.311 INFO 21048 --- [ main] com.ymy.test.User2 : 我是User的構造函數
2020-03-20 16:42:40.311 INFO 21048 --- [ main] com.ymy.test.User3 : 我是User3的靜態代碼塊
2020-03-20 16:42:40.311 INFO 21048 --- [ main] com.ymy.test.User3 : 我是User3的代碼塊
2020-03-20 16:42:40.311 INFO 21048 --- [ main] com.ymy.test.User3 : 我是User3的構造函數
還記得被@Configuration裝配的bean的執行順序嗎?
user1靜態代碼塊 > user2靜態代碼塊 > user3靜態代碼塊 > user1代碼塊 > user1構造函數 >
user2代碼塊 > user2構造函數 > user3代碼塊 > user3構造函數
發現什麼了嗎?被@Component裝配的bean執行順序居然發生改變了,執行順序如下:
user1靜態代碼塊 > user1代碼塊 > user1構造函數 > user2靜態代碼塊 > user2代碼塊
user2構造函數 > user3靜態代碼塊 > user3代碼塊 > user3構造函數
可以看出被這兩個裝配的bean執行順序發生了很大的改變,這是爲什麼呢?
爲什麼@Configuration註解與@Component註解裝配的bean執行順序不一樣?
那是因爲@Configuration註解是被動態代理的(CGLIB),很容易理解,執行完靜態代碼塊之後,真正的實例代碼塊這些都會在代理中執行,而不是它本身,所以我們會看到所有被@Configuration裝配的bean都是先執行了靜態代碼塊,而後面猜分別執行他們的構造函數,而@Component註解卻沒有,所以它嚴格的按照這bean的執行順序執行,這也就是我們看到的爲什麼@Configuration註解與@Component註解裝配的bean執行順序不一樣。如果你們對spring的註解感興趣的話,我這裏有一個spring的中文官方文檔:spring中文官方文檔。
總結
寫到這裏就結束了,spring還是那麼的強大,我們對它的瞭解還是那麼的細微,本人最近在努力的加深對spring的理解,希望以後會寫出更漂亮的文章,如果不是這次的小優化,我可能一直還是以爲@Configuration註解與@Component註解就是一個東西,真的是活到老,學到老啊。