深圳幻海软件技术有限公司 欢迎您!

如何开发自己的Spring Boot Starter

2023-02-26

我们在使用SpringBoot的过程中,往往都是在pom.xml里加了一系列的依赖,然后启支一个包含main方法的Application,一切就OK啦。给你我的感觉,就像是自己要动手做个菜,自己不再需要准备每一部分的原材料,直接购买包装好的一份菜的原料,下锅即可。那我们详细看下,这份「包装好」的原料

我们在使用 Spring Boot 的过程中,往往都是在pom.xml里加了一系列的依赖,然后启支一个包含main方法的Application,一切就OK啦。给你我的感觉,就像是自己要动手做个菜,自己不再需要准备每一部分的原材料,直接购买包装好的一份菜的原料,下锅即可。

那我们详细看下,这份「包装好」的原料中,到底做了些什么。

添加Starter依赖

这里添加的依赖,除了我们之前在Maven中熟悉的之外,还有一些都是长这个样子:

名为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> 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

具体这些starter是怎么起作用的呢,他们什么时候开始工作的?

一切都要从入口处说起。我们以上面的starter为例,看到这个mybatis的starter,其对应的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> 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.

我们看到,相当于我们添加了一个Starter的依赖,其背后会引入许多其定义的其他依赖,通过 Maven 的传递依赖,这些都会被自动添加了进来。

自动配置

相比传统的依赖,我们看到其中包含这样一个:mybatis-spring-boot-autoconfigure,这也是每个Starter的秘密所在:「AutoConfigure」

它会在实现时,考虑应用中的其他部分因素,「推断」你所需要的 Spring 配置。

在Spring Boot中,我们***的感受是配置仿佛都被做好了,直接使用即可,这就是

spring-boot-autoconfigure. 每个starter都有一个名为spring.factories

的文件,存放在META-INF目录下,其中的内容类似下面这个样子:

# Auto Configure 
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ 
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration 
  • 1.
  • 2.
  • 3.

所有需要自动配置的Class,都需要配置成key是EnableAutoConfiguration的。

我们来看类的内部

@Configuration 
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class}) 
@ConditionalOnBean({DataSource.class}) 
@EnableConfigurationProperties({MybatisProperties.class}) 
@AutoConfigureAfter({DataSourceAutoConfiguration.class}) 
public class MybatisAutoConfiguration { 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

Class 之上, 有不少注解来标识,有几点需要关注的:

  • 其中有标准的 Spring 配置注解 @Configuration
  • 几个@ConditionalXX
  • 标识执行顺序的@AutoConfigureAfter

其中,@ConditionalOnClass 标识 SqlSessionFactory类存在时,执行该配置, @ConditionalOnBean标识DataSource Bean在 Spring Context时,执行配置。

这些spring.factories是怎么被识别的呢? 这就得夸下 Spring 的FactoriesLoader了。

看下官方文档说明

  • 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).

 

启动的时候,根据ClassLoader中的jar,扫描所有 spring.factories,将其中符合条件的过滤出来,执行对应的配置。重点可以关注下

protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() { 
       return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, 
               this.beanClassLoader); 
   } 
  • 1.
  • 2.
  • 3.
  • 4.
           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); 
    } 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.

经过这里的执行之后, filter方法把符合条件的过滤出来了。

创建自定义Starter

经过上面两步,我们大概知道 Starter的工作原理。有时候,我们需要对外提供一些工具组件时,也想以 Starter 的形式提供出来,供别人使用。步骤也还算清晰,照葫芦画瓢。

  • 先创建自己的模块
  • 增加需要用到的依赖
  • 创建对应的 AutoConfiguration类
  • 创建META-INF/spring.factories 文件

此时,就不需要再将 Spring Boot 做为 Parent依赖,在单独的依赖中增加

<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> 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

AutoConfiguration类也简单,照上面的创建一个

@Configuration 
@ConditionalOnClass(HelloService.class) 
public class HelloServiceAutoConfiguration { 
  • 1.
  • 2.
  • 3.

然后,增加文件

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.example.demo.HelloServiceAutoConfiguration 
  • 1.

在需要这个服务的地方,直接引入依赖就OK啦。

【本文为51CTO专栏作者“侯树成”的原创稿件,转载请通过作者微信公众号『Tomcat那些事儿』获取授权】

戳这里,看该作者更多好文