朱曄和你聊Spring系列S1E2:SpringBoot並不神祕

powerzhuye發表於2018-09-30

本文我們會一步一步做一個例子來看看SpringBoot的自動配置是如何實現的,然後來看一些SpringBoot留給我們的擴充套件點。

自己製作一個SpringBoot Starter

我們知道SpringBoot提供了非常多的啟動器,引入了啟動器依賴即可直接享受到自動依賴配置和自動屬性配置:

朱曄和你聊Spring系列S1E2:SpringBoot並不神祕
github.com/spring-proj…

在第一篇文章中我提到,在SpringBoot出現之前,我們需要使用SpringMVC、Spring Data、Spring Core都需要對Spring內部的各種元件進行Bean以及Bean依賴的配置,在90%的時候我們用的是預設的配置,不會自定義任何擴充套件類,這個時候也需要由使用者來手動配置顯然不合理,有了SpringBoot,我們只需引入啟動器依賴,然後啟動器就可以自己做為自己的一些內部元件做自動配置,大大方便了使用者。啟動器的實現非常簡單,我們來看下實現過程。 首先建立一個Maven空專案,引入SpringBoot:

<?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>me.josephzhu</groupId>
    <artifactId>spring101</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>spring101</name>
    <description></description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>


</project>
複製程式碼

然後我們建立一個Starter模組隸屬於父專案:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
         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>me.josephzhu</groupId>
    <artifactId>spring101-customstarter</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>spring101-customstarter</name>
    <description></description>

    <parent>
        <groupId>me.josephzhu</groupId>
        <artifactId>spring101</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
        </dependency>
    </dependencies>

</project>
複製程式碼

接下去我們建立一個服務抽象基類和實現,這個服務非常簡單,會依賴到一些屬性,然後會有不同的實現(無參建構函式設定了打招呼用語的預設值為Hello):

package me.josephzhu.spring101customstarter;

import org.springframework.beans.factory.annotation.Autowired;

public abstract class AbstractMyService {

    protected String word;
    public AbstractMyService(String word) {
        this.word = word;
    }

    public AbstractMyService() {
        this ("Hello");
    }

    @Autowired
    protected MyServiceProperties properties;

    public abstract String hello();
}
複製程式碼

這裡注入了自定義屬性類:

package me.josephzhu.spring101customstarter;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "spring101")
@Data
public class MyServiceProperties {
    /**
     * user name
     */
    private String name;
    /**
     * user age *Should between 1 and 120
     */
    private Integer age;
    /**
     * determine the service version you want use
     */
    private String version;
}
複製程式碼

這裡看到了如果我們需要定義一個自定義類來關聯配置源(比如application.properties檔案配置)是多麼簡單,使用@ConfigurationProperties註解標註我們的POJO告知註解我們配置的字首即可。額外提一句,如果希望我們的IDE可以針對自定義配置有提示的話(自動完成,而且帶上註解中的提示語),可以引入如下的依賴:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>
複製程式碼

這樣編譯後就會在META-INF下面生成一個叫做spring-configuration-metadata.json的檔案:

{
  "hints": [],
  "groups": [
    {
      "sourceType": "me.josephzhu.spring101customstarter.MyServiceProperties",
      "name": "spring101",
      "type": "me.josephzhu.spring101customstarter.MyServiceProperties"
    }
  ],
  "properties": [
    {
      "sourceType": "me.josephzhu.spring101customstarter.MyServiceProperties",
      "name": "spring101.age",
      "description": "user age *Should between 1 and 120",
      "type": "java.lang.Integer"
    },
    {
      "sourceType": "me.josephzhu.spring101customstarter.MyServiceProperties",
      "name": "spring101.name",
      "description": "user name",
      "type": "java.lang.String"
    },
    {
      "sourceType": "me.josephzhu.spring101customstarter.MyServiceProperties",
      "name": "spring101.version",
      "description": "determine the service version you want use",
      "type": "java.lang.String"
    }
  ]
}
複製程式碼

之後在使用配置的時候就可以有提示:

朱曄和你聊Spring系列S1E2:SpringBoot並不神祕

我們先來寫第一個服務實現,如下,只是輸出一下使用到的一些自定義屬性:

package me.josephzhu.spring101customstarter;

import org.springframework.stereotype.Service;

@Service
public class MyService extends AbstractMyService {

    public MyService(String word) {
        super(word);
    }

    public MyService(){}

