實現(xiàn)一個Spring boot stater

1 自動配置

Spring boot的一大特性就是“自動配置”。在傳統(tǒng)的Spring應(yīng)用開發(fā)中,開發(fā)者往往需要寫很多的XML配置項,包括數(shù)據(jù)源的配置,組件Bean的配置,數(shù)據(jù)庫事務(wù)的配置等等,但如果使用Spring Boot的話,往往不需要做這些配置,只需要添加對應(yīng)的依賴庫即可,即所謂的“開箱即用”,這可以讓應(yīng)用開發(fā)者把更多的精力放在業(yè)務(wù)邏輯上,而不是一大堆的亂七八糟的配置上,對于這點,我想只要有傳統(tǒng)SSM項目開發(fā)經(jīng)驗以及Spring Boot開發(fā)經(jīng)驗的朋友應(yīng)該能體會到。

那Spring Boot自動配置的原理是什么呢?它又是如何實現(xiàn)的呢?

Spring Boot的自動配置關(guān)鍵是spring-boot-autoconfigure依賴,如果仔細(xì)觀察,會發(fā)現(xiàn)Spring boot starter包含了該依賴,如下圖所示(我這里用的2.0.0版本):

pring-boot-autoconfigure包含了很多自動配置項,例如JPA的自動配置,Kafka的自動配置等,如下圖所示(僅截了部分):

進(jìn)一步查看源碼,會發(fā)現(xiàn)每一個包下都有一個XXXAutoConfiguration(XXX表示就是組件名稱,例如JPA,Kafka等),下面是KafkaAutoConfiguration的部分源碼:

@Configuration
@ConditionalOnClass(KafkaTemplate.class)
@EnableConfigurationProperties(KafkaProperties.class)
@Import(KafkaAnnotationDrivenConfiguration.class)
public class KafkaAutoConfiguration {

    private final KafkaProperties properties;

    private final RecordMessageConverter messageConverter;
    
    .........
}
  1. @Configuration注解表示這是一個配置類,用過Spring Boot的朋友都應(yīng)該知道是什么東西,不多做解釋了。
  2. @ConditionalOnClass(KafkaTemplate.class),該注解表示當(dāng)KafkaTemplate.class被加載到JVM中,才會對配置類進(jìn)行初始化,簡單理解就是把他當(dāng)做if語句來看。
  3. @EnableConfigurationProperties(KafkaProperties.class),加載屬性配置類的注解,有這個注解,KafkaProperties才會作用于應(yīng)用上下文中。
  4. @Import(KafkaAnnotationDrivenConfiguration.class),Spring 的基礎(chǔ)注解,不多說了。

如果你用IDEA來查看該源碼,且你的項目中沒有包含Spring Kafka相關(guān)的依賴項,應(yīng)該會看到KafkaTemplate被標(biāo)紅了,即IDE找不到該類,所以最終KafkaAutoConfiguration不會被初始化,項目中也不會存在KafkaTemplate這個Bean。而如果此時加入Spring Kafka的依賴項,那么KafkaAutoConfiguration就會被初始化,最終應(yīng)用中會存在KafkaTemplate這個Bean,用戶可以直接依賴注入到需要用到的地方而不需要做什么配置,因為KafkaAutoConfiguration這個類里都幫我們做了一些默認(rèn)的配置。

如果項目確實對KafkaTemplate有什么特殊的配置,仍然可以選擇自己手動配置,一般有兩種方法:

  1. 自己創(chuàng)建一個KafkaTemplate的Bean,這個應(yīng)該不難。在配置類中用@Bean注解即可。
  2. 在配置文件application.properties配置一些KafkaTemplate的配置項,這些配置項會覆蓋默認(rèn)配置。

那Spring Boot是如何發(fā)現(xiàn)這些東西的呢?也就是說Spring boot是怎么知道這是一個自動配置類的呢?主要有兩個,一是@EnableAutoConfiguration注解,而是spring.factories配置文件,現(xiàn)在看看spring.factories配置文件,該文件在類路徑中META-INF文件夾下,如下圖所示:

文件里有什么配置呢?打開看看就知道了,文件里的org.springframework.boot.autoconfigure.EnableAutoConfiguration配置就是我們今天討論的關(guān)鍵,可以發(fā)現(xiàn),該鍵對應(yīng)著很多值,每個值之間用逗號分隔(\是換行),搜索一下可以發(fā)現(xiàn),存在KafkaAutoConfiguration這個配置項,完整的是org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration。

接下來來看看@EnableAutoConfiguration注解,該注解在@SpringBootApplication中有用到,所以只要加入了@SpringBootApplication注解,也就加入了@EnableAutoConfiguration注解,其源碼如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};

}

