1. 程式人生 > >Spring Boot2 系列教程(七)理解自動化配置的原理

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

Spring Boot 中的自動化配置確實夠吸引人,甚至有人說 Spring Boot 讓 Java 又一次煥發了生機,這話雖然聽著有點誇張,但是不可否認的是,曾經臃腫繁瑣的 Spring 配置確實讓人感到頭大,而 Spring Boot 帶來的全新自動化配置,又確實緩解了這個問題。

你要是問這個自動化配置是怎麼實現的,很多人會說不就是 starter 嘛!那麼 starter 的原理又是什麼呢?鬆哥以前寫過一篇文章,介紹了自定義 starter:

  • 徒手擼一個 Spring Boot 中的 Starter ,解密自動化配置黑魔法!

這裡邊有一個非常關鍵的點,那就是條件註解,甚至可以說條件註解是整個 Spring Boot 的基石。

條件註解並非一個新事物,這是一個存在於 Spring 中的東西,我們在 Spring 中常用的 profile 實際上就是條件註解的一個特殊化。

想要把 Spring Boot 的原理搞清,條件註解必須要會用,因此今天鬆哥就來和大家聊一聊條件註解。

定義

Spring4 中提供了更加通用的條件註解,讓我們可以在滿足不同條件時建立不同的 Bean,這種配置方式在 Spring Boot 中得到了廣泛的使用,大量的自動化配置都是通過條件註解來實現的,檢視鬆哥之前的 Spring Boot 文章,凡是涉及到原始碼解讀的文章,基本上都離不開條件註解:

  • 40 篇原創乾貨,帶你進入 Spring Boot 殿堂!

有的小夥伴可能沒用過條件註解,但是開發環境、生產環境切換的 Profile 多多少少都有用過吧?實際上這就是條件註解的一個特例。

實踐

拋開 Spring Boot,我們來單純的看看在 Spring 中條件註解的用法。

首先我們來建立一個普通的 Maven 專案,然後引入 spring-context,如下:

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.1.5.RELEASE</version>
    </dependency>
</dependencies>

然後定義一個 Food 介面:

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 乾貨!

相關推薦

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

Spring Boot 中的自動化配置確實夠吸引人,甚至有人說 Spring Boot 讓 Java 又一次煥發了生機,這話雖然聽著有點誇張,但是不可否認的是,曾經臃腫繁瑣的 Spring 配置確實讓人感到頭大,而 Spring Boot 帶來的全新自動化配置,又確實緩解了這個問題。 你要是問這個自動化配置是

Spring Boot2 系列教程(四)理解Spring Boot 配置檔案 application.properties

在 Spring Boot 中,配置檔案有兩種不同的格式,一個是 properties ,另一個是 yaml 。 雖然 properties 檔案比較常見,但是相對於 properties 而言,yaml 更加簡潔明瞭,而且使用的場景也更多,很多開源專案都是使用 yaml 進行配置(例如 Hexo)。除了簡潔

Spring Boot2 系列教程(三)理解 Spring Boot 專案中的 parent

前面和大夥聊了 Spring Boot 專案的三種建立方式,這三種建立方式,無論是哪一種,建立成功後,pom.xml 座標檔案中都有如下一段引用: <parent> <groupId>org.springframework.boot</groupId> &l

Spring Boot2 系列教程(五)Spring Boot中的 yaml 配置

搞 Spring Boot 的小夥伴都知道,Spring Boot 中的配置檔案有兩種格式,properties 或者 yaml,一般情況下,兩者可以隨意使用,選擇自己順手的就行了,那麼這兩者完全一樣嗎?肯定不是啦!本文就來和大夥重點介紹下 yaml 配置,最後再來看看 yaml 和 properties 配

Spring Boot2 系列教程(八)Spring Boot 中配置 Https

https 現在已經越來越普及了,特別是做一些小程式或者公眾號開發的時候,https 基本上都是剛需了。 不過一個 https 證書還是挺費錢的,個人開發者可以在各個雲服務提供商那裡申請一個免費的證書。我印象中有效期一年,可以申請 20 個。 今天要和大家聊的是在 Spring Boot 專案中,如何開啟 h

Spring Boot2 系列教程(十一)Spring Boot 中的靜態資源配置

當我們使用 SpringMVC 框架時,靜態資源會被攔截,需要新增額外配置,之前老有小夥伴在微信上問鬆哥 Spring Boot 中的靜態資源載入問題:“鬆哥,我的 HTML 頁面好像沒有樣式?”,今天我就通過一篇文章,來和大夥仔細聊一聊這個問題。 1. SSM 中的配置 要講 Spring Boot 中的問

Spring Boot2 系列教程(十)SpringBoot 整合 Swagger2

前後端分離後,維護介面文件基本上是必不可少的工作。 一個理想的狀態是設計好後,介面文件發給前端和後端,大夥按照既定的規則各自開發,開發好了對接上了就可以上線了。當然這是一種非常理想的狀態,實際開發中卻很少遇到這樣的情況,介面總是在不斷的變化之中,有變化就要去維護,做過的小夥伴都知道這件事有多麼頭大!還好,有一

Spring Boot2 系列教程(十八)Spring Boot 中自定義 SpringMVC 配置