    @Override
    public String hello() {
        return String.format("V1 %s >> %s:%s !!", word, properties.getName(), properties.getAge());
    }

}
複製程式碼

關鍵的一步來了,接下去我們需要定義自動配置類:

package me.josephzhu.spring101customstarter;

import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableConfigurationProperties(MyServiceProperties.class)
public class MyAutoConfiguration {
    @Bean
    MyService getMyService(){
        return new MyService();
    }
}
複製程式碼

通過EnableConfigurationProperties來告訴Spring我們需要關聯一個配置檔案配置類(配置類鬥不需要設定@Component),通過@Configuration告訴Spring這是一個Bean的配置類,下面我們定義了我們Service的實現。 最後,我們需要告訴SpringBoot如何來找到我們的自動配置類,在合適的時候自動配置。我們需要在專案資源目錄建一個META-INF資料夾,然後建立一個spring.factories檔案,寫入下面的內容:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=me.josephzhu.spring101customstarter.MyAutoConfiguration
複製程式碼

好了,就是這麼簡單,接下去我們建立一個專案來使用我們的自定義啟動器來試試:

<?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>me.josephzhu</groupId>
    <artifactId>spring101-main</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>spring101-main</name>
    <description></description>

    <parent>
        <groupId>me.josephzhu</groupId>
        <artifactId>spring101</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>me.josephzhu</groupId>
            <artifactId>spring101-customstarter</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>


</project>
複製程式碼

建立一個Runner來呼叫服務:

package me.josephzhu.spring101main;

import lombok.extern.slf4j.Slf4j;
import me.josephzhu.spring101customstarter.AbstractMyService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
@Slf4j
public class Runner1 implements CommandLineRunner {

    @Autowired
    private AbstractMyService service;

    @Override
    public void run(String... args) {
        log.info(service.hello());
    }
}
複製程式碼

建立主程式:

package me.josephzhu.spring101main;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Spring101MainApplication {

    public static void main(String[] args) {
        SpringApplication.run(Spring101MainApplication.class, args);
    }

}
複製程式碼

然後在main模組的資源目錄下建立application.properties檔案,寫入兩個配置:

spring101.age=35
spring101.name=zhuye
複製程式碼

執行後可以看到輸出:

2018-09-30 14:55:00.848  INFO 12704 --- [           main] me.josephzhu.spring101main.Runner1       : V1 Hello >> zhuye:35 !!
複製程式碼

可以證明,第一我們的main模組引入的starter正確被SpringBoot識別載入,第二starter中的Configuration正確執行不但載入了配置類,而且也正確注入了Service的一個實現。

如何實現條件配置?

作為元件的開發者,我們有的時候希望針對環境、配置、類的載入情況等等進行各種更智慧的自動配置,這個時候就需要使用Spring的Conditional特性。我們來看一個例子,如果我們的Service隨著發展演化出了v2版本,我們希望使用者在預設的時候使用v1,如果需要的話可以進行version屬性配置允許使用者切換到v2版本。實現起來非常簡單,首先定義另一個v2版本的服務:

package me.josephzhu.spring101customstarter;

import org.springframework.stereotype.Service;

@Service
public class MyServiceV2 extends AbstractMyService {

    public MyServiceV2(String word) {
        super(word);
    }

    public MyServiceV2(){}

    @Override
    public String hello() {
        return String.format("V2 %s >> %s:%s !!", word, properties.getName(), properties.getAge());
    }

}
複製程式碼

和版本v1沒有任何區別,只是標記了一下v2關鍵字。 然後我們改造一下我們的自動配置類:

package me.josephzhu.spring101customstarter;

import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableConfigurationProperties(MyServiceProperties.class)
public class MyAutoConfiguration {
    @Bean
    @ConditionalOnProperty(prefix = "spring101", name = "version", havingValue = "v1", matchIfMissing = true)
    MyService getMyService(){
        return new MyService();
    }

    @Bean
    @ConditionalOnProperty(prefix = "spring101", name = "version", havingValue = "v2")
    MyServiceV2 getMyServiceV2(){
        return new MyServiceV2();
    }
}
複製程式碼

這裡主要是為兩個Bean分別新增了@ConditionalOnProperty註解,註解是自解釋的。這裡說了如果version的值是v1或沒有定義version的話匹配到預設的v1版本的服務,如果配置設定為v2的話匹配到v2版本的服務,就這麼簡單。 再來看一個例子,如果我們的使用者希望自己定義服務的實現,這個時候我們需要覆蓋自動配置為我們自動裝配的v1和v2,可以使用另一個註解@ConditionalOnMissingBean來告知SpringBoot,如果找不到Bean的話再來自動配置:

