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

1 自動配置

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

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

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

pring-boot-autoconfigure包含了很多自動配置項(xiàng),例如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來查看該源碼,且你的項(xiàng)目中沒有包含Spring Kafka相關(guān)的依賴項(xiàng)咆瘟,應(yīng)該會看到KafkaTemplate被標(biāo)紅了嚼隘,即IDE找不到該類,所以最終KafkaAutoConfiguration不會被初始化袒餐,項(xiàng)目中也不會存在KafkaTemplate這個Bean飞蛹。而如果此時加入Spring Kafka的依賴項(xiàng),那么KafkaAutoConfiguration就會被初始化灸眼,最終應(yīng)用中會存在KafkaTemplate這個Bean卧檐,用戶可以直接依賴注入到需要用到的地方而不需要做什么配置,因?yàn)镵afkaAutoConfiguration這個類里都幫我們做了一些默認(rèn)的配置焰宣。

如果項(xiàng)目確實(shí)對KafkaTemplate有什么特殊的配置霉囚,仍然可以選擇自己手動配置,一般有兩種方法:

  1. 自己創(chuàng)建一個KafkaTemplate的Bean匕积,這個應(yīng)該不難盈罐。在配置類中用@Bean注解即可榜跌。
  2. 在配置文件application.properties配置一些KafkaTemplate的配置項(xiàng),這些配置項(xiàng)會覆蓋默認(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這個配置項(xiàng)潜必,完整的是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的配置信息的等等惶室,所以我以上說的都只是大致原理,真正的實(shí)現(xiàn)其實(shí)異常復(fù)雜P酢;食!

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

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

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

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

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

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

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

spring-boot-autoconfigure里其實(shí)包含了很多XXXAutoConfiguration,例如KafkaAutoConfiguration,HibernateJpaAutoConfiguration等等叠穆∩倨幔基本都是Spring家族的項(xiàng)目,但對于我們自制的starter就需要在項(xiàng)目中寫XXAutoConfiguration類了硼被,畢竟Spring boot不能提前預(yù)知所有用戶的需求不是示损?好了,不多說了嚷硫,直接動手實(shí)現(xiàn)吧检访!

首先構(gòu)建一個Maven項(xiàng)目(其他的構(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>

其實(shí)只加入了spring-boot-autoconfigure依賴脆贵,因?yàn)楸容^簡單嘛,不需要太多東西起暮。

然后創(chuàng)建一個Properties類(其實(shí)沒有也沒什么關(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我們是沒法動的,所以就只能在自己的項(xiàng)目中動刀子了逞敷。

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

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

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

這才算完事椭更,然后測試一下?先打包蛾魄,打包過程我就不說了虑瀑,然后新建一個項(xiàng)目,把剛剛打包好的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)在來試試修改配置項(xiàng),如下所示:

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

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

是不是很神奇?

3 小結(jié)

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

最后除呵,Spring水很深再菊,道阻且長!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末颜曾,一起剝皮案震驚了整個濱河市纠拔,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌泛豪,老刑警劉巖稠诲,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異诡曙,居然都是意外死亡臀叙,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進(jìn)店門价卤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來劝萤,“玉大人,你說我怎么就攤上這事慎璧〈蚕樱” “怎么了?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵炸卑,是天一觀的道長既鞠。 經(jīng)常有香客問我煤傍,道長盖文,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任蚯姆,我火速辦了婚禮五续,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘龄恋。我一直安慰自己疙驾,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布郭毕。 她就那樣靜靜地躺著它碎,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上扳肛,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天傻挂,我揣著相機(jī)與錄音,去河邊找鬼挖息。 笑死金拒,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的套腹。 我是一名探鬼主播绪抛,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼电禀!你這毒婦竟也來了幢码?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤鞭呕,失蹤者是張志新(化名)和其女友劉穎蛤育,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體葫松,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡瓦糕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了腋么。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片咕娄。...
    茶點(diǎn)故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖珊擂,靈堂內(nèi)的尸體忽然破棺而出圣勒,到底是詐尸還是另有隱情,我是刑警寧澤摧扇,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布圣贸,位于F島的核電站,受9級特大地震影響扛稽,放射性物質(zhì)發(fā)生泄漏吁峻。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一在张、第九天 我趴在偏房一處隱蔽的房頂上張望用含。 院中可真熱鬧,春花似錦帮匾、人聲如沸啄骇。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽缸夹。三九已至痪寻,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間虽惭,已是汗流浹背槽华。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留趟妥,地道東北人猫态。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像披摄,于是被迫代替她去往敵國和親亲雪。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評論 2 360

推薦閱讀更多精彩內(nèi)容