在实际开发中,对于一些通用业务和公共组件,我们可能想将其做成一个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都需要具备哪些能力:
- 提供了统一的dependency版本管理。仅需要导入对应的Starter依赖,相关的library,甚至是中间件,都一次性被引入了,而且要保证各dependency之间是不冲突的。例如当我们引入mybatis-spring-boot-starter依赖,mybatis和mybatis-spring等相关依赖也顺带被导入了。
- 提供自动装配的能力。Starter可以自动的向Spring容器中注入需要的Bean,并且完成对应的配置。
- 对外暴露恰当的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的步骤:
- 确保在pom.xml文件中声明了使用该组件所需要的全部dependency
- 利用@ConfigurationProperties注解对外暴露恰当的properties
- 利用条件注解@ConditionalXXX编写XXXAutoConfiguration类
- 把写好的XXXAutoConfiguration类加到META-INF/spring.factories文件的EnableAutoConfiguration配置中,这样在应用启动时就会自动加载并执行XXXAutoConfiguration。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。