一、引言
當我們使用 SpringBoot 進行開發時,會引入許多的 starter 包,引入後就會爲我們做一些自動化配置,省去了大量配置的時間,快速開發。
以 Redis 爲例,當我們引入 Redis 的 starter 包時:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
spring-boot-autoconfigure
包中有一個類叫做 RedisAutoConfiguration ,該類頭上有三個註解:
@Configuration
:標明該類是一個配置類,並注入容器@ConditionalOnClass
:當需求類均存在時,滿足啓用要求@EnableConfigurationProperties
:開啓 @ConfigurationProperties 註解
當 spring-boot-starter-data-redis
包被引入後,@ConditionalOnClass 的要求被滿足,RedisAutoConfiguration
這個類就會被啓用。在圖中可以看到最後注入了一個名爲 redisTemplate
的 Bean,這個 Bean 我們很熟悉,用它就可以來操縱 Redis。
@ConditionalOnClass
註解是上面流程中最關鍵的註解,你可以認爲它是一個開關,只有滿足這個註解的條件,這個開關纔會打開,這個類就會啓用。這就是 SpringBoot 自動配置的關鍵,只有引入相關的 starter,@ConditionalOnClass 的條件纔會滿足,自動配置纔會生效。
二、簡單實踐
@ConditionalOnClass
是 SpringBoot 二次封裝的註解,它的底層依賴於 Spring 的 @Conditional
註解。
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {
...
}
@Conditional
註解又名 條件註解,下面就來跟着我一探究竟吧!
2.1 引入依賴
讓我們脫離 SpringBoot 框架,建立一個 Maven 項目,只引入 Spring 的依賴:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
</dependencies>
2.2 接口與實現類
定義一個名爲 Animal
的接口,這個接口只有一個方法,即 say()
:
public interface Animal {
void say();
}
然後分別定義 Dog 和 Cat 的實現,衆所周知,狗和貓的叫聲是不一樣的~
public class Dog implements Animal {
public void say() {
System.out.println("汪汪~");
}
}
public class Cat implements Animal {
public void say() {
System.out.println("喵喵~");
}
}
2.3 條件類
有了 Dog 和 Cat 這兩個實現類,下面需要配合 @Conditional
註解來決定什麼時候加載。首先我們需要定義加載的規則,簡單起見,以讀取環境變量的 animal 屬性爲例。
@Conditional
屬性的條件規則需要實現 Conditional
接口,該接口的 matches()
方法決定了是否加載。分別定義兩個 Condition,用於判斷是否加載 Dog 或者 Cat。
public class DogCondition implements Condition {
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
return "dog".equals(conditionContext.getEnvironment().getProperty("animal"));
}
}
public class CatCondition implements Condition {
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
return "cat".equals(conditionContext.getEnvironment().getProperty("animal"));
}
}
2.4 配置類
下面就是關鍵的配置類的信息了:
@Configuration
public class AnimalConfig {
@Bean("animal")
@Conditional(DogCondition.class)
public Animal dog() {
return new Dog();
}
@Bean("animal")
@Conditional(CatCondition.class)
public Animal cat() {
return new Cat();
}
}
你可以看到這裏注入了兩個都叫 animal 的 Bean,我們知道默認情況下同名會被覆蓋,因此我們通過 @Conditional
註解決定了到底應該如何加載。
-
當
@Conditional(DogCondition.class)
滿足時,加載 Dog; -
當
@Conditional(CatCondition.class)
滿足時,加載 Cat。
2.5 啓動類
讓我們寫入一個啓動類來實現下,首先設置了環境變量中 animal 的值,然後將配置類注入容器並刷新 context,最後從容器中取出 Bean,並調用 Bean 的 say() 方法。
public class Application {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.getEnvironment().getSystemProperties().put("animal", "cat");
context.register(AnimalConfig.class);
context.refresh();
Animal animal = (Animal) context.getBean("animal");
animal.say();
}
}
當變量值爲 cat 時,輸出:
喵喵~
當變量值爲 dog 時,輸出:
汪汪~
三、@Profile
在實際開發中,由於存在開發、測試、預發、生產等多套環境,程序中相應的也會準備多套配置文件,根據當前環境選擇對應的配置文件。傳統情況下我們會建立多個 application.yaml,命名符合 application-{profie}.yaml
規範,然後通過 spring.profiles.active={profile}
來指定環境。
我們可以利用 @Profile
註解來決定在什麼環境下加載配置。例如在 dev 環境下加載 Dog,在 test 環境下加載 Cat。
3.1 配置類
修改配置類如下,不再需要 Condition 註解,使用 @Profile 註解決定是否加載:
@Configuration
public class AnimalConfig {
@Bean("animal")
@Profile("dev")
public Animal dog() {
return new Dog();
}
@Bean("animal")
@Profile("test")
public Animal cat() {
return new Cat();
}
}
3.2 啓動類
修改啓動類如下:
public class Application {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.getEnvironment().setActiveProfiles("dev");
context.register(AnimalConfig.class);
context.refresh();
Animal animal = (Animal) context.getBean("animal");
animal.say();
}
}
通過 setActiveProfiles()
手動指定當前環境,當值爲 dev 或 test 時,得到的運行結果符合預期。
四、更多註解
回到 SpringBoot,除了一開始介紹的 @ConditionalOnClass 外,SpringBoot 還封裝了這些註解,供我們使用:
註解 | 註釋 |
---|---|
@ConditionalOnSingleCandidate | 類似於@ConditionalOnBean 但只有在確定了給定bean類的單個候選項時纔會加載bean。 |
@ConditionalOnMissingBean | 當給定的 Bean 不存在時返回 true 各類型間是 or 的關係 |
@ConditionalOnBean | 與上面相反,要求bean存在 |
@ConditionalOnMissingClass | 當給定的類名在類路徑上不存在時返回true 各類型間是 and 的關係 |
@ConditionalOnClass | 與上面相反,要求類存在 |
@ConditionalOnExpression | Spel 表達式成立,返回 true |
@ConditionalOnJava | 運行時的Java版本號包含給定的版本號,返回 true |
@ConditionalOnProperty | 屬性匹配條件滿足,返回 true |
@ConditionalOnWebApplication | web環境存在時,返回 true |
@ConditionalOnNotWebApplication | web環境不存在時,返回 true |
@ConditionalOnResource | 指定的資源存在時,返回 true |