關(guān)鍵是@Import(AutoConfigurationImportSelector.class)注解,該注解導(dǎo)入了AutoConfigurationImportSelector這個類,該類是自動配置的核心,從名字上看可以看出這應(yīng)該是一個選擇器。其部分代碼如下:

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
            AnnotationAttributes attributes) {
         //加載spring.factories配置文件的配置信息
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
                getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
        Assert.notEmpty(configurations,
                "No auto configuration classes found in META-INF/spring.factories. If you "
                        + "are using a custom packaging, make sure that file is correct.");
        return configurations;
    }

現(xiàn)在可以把上面說到的串起來了。

  1. 在應(yīng)用的配置類上加入@EnableAutoConfiguration注解,該注解會導(dǎo)入AutoConfigurationImportSelector類。
  2. 啟動應(yīng)用的時候,Spring Boot會獲取spring.factories里的配置信息。(AutoConfigurationImportSelector。getCandidateConfigurations()方法)
  3. 獲取到信息之后,Spring Boot就會嘗試去觸發(fā)XXXAutoConfiguration,是否能觸發(fā)還取決于具體AutoConfiguration類,例如在KafkaAutoConfiguration中,如果缺少KafkaTemplate類的存在,那么就不會觸發(fā)KafkaAutoConfiguration的執(zhí)行,如果觸發(fā)成功,就會執(zhí)行KafkaAutoConfiguration里的代碼,這時候會發(fā)生什么就取決于具體的代碼邏輯了。

簡單概括就是以上三個流程。當(dāng)然,其中的細(xì)節(jié)還有很多,例如Spring Boot是如何去讀取spring.factories的配置信息的等等,所以我以上說的都只是大致原理,真正的實現(xiàn)其實異常復(fù)雜?。?!

這里還要一說的是spring.factories文件并不僅僅包含了自動配置相關(guān)的配置信息,還包含其他一些信息,所以不要誤以為spring.factories是專門為自動配置服務(wù)的。

2 動手實現(xiàn)一個Starter

Spring Boot系列有很多的starter,例如spring-boot-starter-web,spring-boot-starter-data-jpa等等,我們知道加入spring-boot-starter-web依賴,不需要任何配置,就可以直接構(gòu)建Web應(yīng)用了,這就是自動配置的威力。實際上,Spring作為一個擴(kuò)展性比較強(qiáng)的框架,還允許用戶自己編寫符合需求的starter。

一個完整的starter組件至少需要包含兩個部分:

  • 提供自動配置功能的自動配置模塊。
  • 提供依賴關(guān)系管理功能的組件模塊,即封裝了組件所有功能,開箱即用。

具體的來說,就是需要一個XXXAutoConfiguration自動配置類以及一個封裝好的功能模塊,例如JPATemplate等等,用的時候直接依賴注入即可。

這時候可能有讀者要問了,在spring-boot-starter-web項目里好像沒發(fā)現(xiàn)什么XXXAutoConfiguration啊,其實是有的,和Web相關(guān)的自動配置類被Spring寫到spring-boot-autoconfigure項目里了,即ServletWebServerFactoryAutoConfiguration。

spring-boot-autoconfigure里其實包含了很多XXXAutoConfiguration,例如KafkaAutoConfiguration,HibernateJpaAutoConfiguration等等?;径际荢pring家族的項目,但對于我們自制的starter就需要在項目中寫XXAutoConfiguration類了,畢竟Spring boot不能提前預(yù)知所有用戶的需求不是?好了,不多說了,直接動手實現(xiàn)吧!

首先構(gòu)建一個Maven項目(其他的構(gòu)建框架也可以,挑一個熟悉就行),pom文件如下所示:

<?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>top.yeonon</groupId>
    <artifactId>car-server-starter</artifactId>
    <version>1.0</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.0.RELEASE</version>
    </parent>

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

</project>

其實只加入了spring-boot-autoconfigure依賴,因為比較簡單嘛,不需要太多東西。

然后創(chuàng)建一個Properties類(其實沒有也沒什么關(guān)系,最好還是有,提供一些靈活性):

@ConfigurationProperties(prefix = "yeonon.car")
public class CarProperties {

    private static final String DEFAULT_NAME = "BWM";
    private static final String DEFAULT_COLOR = "white";
    private static final Integer DEFAULT_AGE = 1;


    private String name = DEFAULT_NAME;

    private String color = DEFAULT_COLOR;

    private Integer age = DEFAULT_AGE;

    //getter 和 setter必須要有,否則屬性注入會有問題
}

之后創(chuàng)建我們的功能模塊:

