Spring Boot2 系列教程(七)理解自動化配置的原理

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();
    }
}
複製代碼

這個配置類,你們重點注意兩個地方:

  • 兩個 Bean 的名字都爲 food,這不是巧合,而是有意取的。兩個 Bean 的返回值都爲其父類對象 Food。
  • 每一個 Bean 上都多了 @Conditional 註解,當 @Conditional 註解中配置的條件類的 matches 方法返回值爲 true 時,對應的 Bean 就會生效。

配置完成後,咱們就能夠在 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 乾貨!

相關文章
相關標籤/搜索