SpringBoot(04)——建立自己的自動配置

elim168發表於2018-11-01

建立自己的自動配置

當你的應用需要以jar包的形式提供給其它應用使用時,可以考慮把它們封裝為一個Spring Boot Starter。即該jar包是可以自動新增需要引用的依賴項,也能夠對核心功能進行自動配置。自動配置的核心類是一個標註了@Configuration的類,然後在自動配置類中可以定義相應的bean。比如下面的配置類中定義了一個HelloBean型別的bean。

@Configuration
public class HelloAutoConfiguration {

    @Bean
    public HelloBean helloBean() {
        return new HelloBean();
    }
    
}

然後需要在Classpath下的META-INF/spring.factories中以org.springframework.boot.autoconfigure.EnableAutoConfiguration為Key,以對應的自動配置類為Value進行配置,如果有多個自動配置類,多個自動配置類之間可以以英文逗號分隔。

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.elim.autoconfigure.HelloAutoConfiguration

Spring Boot在啟動時將讀取Classpath下META-INF/spring.factories中Key為org.springframework.boot.autoconfigure.EnableAutoConfiguration的配置類,將它們進行例項化。所以經過上述配置後,系統啟動後將自動建立HelloBean型別的bean。

自動配置類通常不會像上面那樣直接進行定義,而是會新增一些附加條件,比如在Classpath中擁有某些Class才生效,或者需要bean容器中不存在指定bean時才生效等等。Spring Boot為它們提供了一系列的@ConditionalXXX,常用的如下:

  • ConditionalOnClass :用於指定在Classpath下擁有某些Class時才生效
  • ConditionalOnMissingClass :用於指定在Classpath下不存在某些Class時才生效
  • ConditionalOnBean :用於指定在bean容器中存在某些bean時生效
  • ConditionalOnMissingBean :用於指定在bean容器中不存在某些bean時生效
  • ConditionalOnWebApplication :用於指定當應用是Web應用時生效
  • ConditionalOnNotWebApplication :用於指定當應用是非Web應用時生效
  • ConditionalOnProperty :用於指定當配置了某些特定的引數時生效
  • ConditionalOnExpression :用於根據SpEl表示式控制是否生效
  • ConditionalOnSingleCandidate :用於指定當bean容器中只存在唯一的指定型別的bean時才生效;當bean容器中存在多個指定型別的bean,但是使用@Primary指定了主候選者也是可以匹配的,即也是生效的

更多可用的Conditional註解可以參考API文件的org.springframework.boot.autoconfigure.condition包。

下面的程式碼中指定了當Classpath下存在Hello.class,且bean容器中不存在HelloBean型別的bean時下面的配置類將生效。

@Configuration
@ConditionalOnClass(Hello.class)
@ConditionalOnMissingBean(HelloBean.class)
public class HelloAutoConfiguration {

    @Bean
    public HelloBean helloBean() {
        return new HelloBean();
    }
    
}

這些條件配置註解也是可以新增到bean上的。比如下面程式碼中指定了當Environment中存在Key為autoconfigure.hello.enabled屬性且其值為true時將建立HelloBean型別的bean,或者當Environment中不存在Key為autoconfigure.hello.enabled屬性時也將建立HelloBean型別的bean(由matchIfMissing控制)。所以針對下面的配置,預設情況下是會建立HelloBean型別的bean的,如果不期望建立該型別的bean,可以在application.properties檔案中指定autoconfigure.hello.enabled=false

@Configuration
@ConditionalOnClass(Hello.class)
@ConditionalOnMissingBean(HelloBean.class)
public class HelloAutoConfiguration {

    @Bean
    @ConditionalOnProperty(prefix = "autoconfigure.hello", name = "enabled", havingValue = "true", matchIfMissing = true)
    public HelloBean helloBean() {
        return new HelloBean();
    }

}

當使用@ConditionalOnProperty時如果對應的值的可選值是true/false,可以不指定havingValue屬性,此時只要值不為false,都會認為是true。

繫結引數

通常自定義的Starter會需要依靠外部配置的屬性進行一些自動配置。此時可以使用@ConfigurationProperties標註在用來接收屬性的Class上,它可以指定一個字首,然後將在application.properties中尋找指定字首和欄位名稱組合起來的屬性進行繫結。比如下面的屬性類中的name屬性將繫結application.properties中定義的autoconfigure.hello.name屬性的值。

@Data
@ConfigurationProperties("autoconfigure.hello")
public class HelloProperties {

    private String name;
    
    private String message;
    
}

@ConfigurationProperties標註的Class需要通過在配置類上使用@EnableConfigurationProperties進行啟用。@EnableConfigurationProperties指定的配置類會自動註冊為Spring bean容器中的一個bean,然後可以在配置類中自動注入對應的屬性類。比如下面的程式碼中在HelloAutoConfiguration類上通過@EnableConfigurationProperties(HelloProperties.class)指定了啟用HelloProperties這個屬性配置類,然後把它定義為HelloAutoConfiguration類中的一個屬性,並標註為自動注入,然後在定義HelloBean型別的bean時從HelloProperties中獲取屬性值進行配置。

@Configuration
@ConditionalOnClass(Hello.class)
@ConditionalOnMissingBean(HelloBean.class)
@EnableConfigurationProperties(HelloProperties.class)
public class HelloAutoConfiguration {

    @Autowired
    private HelloProperties helloProperties;
    
    @Bean
    @ConditionalOnProperty(prefix = "autoconfigure.hello", name = "enabled", matchIfMissing = true)
    public HelloBean helloBean() {
        HelloBean helloBean = new HelloBean();
        helloBean.setName(helloProperties.getName());
        return helloBean;
    }

}

上面的程式碼中定義HelloBean時從HelloProperties中獲取name屬性賦值給了HelloBean物件的name屬性。這樣的需求其實可以直接通過@ConfigurationProperties給HelloBean的name屬性賦值,而不必新增多餘的HelloProperties類。把@ConfigurationProperties定義在HelloBean定義的方法上可以擁有相同的效果,比如下面這樣。

@Configuration
@ConditionalOnClass(Hello.class)
@ConditionalOnMissingBean(HelloBean.class)
public class HelloAutoConfiguration {

    @Bean
    @ConditionalOnProperty(prefix = "autoconfigure.hello", name = "enabled", matchIfMissing = true)
    @ConfigurationProperties("autoconfigure.hello")
    public HelloBean helloBean() {
        return new HelloBean();
    }

}

使用@ConfigurationProperties定義屬性配置類時最好定義的字首不要以spring開頭,以免跟未來Spring官方提供的屬性配置類存在衝突。

自定義的Starter在命名時不要命名為spring-boot-starter-xxx,可以命名為xxx-spring-boot-starterspring-boot-starter-xxx留給官方使用。

參考文件

https://docs.spring.io/spring-boot/docs/2.0.3.RELEASE/reference/html/boot-features-developing-auto-configuration.html

(注:本文基於Spring Boot 2.0.3所寫)


相關文章