public class CarService {

    private Car car;

    public Car getCar() {
        return car;
    }

    public void setCar(Car car) {
        this.car = car;
    }
}

非常簡單,就是獲取Car對象而已。Car類就不貼出來了,就是一個POJO而已。下面就是核心類了,即CarAutoConfiguration自動配置類,如下所示:

@Configuration
@ConditionalOnClass(CarService.class)
@EnableConfigurationProperties(value = CarProperties.class)
public class CarAutoConfiguration {

    @Autowired
    private CarProperties carProperties;

    @Bean
    @ConditionalOnMissingBean(CarService.class)
    @ConditionalOnProperty(value = "yeonon.car.server.configuration.auto.enabled", matchIfMissing = true)
    public CarService carService() {
        CarService carService = new CarService();
        Car car = new Car();
        car.setName(carProperties.getName());
        car.setColor(carProperties.getColor());
        car.setAge(carProperties.getAge());
        carService.setCar(car);
        return carService;
    }
}

  1. @Configuration注解是要有的,否則無法生效。
  2. @ConditionalOnClass(CarService.class),原則上可有可無,但作為一個健壯的starter,還是有的好,作為一個“防御措施”。
  3. @EnableConfigurationProperties(value = CarProperties.class),要使用屬性系統(tǒng),就需要把屬性類加入到應(yīng)用上下文中。
  4. @Bean就是定義一個Bean了,代碼邏輯沒什么可說的,無法就是創(chuàng)建對象,然后構(gòu)建對象并返回而已。關(guān)鍵在于@ConditionalOnMissingBean注解和@ConditionalOnProperty注解。@ConditionalOnMissingBean注解的意思就是如果應(yīng)用中不存在CarService的Bean,那么就執(zhí)行下面的方法構(gòu)建一個Bean,已經(jīng)存在的話,就不會調(diào)用下面的方法了,這意味著用戶可以自己創(chuàng)建Bean來覆蓋系統(tǒng)默認(rèn)配置的Bean。@ConditionalOnProperty就是當(dāng)配置存在的時候,才會執(zhí)行Bean的構(gòu)建。

打完收工!別著急!別忘了要配置spring.factories,spring-boot-autoconfigure里的spring.factories我們是沒法動的,所以就只能在自己的項目中動刀子了。

在resource文件夾(其實就是類路徑classpath)下創(chuàng)建一個META-INF文件夾,為什么要創(chuàng)建這玩意?問Spring去吧!然后創(chuàng)建spring.factories文件(不要打錯一個字!)。在里面寫入如下內(nèi)容:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
top.yeonon.stater.test.CarAutoConfiguration

org.springframework.boot.autoconfigure.EnableAutoConfiguration這個鍵名是不是很熟悉,沒錯,就是spring-boot-autoconfigure中的spring.factories里的鍵名,在這里我們只需要把我們自己的CarAutoConfiguration全限定類名加入就行了。

這才算完事,然后測試一下?先打包,打包過程我就不說了,然后新建一個項目,把剛剛打包好的car-starter作為依賴加入進(jìn)去,如下所示:

<dependency>
    <groupId>top.yeonon</groupId>
    <artifactId>car-server-starter</artifactId>
    <version>1.0</version>
</dependency>

為了方便測試,也加入spring-boot-starter-web吧,然后寫一個Controller,如下所示:

@RestController
@RequestMapping("/hello")
public class HelloController {

    @Autowired
    private CarService carService;

    @GetMapping("car")
    public Car getMyCar() {
        return carService.getCar();
    }
}

運(yùn)行一下,訪問該路徑,大致可以得到如下輸出:

這里的值還是我們之前的默認(rèn)值(返回去看看CarProperties類),Spring Boot的屬性系統(tǒng)也是相當(dāng)厲害,現(xiàn)在來試試修改配置項,如下所示:

## 注意前綴是yeonon.car,也是在CarProperties類里配置好的
yeonon.car.name=MSLD
yeonon.car.age=2
yeonon.car.color=black

然后重啟項目,再次訪問,得到的結(jié)果應(yīng)該是這樣:

是不是很神奇?

3 小結(jié)

Spring Boot的自動配置非常強(qiáng)大,免去了很多配置文件,用得好的話會覺得既方便又靈活,用不好的話可能會發(fā)生一些配置沖突的問題(不過其實都是能解決的)。自己編寫stater也是可行的,不過要確定確實需要自定義的starter,否則最好還是不要給自己挖坑哈,例如我們的Car stater這個例子,實際上如果僅僅是為了實現(xiàn)這個功能,完全不需要專門寫一個starter。

最后,Spring水很深,道阻且長!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容