如何開(kāi)發(fā)自己的Spring Boot Starter
我們?cè)谑褂?Spring Boot 的過(guò)程中,往往都是在pom.xml里加了一系列的依賴,然后啟支一個(gè)包含main方法的Application,一切就OK啦。給你我的感覺(jué),就像是自己要?jiǎng)邮肿鰝€(gè)菜,自己不再需要準(zhǔn)備每一部分的原材料,直接購(gòu)買包裝好的一份菜的原料,下鍋即可。
那我們?cè)敿?xì)看下,這份「包裝好」的原料中,到底做了些什么。
添加Starter依賴
這里添加的依賴,除了我們之前在Maven中熟悉的之外,還有一些都是長(zhǎng)這個(gè)樣子:
名為xxx-starter,比如
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
- <dependency>
- <groupId>org.mybatis.spring.boot</groupId>
- <artifactId>mybatis-spring-boot-starter</artifactId>
- <version>1.3.2</version>
- </dependency>
具體這些starter是怎么起作用的呢,他們什么時(shí)候開(kāi)始工作的?
一切都要從入口處說(shuō)起。我們以上面的starter為例,看到這個(gè)mybatis的starter,其對(duì)應(yīng)的pom中,包含這些依賴
- <dependencies>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-jdbc</artifactId>
- </dependency>
- <dependency>
- <groupId>org.mybatis.spring.boot</groupId>
- <artifactId>mybatis-spring-boot-autoconfigure</artifactId>
- </dependency>
- <dependency>
- <groupId>org.mybatis</groupId>
- <artifactId>mybatis</artifactId>
- </dependency>
- <dependency>
- <groupId>org.mybatis</groupId>
- <artifactId>mybatis-spring</artifactId>
- </dependency>
- </dependencies>
我們看到,相當(dāng)于我們添加了一個(gè)Starter的依賴,其背后會(huì)引入許多其定義的其他依賴,通過(guò) Maven 的傳遞依賴,這些都會(huì)被自動(dòng)添加了進(jìn)來(lái)。
自動(dòng)配置
相比傳統(tǒng)的依賴,我們看到其中包含這樣一個(gè):mybatis-spring-boot-autoconfigure,這也是每個(gè)Starter的秘密所在:「AutoConfigure」
它會(huì)在實(shí)現(xiàn)時(shí),考慮應(yīng)用中的其他部分因素,「推斷」你所需要的 Spring 配置。
在Spring Boot中,我們***的感受是配置仿佛都被做好了,直接使用即可,這就是
spring-boot-autoconfigure. 每個(gè)starter都有一個(gè)名為spring.factories
的文件,存放在META-INF目錄下,其中的內(nèi)容類似下面這個(gè)樣子:
- # Auto Configure
- org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
- org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
所有需要自動(dòng)配置的Class,都需要配置成key是EnableAutoConfiguration的。
我們來(lái)看類的內(nèi)部
- @Configuration
- @ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
- @ConditionalOnBean({DataSource.class})
- @EnableConfigurationProperties({MybatisProperties.class})
- @AutoConfigureAfter({DataSourceAutoConfiguration.class})
- public class MybatisAutoConfiguration {
Class 之上, 有不少注解來(lái)標(biāo)識(shí),有幾點(diǎn)需要關(guān)注的:
- 其中有標(biāo)準(zhǔn)的 Spring 配置注解 @Configuration
- 幾個(gè)@ConditionalXX
- 標(biāo)識(shí)執(zhí)行順序的@AutoConfigureAfter
其中,@ConditionalOnClass 標(biāo)識(shí) SqlSessionFactory類存在時(shí),執(zhí)行該配置, @ConditionalOnBean標(biāo)識(shí)DataSource Bean在 Spring Context時(shí),執(zhí)行配置。
這些spring.factories是怎么被識(shí)別的呢? 這就得夸下 Spring 的FactoriesLoader了。
看下官方文檔說(shuō)明
- Auto-configuration classes are regular Spring {@link Configuration} beans. They are located using the {@link SpringFactoriesLoader} mechanism (keyed against this class).
- Generally auto-configuration beans are {@link Conditional @Conditional} beans (most
- often using {@link ConditionalOnClass @ConditionalOnClass} and
- {@link ConditionalOnMissingBean @ConditionalOnMissingBean} annotations).
啟動(dòng)的時(shí)候,根據(jù)ClassLoader中的jar,掃描所有 spring.factories,將其中符合條件的過(guò)濾出來(lái),執(zhí)行對(duì)應(yīng)的配置。重點(diǎn)可以關(guān)注下
- protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
- return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class,
- this.beanClassLoader);
- }
- AutoConfigurationMetadata autoConfigurationMetadata) {
- long startTime = System.nanoTime();
- String[] candidates = StringUtils.toStringArray(configurations);
- boolean[] skip = new boolean[candidates.length];
- boolean skipped = false;
- for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
- invokeAwareMethods(filter);
- boolean[] match = filter.match(candidates, autoConfigurationMetadata);
- for (int i = 0; i < match.length; i++) {
- if (!match[i]) {
- skip[i] = true;
- skipped = true;
- }
- }
- }
- if (!skipped) {
- return configurations;
- }
- List<String> result = new ArrayList<>(candidates.length);
- for (int i = 0; i < candidates.length; i++) {
- if (!skip[i]) {
- result.add(candidates[i]);
- }
- }
- return new ArrayList<>(result);
- }
- public String[] selectImports(AnnotationMetadata annotationMetadata) {
- if (!isEnabled(annotationMetadata)) {
- return NO_IMPORTS;
- }
- AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
- .loadMetadata(this.beanClassLoader);
- AnnotationAttributes attributes = getAttributes(annotationMetadata);
- List<String> configurations = getCandidateConfigurations(annotationMetadata,
- attributes);
- configurations = removeDuplicates(configurations);
- Set<String> exclusions = getExclusions(annotationMetadata, attributes);
- checkExcludedClasses(configurations, exclusions);
- configurations.removeAll(exclusions);
- configurations = filter(configurations, autoConfigurationMetadata);
- fireAutoConfigurationImportEvents(configurations, exclusions);
- return StringUtils.toStringArray(configurations);
- }
經(jīng)過(guò)這里的執(zhí)行之后, filter方法把符合條件的過(guò)濾出來(lái)了。
創(chuàng)建自定義Starter
經(jīng)過(guò)上面兩步,我們大概知道 Starter的工作原理。有時(shí)候,我們需要對(duì)外提供一些工具組件時(shí),也想以 Starter 的形式提供出來(lái),供別人使用。步驟也還算清晰,照葫蘆畫(huà)瓢。
- 先創(chuàng)建自己的模塊
- 增加需要用到的依賴
- 創(chuàng)建對(duì)應(yīng)的 AutoConfiguration類
- 創(chuàng)建META-INF/spring.factories 文件
此時(shí),就不需要再將 Spring Boot 做為 Parent依賴,在單獨(dú)的依賴中增加
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-autoconfigure</artifactId>
- <version>2.0.6.RELEASE</version>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter</artifactId>
- <version>2.0.6.RELEASE</version>
- </dependency>
AutoConfiguration類也簡(jiǎn)單,照上面的創(chuàng)建一個(gè)
- @Configuration
- @ConditionalOnClass(HelloService.class)
- public class HelloServiceAutoConfiguration {
然后,增加文件
- org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.example.demo.HelloServiceAutoConfiguration
在需要這個(gè)服務(wù)的地方,直接引入依賴就OK啦。
【本文為51CTO專欄作者“侯樹(shù)成”的原創(chuàng)稿件,轉(zhuǎn)載請(qǐng)通過(guò)作者微信公眾號(hào)『Tomcat那些事兒』獲取授權(quán)】