1 自動配置
Spring boot的一大特性就是“自動配置”。在傳統(tǒng)的Spring應(yīng)用開發(fā)中辐董,開發(fā)者往往需要寫很多的XML配置項(xiàng)甚亭,包括數(shù)據(jù)源的配置,組件Bean的配置怔匣,數(shù)據(jù)庫事務(wù)的配置等等握联,但如果使用Spring Boot的話,往往不需要做這些配置劫狠,只需要添加對應(yīng)的依賴庫即可拴疤,即所謂的“開箱即用”,這可以讓應(yīng)用開發(fā)者把更多的精力放在業(yè)務(wù)邏輯上独泞,而不是一大堆的亂七八糟的配置上呐矾,對于這點(diǎn),我想只要有傳統(tǒng)SSM項(xiàng)目開發(fā)經(jīng)驗(yàn)以及Spring Boot開發(fā)經(jīng)驗(yàn)的朋友應(yīng)該能體會到懦砂。
那Spring Boot自動配置的原理是什么呢蜒犯?它又是如何實(shí)現(xiàn)的呢组橄?
Spring Boot的自動配置關(guān)鍵是spring-boot-autoconfigure依賴,如果仔細(xì)觀察罚随,會發(fā)現(xiàn)Spring boot starter包含了該依賴玉工,如下圖所示(我這里用的2.0.0版本):
pring-boot-autoconfigure包含了很多自動配置項(xiàng),例如JPA的自動配置淘菩,Kafka的自動配置等遵班,如下圖所示(僅截了部分):
進(jìn)一步查看源碼,會發(fā)現(xiàn)每一個包下都有一個XXXAutoConfiguration(XXX表示就是組件名稱潮改,例如JPA狭郑,Kafka等),下面是KafkaAutoConfiguration的部分源碼:
@Configuration
@ConditionalOnClass(KafkaTemplate.class)
@EnableConfigurationProperties(KafkaProperties.class)
@Import(KafkaAnnotationDrivenConfiguration.class)
public class KafkaAutoConfiguration {
private final KafkaProperties properties;
private final RecordMessageConverter messageConverter;
.........
}
- @Configuration注解表示這是一個配置類汇在,用過Spring Boot的朋友都應(yīng)該知道是什么東西翰萨,不多做解釋了。
- @ConditionalOnClass(KafkaTemplate.class)糕殉,該注解表示當(dāng)KafkaTemplate.class被加載到JVM中亩鬼,才會對配置類進(jìn)行初始化,簡單理解就是把他當(dāng)做if語句來看阿蝶。
- @EnableConfigurationProperties(KafkaProperties.class)雳锋,加載屬性配置類的注解,有這個注解羡洁,KafkaProperties才會作用于應(yīng)用上下文中魄缚。
- @Import(KafkaAnnotationDrivenConfiguration.class),Spring 的基礎(chǔ)注解焚廊,不多說了冶匹。
如果你用IDEA來查看該源碼,且你的項(xiàng)目中沒有包含Spring Kafka相關(guān)的依賴項(xiàng)咆瘟,應(yīng)該會看到KafkaTemplate被標(biāo)紅了嚼隘,即IDE找不到該類,所以最終KafkaAutoConfiguration不會被初始化袒餐,項(xiàng)目中也不會存在KafkaTemplate這個Bean飞蛹。而如果此時加入Spring Kafka的依賴項(xiàng),那么KafkaAutoConfiguration就會被初始化灸眼,最終應(yīng)用中會存在KafkaTemplate這個Bean卧檐,用戶可以直接依賴注入到需要用到的地方而不需要做什么配置,因?yàn)镵afkaAutoConfiguration這個類里都幫我們做了一些默認(rèn)的配置焰宣。
如果項(xiàng)目確實(shí)對KafkaTemplate有什么特殊的配置霉囚,仍然可以選擇自己手動配置,一般有兩種方法:
- 自己創(chuàng)建一個KafkaTemplate的Bean匕积,這個應(yīng)該不難盈罐。在配置類中用@Bean注解即可榜跌。
- 在配置文件application.properties配置一些KafkaTemplate的配置項(xiàng),這些配置項(xiàng)會覆蓋默認(rèn)配置盅粪。
那Spring Boot是如何發(fā)現(xiàn)這些東西的呢钓葫?也就是說Spring boot是怎么知道這是一個自動配置類的呢?主要有兩個票顾,一是@EnableAutoConfiguration注解础浮,而是spring.factories配置文件,現(xiàn)在看看spring.factories配置文件奠骄,該文件在類路徑中META-INF文件夾下霸旗,如下圖所示:
文件里有什么配置呢?打開看看就知道了戚揭,文件里的org.springframework.boot.autoconfigure.EnableAutoConfiguration配置就是我們今天討論的關(guān)鍵,可以發(fā)現(xiàn)撵枢,該鍵對應(yīng)著很多值民晒,每個值之間用逗號分隔(\是換行),搜索一下可以發(fā)現(xiàn)锄禽,存在KafkaAutoConfiguration這個配置項(xiàng)潜必,完整的是org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration。
接下來來看看@EnableAutoConfiguration注解沃但,該注解在@SpringBootApplication中有用到磁滚,所以只要加入了@SpringBootApplication注解,也就加入了@EnableAutoConfiguration注解宵晚,其源碼如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
關(guān)鍵是@Import(AutoConfigurationImportSelector.class)注解垂攘,該注解導(dǎo)入了AutoConfigurationImportSelector這個類,該類是自動配置的核心淤刃,從名字上看可以看出這應(yīng)該是一個選擇器晒他。其部分代碼如下:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
//加載spring.factories配置文件的配置信息
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
Assert.notEmpty(configurations,
"No auto configuration classes found in META-INF/spring.factories. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
現(xiàn)在可以把上面說到的串起來了。
- 在應(yīng)用的配置類上加入@EnableAutoConfiguration注解逸贾,該注解會導(dǎo)入AutoConfigurationImportSelector類陨仅。
- 啟動應(yīng)用的時候,Spring Boot會獲取spring.factories里的配置信息铝侵。(AutoConfigurationImportSelector灼伤。getCandidateConfigurations()方法)
- 獲取到信息之后,Spring Boot就會嘗試去觸發(fā)XXXAutoConfiguration咪鲜,是否能觸發(fā)還取決于具體AutoConfiguration類狐赡,例如在KafkaAutoConfiguration中,如果缺少KafkaTemplate類的存在疟丙,那么就不會觸發(fā)KafkaAutoConfiguration的執(zhí)行猾警,如果觸發(fā)成功孔祸,就會執(zhí)行KafkaAutoConfiguration里的代碼,這時候會發(fā)生什么就取決于具體的代碼邏輯了发皿。
簡單概括就是以上三個流程崔慧。當(dāng)然,其中的細(xì)節(jié)還有很多穴墅,例如Spring Boot是如何去讀取spring.factories的配置信息的等等惶室,所以我以上說的都只是大致原理,真正的實(shí)現(xiàn)其實(shí)異常復(fù)雜P酢;食!
這里還要一說的是spring.factories文件并不僅僅包含了自動配置相關(guān)的配置信息松捉,還包含其他一些信息夹界,所以不要誤以為spring.factories是專門為自動配置服務(wù)的。
2 動手實(shí)現(xiàn)一個Starter
Spring Boot系列有很多的starter隘世,例如spring-boot-starter-web可柿,spring-boot-starter-data-jpa等等,我們知道加入spring-boot-starter-web依賴丙者,不需要任何配置复斥,就可以直接構(gòu)建Web應(yīng)用了,這就是自動配置的威力械媒。實(shí)際上目锭,Spring作為一個擴(kuò)展性比較強(qiáng)的框架,還允許用戶自己編寫符合需求的starter纷捞。
一個完整的starter組件至少需要包含兩個部分:
- 提供自動配置功能的自動配置模塊痢虹。
- 提供依賴關(guān)系管理功能的組件模塊,即封裝了組件所有功能主儡,開箱即用世分。
具體的來說,就是需要一個XXXAutoConfiguration自動配置類以及一個封裝好的功能模塊缀辩,例如JPATemplate等等臭埋,用的時候直接依賴注入即可。
這時候可能有讀者要問了臀玄,在spring-boot-starter-web項(xiàng)目里好像沒發(fā)現(xiàn)什么XXXAutoConfiguration啊瓢阴,其實(shí)是有的,和Web相關(guān)的自動配置類被Spring寫到spring-boot-autoconfigure項(xiàng)目里了健无,即ServletWebServerFactoryAutoConfiguration荣恐。
spring-boot-autoconfigure里其實(shí)包含了很多XXXAutoConfiguration,例如KafkaAutoConfiguration,HibernateJpaAutoConfiguration等等叠穆∩倨幔基本都是Spring家族的項(xiàng)目,但對于我們自制的starter就需要在項(xiàng)目中寫XXAutoConfiguration類了硼被,畢竟Spring boot不能提前預(yù)知所有用戶的需求不是示损?好了,不多說了嚷硫,直接動手實(shí)現(xiàn)吧检访!
首先構(gòu)建一個Maven項(xiàng)目(其他的構(gòu)建框架也可以,挑一個熟悉就行)仔掸,pom文件如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>top.yeonon</groupId>
<artifactId>car-server-starter</artifactId>
<version>1.0</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
</dependencies>
</project>
其實(shí)只加入了spring-boot-autoconfigure依賴脆贵,因?yàn)楸容^簡單嘛,不需要太多東西起暮。
然后創(chuàng)建一個Properties類(其實(shí)沒有也沒什么關(guān)系卖氨,最好還是有,提供一些靈活性):
@ConfigurationProperties(prefix = "yeonon.car")
public class CarProperties {
private static final String DEFAULT_NAME = "BWM";
private static final String DEFAULT_COLOR = "white";
private static final Integer DEFAULT_AGE = 1;
private String name = DEFAULT_NAME;
private String color = DEFAULT_COLOR;
private Integer age = DEFAULT_AGE;
//getter 和 setter必須要有负懦,否則屬性注入會有問題
}
之后創(chuàng)建我們的功能模塊:
public class CarService {
private Car car;
public Car getCar() {
return car;
}
public void setCar(Car car) {
this.car = car;
}
}
非常簡單筒捺,就是獲取Car對象而已。Car類就不貼出來了密似,就是一個POJO而已。下面就是核心類了葫盼,即CarAutoConfiguration自動配置類残腌,如下所示:
@Configuration
@ConditionalOnClass(CarService.class)
@EnableConfigurationProperties(value = CarProperties.class)
public class CarAutoConfiguration {
@Autowired
private CarProperties carProperties;
@Bean
@ConditionalOnMissingBean(CarService.class)
@ConditionalOnProperty(value = "yeonon.car.server.configuration.auto.enabled", matchIfMissing = true)
public CarService carService() {
CarService carService = new CarService();
Car car = new Car();
car.setName(carProperties.getName());
car.setColor(carProperties.getColor());
car.setAge(carProperties.getAge());
carService.setCar(car);
return carService;
}
}
- @Configuration注解是要有的,否則無法生效贫导。
- @ConditionalOnClass(CarService.class)抛猫,原則上可有可無,但作為一個健壯的starter孩灯,還是有的好闺金,作為一個“防御措施”。
- @EnableConfigurationProperties(value = CarProperties.class)峰档,要使用屬性系統(tǒng)败匹,就需要把屬性類加入到應(yīng)用上下文中。
- @Bean就是定義一個Bean了讥巡,代碼邏輯沒什么可說的掀亩,無法就是創(chuàng)建對象,然后構(gòu)建對象并返回而已欢顷。關(guān)鍵在于@ConditionalOnMissingBean注解和@ConditionalOnProperty注解槽棍。@ConditionalOnMissingBean注解的意思就是如果應(yīng)用中不存在CarService的Bean,那么就執(zhí)行下面的方法構(gòu)建一個Bean,已經(jīng)存在的話炼七,就不會調(diào)用下面的方法了缆巧,這意味著用戶可以自己創(chuàng)建Bean來覆蓋系統(tǒng)默認(rèn)配置的Bean。@ConditionalOnProperty就是當(dāng)配置存在的時候豌拙,才會執(zhí)行Bean的構(gòu)建陕悬。
打完收工!別著急姆蘸!別忘了要配置spring.factories墩莫,spring-boot-autoconfigure里的spring.factories我們是沒法動的,所以就只能在自己的項(xiàng)目中動刀子了逞敷。
在resource文件夾(其實(shí)就是類路徑classpath)下創(chuàng)建一個META-INF文件夾狂秦,為什么要創(chuàng)建這玩意?問Spring去吧推捐!然后創(chuàng)建spring.factories文件(不要打錯一個字A盐省)。在里面寫入如下內(nèi)容:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
top.yeonon.stater.test.CarAutoConfiguration
org.springframework.boot.autoconfigure.EnableAutoConfiguration這個鍵名是不是很熟悉牛柒,沒錯堪簿,就是spring-boot-autoconfigure中的spring.factories里的鍵名,在這里我們只需要把我們自己的CarAutoConfiguration全限定類名加入就行了皮壁。
這才算完事椭更,然后測試一下?先打包蛾魄,打包過程我就不說了虑瀑,然后新建一個項(xiàng)目,把剛剛打包好的car-starter作為依賴加入進(jìn)去滴须,如下所示:
<dependency>
<groupId>top.yeonon</groupId>
<artifactId>car-server-starter</artifactId>
<version>1.0</version>
</dependency>
為了方便測試舌狗,也加入spring-boot-starter-web吧,然后寫一個Controller扔水,如下所示:
@RestController
@RequestMapping("/hello")
public class HelloController {
@Autowired
private CarService carService;
@GetMapping("car")
public Car getMyCar() {
return carService.getCar();
}
}
運(yùn)行一下痛侍,訪問該路徑,大致可以得到如下輸出:
這里的值還是我們之前的默認(rèn)值(返回去看看CarProperties類)魔市,Spring Boot的屬性系統(tǒng)也是相當(dāng)厲害主届,現(xiàn)在來試試修改配置項(xiàng),如下所示:
## 注意前綴是yeonon.car,也是在CarProperties類里配置好的
yeonon.car.name=MSLD
yeonon.car.age=2
yeonon.car.color=black
然后重啟項(xiàng)目待德,再次訪問岂膳,得到的結(jié)果應(yīng)該是這樣:
是不是很神奇?
3 小結(jié)
Spring Boot的自動配置非常強(qiáng)大磅网,免去了很多配置文件谈截,用得好的話會覺得既方便又靈活,用不好的話可能會發(fā)生一些配置沖突的問題(不過其實(shí)都是能解決的)。自己編寫stater也是可行的簸喂,不過要確定確實(shí)需要自定義的starter毙死,否則最好還是不要給自己挖坑哈,例如我們的Car stater這個例子喻鳄,實(shí)際上如果僅僅是為了實(shí)現(xiàn)這個功能扼倘,完全不需要專門寫一個starter。
最后除呵,Spring水很深再菊,道阻且長!