package me.josephzhu.spring101customstarter;

import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableConfigurationProperties(MyServiceProperties.class)
public class MyAutoConfiguration {
    @Bean
    @ConditionalOnMissingBean(MyService.class)
    @ConditionalOnProperty(prefix = "spring101", name = "version", havingValue = "v1", matchIfMissing = true)
    MyService getMyService(){
        return new MyService();
    }

    @Bean
    @ConditionalOnMissingBean(MyServiceV2.class)
    @ConditionalOnProperty(prefix = "spring101", name = "version", havingValue = "v2")
    MyServiceV2 getMyServiceV2(){
        return new MyServiceV2();
    }
}
複製程式碼

這樣的話,如果客戶端自己定義了Service的實現的話,就可以讓自動配置放棄自動配置使用客戶端自己定義的Bean。還有N多的Conditional註解可以使用,甚至可以自定義條件,具體可以檢視官方文件。

進行一下測試

接下去,我們來寫一下單元測試來驗證一下我們之前的程式碼,使用ApplicationContextRunner可以方便得設定帶入的各種外部配置項以及自定義配置類: 在這裡我們寫了三個測試用例:

  • 在提供了合適的屬性配置後,可以看到服務的輸出正確獲取到了屬性。
  • 使用配置項version來切換服務的實現,在省略version,設定version為1,設定version為2的情況下得到正確的輸出,分別是v1、v1和v2。
  • 在客戶端自定義實現(MyServiceConfig)後可以看到並沒有載入使用自動配置裡定義的服務實現,最後輸出了打招呼用語Hi而不是Hello。
package me.josephzhu.spring101main;

import me.josephzhu.spring101customstarter.AbstractMyService;
import me.josephzhu.spring101customstarter.MyAutoConfiguration;
import me.josephzhu.spring101customstarter.MyService;
import org.junit.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import static org.assertj.core.api.Assertions.assertThat;


public class Spring101MainApplicationTests {

    private ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner()
            .withConfiguration(AutoConfigurations.of(MyAutoConfiguration.class));

    @Test
    public void testService() {
        applicationContextRunner
                .withPropertyValues("spring101.age=35")
                .withPropertyValues("spring101.name=zhuye")
                .run(context -> {
                    assertThat(context).hasSingleBean(AbstractMyService.class);
                    assertThat(context.getBean(AbstractMyService.class).hello()).containsSequence("zhuye:35");
                    System.out.println(context.getBean(MyService.class).hello());
                });
    }

    @Test
    public void testConditionalOnProperty() {
        applicationContextRunner
                .run(context -> {
                    assertThat(context).hasSingleBean(AbstractMyService.class);
                    assertThat(context.getBean(AbstractMyService.class).hello()).containsSequence("V1 Hello");
                    System.out.println(context.getBean(AbstractMyService.class).hello());
                });
        applicationContextRunner
                .withPropertyValues("spring101.version=v1")
                .run(context -> {
                    assertThat(context).hasSingleBean(AbstractMyService.class);
                    assertThat(context.getBean(AbstractMyService.class).hello()).containsSequence("V1 Hello");
                    System.out.println(context.getBean(AbstractMyService.class).hello());
                });
        applicationContextRunner
                .withPropertyValues("spring101.version=v2")
                .run(context -> {
                    assertThat(context).hasSingleBean(AbstractMyService.class);
                    assertThat(context.getBean(AbstractMyService.class).hello()).containsSequence("V2 Hello");
                    System.out.println(context.getBean(AbstractMyService.class).hello());
                });

    }

    @Test
    public void testConditionalOnMissingBean() {
        applicationContextRunner
                .withUserConfiguration(MyServiceConfig.class)
                .run(context -> {
                    assertThat(context).hasSingleBean(MyService.class);
                    assertThat(context.getBean(MyService.class).hello()).containsSequence("V1 Hi");
                    System.out.println(context.getBean(MyService.class).hello());
                });
    }

}

@Configuration
class MyServiceConfig {
    @Bean
    MyService getService() {
        return new MyService("Hi");
    }
}
複製程式碼

執行測試可以看到三個測試都可以通過,控制檯也輸出了hello方法的返回值:

朱曄和你聊Spring系列S1E2:SpringBoot並不神祕

實現自定義的配置資料來源

接下去我們來看一下如何利用EnvironmentPostProcessor來實現一個自定義的配置資料來源。我們在starter專案中新建一個類,這個類使用了一個Yaml配置源載入器,然後我們把載入到的自定義的PropertySource加入到PropertySource候選列表的第一個,這樣就可以實現屬性優先從我們定義的(classpath下的)config.yml來讀取:

package me.josephzhu.spring101customstarter;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.boot.env.YamlPropertySourceLoader;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;

public class MyPropertySourceEnvironmentPostProcessor implements EnvironmentPostProcessor {

    private final YamlPropertySourceLoader loader = new YamlPropertySourceLoader();

    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment,
                                       SpringApplication application) {
        PropertySource<?> propertySource = loadYaml(new ClassPathResource("config.yml"));
        environment.getPropertySources().addFirst(propertySource);
    }

    private PropertySource<?> loadYaml(Resource path) {
        if (!path.exists()) {
            throw new IllegalArgumentException("Resource " + path + " does not exist");
        }
        try {
            return this.loader.load(path.getFile().getAbsolutePath(), path).get(0);
        }
        catch (Exception ex) {
            throw new IllegalStateException("Failed to load yaml configuration from " + path, ex);
        }
    }
}
複製程式碼

最關鍵的一步就是讓SpringBoot能載入到我們這個PostProcessor,還是老樣子,在spring,factories檔案中加入一項配置即可:

org.springframework.boot.env.EnvironmentPostProcessor=me.josephzhu.spring101customstarter.MyPropertySourceEnvironmentPostProcessor
複製程式碼

現在,我們可以在starter專案下的resrouces目錄下建立一個config.yml來驗證一下:

spring101:
  name: zhuye_yml
複製程式碼

重新執行main專案可以看到如下的輸出結果中包含了yml字樣:

2018-09-30 15:27:05.123  INFO 12769 --- [           main] me.josephzhu.spring101main.Runner1       : V1 Hello >> zhuye_yml:35 !!
複製程式碼

我們可以為專案新增一下Actuator模組進行進一步驗證:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
複製程式碼

在配置檔案中加入設定來放開所有的埠訪問:

management.endpoints.web.exposure.include=*
複製程式碼

然後開啟瀏覽器訪問http://127.0.0.1:8080/actuator/env:

朱曄和你聊Spring系列S1E2:SpringBoot並不神祕
可以看到,的確是新增了我們自定義的config.yml作為PropertySource。

自動配置的除錯問題

對於複雜的專案,如果我們發現自動配置不起作用,要搞清楚框架是如何在各種條件中做自動配置以及自動配置的匹配過程是比較麻煩的事情,這個時候我們可以開啟SpringBoot的Debug來檢視日誌:

朱曄和你聊Spring系列S1E2:SpringBoot並不神祕
我們可以在日誌中搜尋我們關注的型別的匹配情況,是不是很直觀呢:

MyAutoConfiguration#getMyService matched:
      - @ConditionalOnProperty (spring101.version=v1) matched (OnPropertyCondition)
      - @ConditionalOnMissingBean (types: me.josephzhu.spring101customstarter.MyService; SearchStrategy: all) did not find any beans (OnBeanCondition)
MyAutoConfiguration#getMyServiceV2:
      Did not match:
         - @ConditionalOnProperty (spring101.version=v2) did not find property 'version' (OnPropertyCondition)
複製程式碼

我們可以試試在出錯的時候系統給我們的提示,來把配置檔案中的version設定為3:

spring101.version=v3
複製程式碼

重新執行後看到如下輸出:

***************************
APPLICATION FAILED TO START
**************************
Description:
Field service in me.josephzhu.spring101main.Runner1 required a bean of type 'me.josephzhu.spring101customstarter.AbstractMyService' that could not be found.
	- Bean method 'getMyService' in 'MyAutoConfiguration' not loaded because @ConditionalOnProperty (spring101.version=v1) found different value in property 'version'
	- Bean method 'getMyServiceV2' in 'MyAutoConfiguration' not loaded because @ConditionalOnProperty (spring101.version=v2) found different value in property 'version'
Action:
Consider revisiting the entries above or defining a bean of type 'me.josephzhu.spring101customstarter.AbstractMyService' in your configuration.
複製程式碼