用過 Spring Boot 的小夥伴都知道,我們只需要在專案中引入 spring-boot-starter-web 依賴,SpringMVC 的一整套東西就會自動給我們配置好,但是,真實的專案環境比較複雜,系統自帶的配置不一定滿足我們的需求,往往我們還需要結合實際情況自定義配置。 自定義配置就有講究了,由於

Spring Boot2 系列教程(二十三)理解 Spring Data Jpa

有很多讀者留言希望鬆哥能好好聊聊 Spring Data Jpa! 其實這個話題鬆哥以前零零散散的介紹過,在我的書裡也有介紹過,但是在公眾號中還沒和大夥聊過,因此本文就和大家來仔細聊聊 Spring Data 和 Jpa! 本文大綱: 1. 故事的主角 1.1 Jpa 1.1.1 JPA 是什麼 Jav

Spring Boot系列教程Spring boot集成MyBatis

override fill sql water sso avi size logs index 一.創建項目 項目名稱為 “springboot_mybatis_demo”,創建過程中勾選 “Web”,“MyBatis”,“MySQL”,第一次創建Maven

spring cloud系列教程(14)---配置中心實戰

大家推薦個靠譜的公眾號程式設計師探索之路,大家一起加油,這個公眾號已經接入圖靈 ​   這兩天更新的有點慢兩點原因 1.字符集,在github上上傳的配置檔案一定要轉成utf-8的!!!!!!!! 2.配置中心的配置檔案的檔名稱和application: name

Spring Boot2 系列教程(一)純 Java 搭建 SSM 專案

在 Spring Boot 專案中,正常來說是不存在 XML 配置,這是因為 Spring Boot 不推薦使用 XML ,注意,並非不支援,Spring Boot 推薦開發者使用 Java 配置來搭建框架,Spring Boot 中,大量的自動化配置都是通過 Java 配置來實現的,這一套實現方案,我們也可

Spring Boot2 系列教程(二)建立 Spring Boot 專案的三種方式

我最早是 2016 年底開始寫 Spring Boot 相關的部落格,當時使用的版本還是 1.4.x ,文章發表在 CSDN 上,閱讀量最大的一篇有 43W+,如下圖: 2017 年由於種種原因,就沒有再繼續更新 Spring Boot 相關的部落格了,2018年又去寫書了,也沒更新,現在 Spring

Spring Boot2 系列教程(六)自定義 Spring Boot 中的 starter

我們使用 Spring Boot,基本上都是沉醉在它 Stater 的方便之中。Starter 為我們帶來了眾多的自動化配置,有了這些自動化配置,我們可以不費吹灰之力就能搭建一個生產級開發環境,有的小夥伴會覺得這個 Starter 好神奇呀!其實 Starter 也都是 Spring + SpringMVC

Spring Boot2 系列教程(九)Spring Boot 整合 Thymeleaf

雖然現在慢慢在流行前後端分離開發,但是據鬆哥所瞭解到的,還是有一些公司在做前後端不分的開發,而在前後端不分的開發中,我們就會需要後端頁面模板(實際上,即使前後端分離,也會在一些場景下需要使用頁面模板,例如郵件傳送模板)。 早期的 Spring Boot 中還支援使用 Velocity 作為頁面模板,現在的

Spring Boot2 系列教程(十)Spring Boot 整合 Freemarker

今天來聊聊 Spring Boot 整合 Freemarker。 Freemarker 簡介 這是一個相當老牌的開源的免費的模版引擎。通過 Freemarker 模版,我們可以將資料渲染成 HTML 網頁、電子郵件、配置檔案以及原始碼等。Freemarker 不是面向終端使用者的,而是一個 Java 類庫,我

Spring Boot2 系列教程(十二)@ControllerAdvice 的三種使用場景

嚴格來說,本文並不算是 Spring Boot 中的知識點,但是很多學過 SpringMVC 的小夥伴,對於 @ControllerAdvice 卻並不熟悉,Spring Boot 和 SpringMVC 一脈相承,@ControllerAdvice 在 Spring Boot 中也有廣泛的使用場景,因此本文

Spring Boot2 系列教程(十四)CORS 解決跨域問題

今天和小夥伴們來聊一聊通過CORS解決跨域問題。 同源策略 很多人對跨域有一種誤解,以為這是前端的事,和後端沒關係,其實不是這樣的,說到跨域,就不得不說說瀏覽器的同源策略。 同源策略是由 Netscape 提出的一個著名的安全策略,它是瀏覽器最核心也最基本的安全功能,現在所有支援 JavaScript 的瀏覽

Spring Boot2 系列教程(十五)定義系統啟動任務的兩種方式

在 Servlet/Jsp 專案中,如果涉及到系統任務,例如在專案啟動階段要做一些資料初始化操作,這些操作有一個共同的特點,只在專案啟動時進行,以後都不再執行,這裡,容易想到web基礎中的三大元件( Servlet、Filter、Listener )之一 Listener ,這種情況下,一般定義一個 Serv

Spring Boot2 系列教程(十六)定時任務的兩種實現方式

在 Spring + SpringMVC 環境中,一般來說,要實現定時任務,我們有兩中方案,一種是使用 Spring 自帶的定時任務處理器 @Scheduled 註解,另一種就是使用第三方框架 Quartz ,Spring Boot 源自 Spring+SpringMVC ,因此天然具備這兩個 Spring