乐趣区

自定义Spring-Boot-Starter

在实际开发中,对于一些通用业务和公共组件,我们可能想将其做成一个 Spring Boot Starter 便于所有系统使用,这就需要我们定义自己的 Spring Boot Starter。
本文不会编写一个真正的 Spring Boot Starter,而是选择借用 mybatis-spring-boot-starter 来描述 starter 的制作方式。之所以选择 mybatis 的 spring-boot-starter 进行讲解,一是考虑到 mybatis 比较重要,大家都很熟悉。二是 mybatis 的 spring-boot-starter 不是 spring 官方提供的,是由 mybatis 自己实现的。
先来思考一下,一个 Spring Boot Starter 都需要具备哪些能力:

  1. 提供了统一的 dependency 版本管理。仅需要导入对应的 Starter 依赖,相关的 library,甚至是中间件,都一次性被引入了,而且要保证各 dependency 之间是不冲突的。例如当我们引入 mybatis-spring-boot-starter 依赖,mybatis 和 mybatis-spring 等相关依赖也顺带被导入了。
  2. 提供自动装配的能力。Starter 可以自动的向 Spring 容器中注入需要的 Bean,并且完成对应的配置。
  3. 对外暴露恰当的 properties。Starter 不可能提前知道全部的配置信息,有些配置信息只有在应用集成这个 Starter 的时候才能明确。例如对于 mybatis,configLocation、mapperLocation 这些参数在每个项目中都可能不同,所以只有应用自己知道这些参数的值该是什么。mybatis-spring-boot-starter 对外暴露了一组 properties,例如如果我们想指定 mapper 文件的存放位置,只需要在 application.properties 中添加 mybatis.mapperLocations=classpath:mapping/*.xml 即可。

下面我们带着这几个问题,来看 mybatis-spring-boot-starter 是如何实现的。

统一的 dependency 管理

这一点比较好理解,就是利用 maven 的间接依赖特性,在 Starter 的 maven pom.xml 中声明所有需要的 dependency,这样在项目工程导入这个 Starter 时,相关的依赖就都被一起导入了。下面是 mybatis-spring-boot-starter 的 pom.xml,可以看到与集成 mybatis 相关的 dependency 都已经声明了。

<project ...>
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot</artifactId>
        <version>2.0.1</version>
    </parent>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <name>mybatis-spring-boot-starter</name>
    <properties>
        <module.name>org.mybatis.spring.boot.starter</module.name>
    </properties>
    <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>
</project>

对外暴露 properties

mybatis-spring-boot-autoconfigure 模块中的 MybatisProperties 类支持向外暴露相关的 properties,它是通过 @ConfigurationProperties 实现的,并指定所有 properties 都以”mybatis“为前缀。关于 @ConfigurationProperties 的具体用法可参考《Spring Boot 外部化配置》
下面是 MybatisProperties 类的部分源码:

@ConfigurationProperties(prefix = MybatisProperties.MYBATIS_PREFIX)
public class MybatisProperties {

  public static final String MYBATIS_PREFIX = "mybatis";

  private static final ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();

  /**
   * Location of MyBatis xml config file.
   */
  private String configLocation;

  /**
   * Locations of MyBatis mapper files.
   */
  private String[] mapperLocations;

  /**
   * Packages to search type aliases. (Package delimiters are ",; \t\n")
   */
  private String typeAliasesPackage;

  /**
   * The super class for filtering type alias. If this not specifies, the MyBatis deal as type alias all classes that
   * searched from typeAliasesPackage.
   */
  private Class<?> typeAliasesSuperType;

可以看到,我们使用 mybatis 的所有属性都可以通过 properties 的形式在 application.properties 中配置。

实现自动装配

自动装配的原理已经在《Spring Boot 自动装配详解》一问中介绍过。mybatis-spring-boot-autoconfigure 模块提供了两个自动装配类—— MybatisAutoConfiguration 和 MybatisLanguageDriverAutoConfiguration,其中主要的配置功能是在 MybatisAutoConfiguration 中实现的,下面是 MybatisAutoConfiguration 类的部分源码:

@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class})
public class MybatisAutoConfiguration implements InitializingBean {
  
...

  @Bean
  @ConditionalOnMissingBean
  public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
    factory.setDataSource(dataSource);
    factory.setVfs(SpringBootVFS.class);
    if (StringUtils.hasText(this.properties.getConfigLocation())) {factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
    }
    applyConfiguration(factory);
    if (this.properties.getConfigurationProperties() != null) {factory.setConfigurationProperties(this.properties.getConfigurationProperties());
    }
    if (!ObjectUtils.isEmpty(this.interceptors)) {factory.setPlugins(this.interceptors);
    }
    
    ...
    
  }

  ...

可以看到,MybatisAutoConfiguration 也是利用条件注解的方式构建各个需要的 Bean,例如上面代码片段中创建的 sqlSessionFactory Bean。
其中 @AutoConfigureAfter 注解在之前没有说明过,它是一个 hint 注解,在这里的作用是只有当 DataSourceAutoConfiguration 和 MybatisLanguageDriverAutoConfiguration 这两个自动装配类执行完成后,再执行 MybatisAutoConfiguration 的自动装配功能。

spring.factories

上面只是完成了 AutoConfiguration 类的编写,那么如何让其在 Spring Boot 应用启动时执行呢?
还记得 EnableAutoConfiguration 这个注解吗?它是利用 SpringFactoriesLoader 机制加载所有的 AutoConfiguration 类的。所以我们还需要将写好的 AutoConfiguration 类放置到 META-INF/spring.factories 中。
在 mybatis-spring-boot-autoconfigure 模块的 META-INF/spring.factories 文件中有下面配置代码:

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

这样,在 Spring Boot 应用启动时,就可以加载并执行 MybatisLanguageDriverAutoConfiguration 和 MybatisAutoConfiguration 这两个自动装配类了。

总结

创建自定义的 Spring Boot Starter 并不是什么难事。通过对 mybatis-spring-boot-starter 的实现方式进行分析,可以总结出下面创建自定义 starter 的步骤:

  1. 确保在 pom.xml 文件中声明了使用该组件所需要的全部 dependency
  2. 利用 @ConfigurationProperties 注解对外暴露恰当的 properties
  3. 利用条件注解 @ConditionalXXX 编写 XXXAutoConfiguration 类
  4. 把写好的 XXXAutoConfiguration 类加到 META-INF/spring.factories 文件的 EnableAutoConfiguration 配置中,这样在应用启动时就会自动加载并执行 XXXAutoConfiguration。
退出移动版