這個所謂的分析報告是比較機械性的,作為框架的開發者,我們甚至可以自定義叫做FailureAnalyzer的東西來做更明確的提示。實現上和之前的步驟幾乎一樣,首先自定義一個類:

package me.josephzhu.spring101customstarter;

import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
import org.springframework.boot.diagnostics.FailureAnalysis;

public class MyFailureAnalyzer extends AbstractFailureAnalyzer<NoSuchBeanDefinitionException> {
    @Override
    protected FailureAnalysis analyze(Throwable rootFailure, NoSuchBeanDefinitionException cause) {
        if(cause.getBeanType().equals(AbstractMyService.class))
            return new FailureAnalysis("載入MyService失敗", "請檢查配置檔案中的version屬性設定是否是v1或v2", rootFailure);

        return null;
    }
}
複製程式碼

這裡我們根據cause的Bean型別做了簡單判斷,如果發生錯誤的是我們的Service型別的話,告知使用者明確的錯誤原因(Description)以及怎麼來糾正這個錯誤(Action)。 然後老規矩,在spring.factories中進行關聯:

org.springframework.boot.diagnostics.FailureAnalyzer=me.josephzhu.spring101customstarter.MyFailureAnalyzer
複製程式碼

重新執行程式後可以看到如下的結果:

***************************
APPLICATION FAILED TO START
***************************
Description:
載入MyService失敗
Action:
請檢查配置檔案中的version屬性設定是否是v1或v2
複製程式碼

是不是直觀很多呢?這裡我的實現比較簡單,在正式的實現中你可以根據上下文以及環境的各種情況為使用者進行全面的分析,分析服務啟動失敗的原因。

SpringBoot的擴充套件點

在之前的幾個例子中,我們進行了各種擴充套件配置,通過spring.factories進行了自動配置、環境後處理器配置以及錯誤分析器配置:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=me.josephzhu.spring101customstarter.MyAutoConfiguration
org.springframework.boot.env.EnvironmentPostProcessor=me.josephzhu.spring101customstarter.MyPropertySourceEnvironmentPostProcessor
org.springframework.boot.diagnostics.FailureAnalyzer=me.josephzhu.spring101customstarter.MyFailureAnalyzer
複製程式碼

其實,SpringBoot還有一些其它的擴充套件槽,如下是SpringBoot自帶的一些配置類:

# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientPropertiesEnvironmentPostProcessor

# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener

# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jdbc.JdbcRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.jest.JestAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.rest.RestClientAutoConfiguration,\
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\
org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\
org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\
org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration,\
org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration,\
org.springframework.boot.autoconfigure.influx.InfluxDbAutoConfiguration,\
org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,\
org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,\
org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\
org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration,\
org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,\
org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration,\
org.springframework.boot.autoconfigure.reactor.core.ReactorCoreAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityRequestMatcherProviderAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration,\
org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\
org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration,\
org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\
org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\
org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.function.client.ClientHttpConnectorAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.reactive.WebSocketReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration

# Failure analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=\
org.springframework.boot.autoconfigure.diagnostics.analyzer.NoSuchBeanDefinitionFailureAnalyzer,\
org.springframework.boot.autoconfigure.jdbc.DataSourceBeanCreationFailureAnalyzer,\
org.springframework.boot.autoconfigure.jdbc.HikariDriverConfigurationFailureAnalyzer,\
org.springframework.boot.autoconfigure.session.NonUniqueSessionRepositoryFailureAnalyzer

# Template availability providers
org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider=\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.mustache.MustacheTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.web.servlet.JspTemplateAvailabilityProvider
複製程式碼

我們可以看到一共有8大類的擴充套件槽,這些槽貫穿了整個SpringBoot載入的整個過程,感興趣的讀者可以逐一搜尋檢視Spring的文件和原始碼進行進一步的分析。 本文從如何做一個Starter實現自動配置開始,進一步闡述瞭如何實現智慧的條件配置,如何,如何進行自動配置的測試,然後我們自定義了環境後處理器來載入額外的配置源(你當然可以實現更復雜的配置源,比如從Redis和資料庫中獲取配置)以及通過開啟Actutor來驗證,定義了配置錯誤分析器來給使用者明確的錯誤提示。最後,我們看了一下spring.factories中的內容瞭解了SpringBoot內部定義的一些擴充套件槽和實現。

本文程式碼位於 github.com/JosephZhu19…

相關文章