Spring Boot 中的自動化配置確實夠吸引人,甚至有人說 Spring Boot 讓 Java 又一次煥發了生機,這話雖然聽着有點誇張,可是不能否認的是,曾經臃腫繁瑣的 Spring 配置確實讓人感到頭大,而 Spring Boot 帶來的全新自動化配置,又確實緩解了這個問題。java
你要是問這個自動化配置是怎麼實現的,不少人會說不就是 starter 嘛!那麼 starter 的原理又是什麼呢?鬆哥之前寫過一篇文章,介紹了自定義 starter:spring
這裏邊有一個很是關鍵的點,那就是條件註解,甚至能夠說條件註解是整個 Spring Boot 的基石。後端
條件註解並不是一個新事物,這是一個存在於 Spring 中的東西,咱們在 Spring 中經常使用的 profile 實際上就是條件註解的一個特殊化。mvc
想要把 Spring Boot 的原理搞清,條件註解必需要會用,所以今天鬆哥就來和你們聊一聊條件註解。前後端分離
Spring4 中提供了更加通用的條件註解,讓咱們能夠在知足不一樣條件時建立不一樣的 Bean,這種配置方式在 Spring Boot 中獲得了普遍的使用,大量的自動化配置都是經過條件註解來實現的,查看鬆哥以前的 Spring Boot 文章,凡是涉及到源碼解讀的文章,基本上都離不開條件註解:ide
有的小夥伴可能沒用過條件註解,可是開發環境、生產環境切換的 Profile 多多少少都有用過吧?實際上這就是條件註解的一個特例。微服務
拋開 Spring Boot,咱們來單純的看看在 Spring 中條件註解的用法。測試
首先咱們來建立一個普通的 Maven 項目,而後引入 spring-context,以下:spa
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
</dependencies>
複製代碼
而後定義一個 Food 接口:code
public interface Food {
String showName();
}
複製代碼
Food 接口有一個 showName 方法和兩個實現類:
public class Rice implements Food {
public String showName() {
return "米飯";
}
}
public class Noodles implements Food {
public String showName() {
return "麪條";
}
}
複製代碼
分別是 Rice 和 Noodles 兩個類,兩個類實現了 showName 方法,而後分別返回不一樣值。
接下來再分別建立 Rice 和 Noodles 的條件類,以下:
public class NoodlesCondition implements Condition {
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getEnvironment().getProperty("people").equals("北方人");
}
}
public class RiceCondition implements Condition {
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getEnvironment().getProperty("people").equals("南方人");
}
}
複製代碼
在 matches 方法中作條件屬性判斷,當系統屬性中的 people 屬性值爲 '北方人' 的時候,NoodlesCondition 的條件獲得知足,當系統中 people 屬性值爲 '南方人' 的時候,RiceCondition 的條件獲得知足,換句話說,哪一個條件獲得知足,一會就會建立哪一個 Bean 。
接下來咱們來配置 Rice 和 Noodles :
@Configuration
public class JavaConfig {
@Bean("food")
@Conditional(RiceCondition.class)
Food rice() {
return new Rice();
}
@Bean("food")
@Conditional(NoodlesCondition.class)
Food noodles() {
return new Noodles();
}
}
複製代碼
這個配置類,你們重點注意兩個地方:
配置完成後,咱們就能夠在 main 方法中進行測試了:
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().getSystemProperties().put("people", "南方人");
ctx.register(JavaConfig.class);
ctx.refresh();
Food food = (Food) ctx.getBean("food");
System.out.println(food.showName());
}
}
複製代碼
首先咱們建立一個 AnnotationConfigApplicationContext 實例用來加載 Java 配置類,而後咱們添加一個 property 到 environment 中,添加完成後,再去註冊咱們的配置類,而後刷新容器。容器刷新完成後,咱們就能夠從容器中去獲取 food 的實例了,這個實例會根據 people 屬性的不一樣,而建立出來不一樣的 Food 實例。
這個就是 Spring 中的條件註解。
條件註解還有一個進化版,那就是 Profile。咱們通常利用 Profile 來實如今開發環境和生產環境之間進行快速切換。其實 Profile 就是利用條件註解來實現的。
仍是剛纔的例子,咱們用 Profile 來稍微改造一下:
首先 Food、Rice 以及 Noodles 的定義不用變,條件註解此次咱們不須要了,咱們直接在 Bean 定義時添加 @Profile 註解,以下:
@Configuration
public class JavaConfig {
@Bean("food")
@Profile("南方人")
Food rice() {
return new Rice();
}
@Bean("food")
@Profile("北方人")
Food noodles() {
return new Noodles();
}
}
複製代碼
此次不須要條件註解了,取而代之的是 @Profile 。而後在 Main 方法中,按照以下方式加載 Bean:
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("南方人");
ctx.register(JavaConfig.class);
ctx.refresh();
Food food = (Food) ctx.getBean("food");
System.out.println(food.showName());
}
}
複製代碼
效果和上面的案例同樣。
這樣看起來 @Profile 註解貌似比 @Conditional 註解還要方便,那麼 @Profile 註解究竟是什麼實現的呢?
咱們來看一下 @Profile 的定義:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ProfileCondition.class)
public @interface Profile {
String[] value();
}
複製代碼
能夠看到,它也是經過條件註解來實現的。條件類是 ProfileCondition ,咱們來看看:
class ProfileCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
if (attrs != null) {
for (Object value : attrs.get("value")) {
if (context.getEnvironment().acceptsProfiles(Profiles.of((String[]) value))) {
return true;
}
}
return false;
}
return true;
}
}
複製代碼
看到這裏就明白了,其實仍是咱們在條件註解中寫的那一套東西,只不過 @Profile 註解自動幫咱們實現了而已。
@Profile 雖然方便,可是不夠靈活,由於具體的判斷邏輯不是咱們本身實現的。而 @Conditional 則比較靈活。
兩個例子向你們展現了條件註解在 Spring 中的使用,它的一個核心思想就是當知足某種條件的時候,某個 Bean 纔會生效,而正是這一特性,支撐起了 Spring Boot 的自動化配置。
好了,本文就說到這裏,有問題歡迎留言討論。
關注公衆號【江南一點雨】,專一於 Spring Boot+微服務以及先後端分離等全棧技術,按期視頻教程分享,關注後回覆 Java ,領取鬆哥爲你精心準備的 Java 乾貨!