SpringBoot 系列-自动配置及 starter 机制解析

1,982 阅读8分钟

原文链接:www.glmapper.com/2020/01/05/…

写在前面: 在沸点列了个小目标 -2020 立个 Flag,掘金等级到 6 级,原创文章数 120+ 篇幅!😎 - #掘金沸点#,明年打不打脸,就看各位的老板的了。小手抖一抖,关注、点赞走一走~

本篇主要来讨论研究两个问题:1、什么自动配置,2、如何编写自动配置

在使用 Spring 作为项目开发框架的过程中,当需要集成某个组件时,通常需要大量的 xml 配置才可以让项目工程 run 起来,下面先以 mybatis 为例,来看下如何使用 mybatis-Spring 模块,需要哪些必不可少的依赖和配置。

使用 mybatis-spring

任何组件的集成都绕不过两个问题:依赖和配置,关于配置在[]()这篇文章中介绍了配置的一些点,有兴趣的可以看下。

依赖

从 mybatis 的官方文当可以了解到,要使用 MyBatis-Spring 模块,需要在类路径下包含 mybatis-spring.jar 文件和相关依赖(如:mysql-connector-java)即可。如果使用 Maven 作为构建工具,则在 pom.xml 中加入以下代码即可:

<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis-spring</artifactId>
  <version>${latest.version}</version>
</dependency>

bean 配置

Spirng 集成 mybatis 通常需要以下 bean 配置:

1、dataSource

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
    init-method="init" destroy-method="close">

    // 省略其他配置
</bean>

2、sqlSessionFactory

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  <property name="dataSource" ref="dataSource" />
</bean>

3、其他:包扫描和事务配置

<!-- DAO 接口所在包名,Spring 会自动查找其下的类,并将其定义为一个 Spring Bean -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.glmapper.bridge.boot.dao" />
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
</bean>

<!-- (事务管理)transaction manager -->
<bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

    <property name="dataSource" ref="dataSource" />
</bean>

这些个 bean 是在 Spring 中使用 mybatis 框架时基本必不可少的配置。那么在 SpringBoot 中呢?

SpringBoot 中如何集成 mybatis 的

SpringBoot 集成 mybatis 非常简单,加一下下面的 starter ,再在 application.properties 配置下数据库连接配置即可;不需要配置 datasource,sqlSessionFactory 等这些 bean。

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.1</version>
</dependency>

官方文档:https://mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/

mybatis starter 是如何规避 bean 配置的

引用 mybatis-spring-boot-starter 既然可以不用在 xml 中配置 bean ,那肯定是这些 bean 是在 mybatis-spring-boot-starter 中通过某种方式被创建了。

在 SpringBoot 官方文档的描述中,starter 只是用来管理依赖的,一般不会有代码,自动配置的代码一般在 xxxx-autoconfigure 中。mybatis 的自动配置相关代码是在 mybatis-spring-boot-autoconfigure 中。

mybatis-spring-boot-autoconfigure 这依赖中只有简单的几个类,其中最核心的就是 MybatisAutoConfiguration 这个配置类。另外一个 MybatisProperties 是 mybatis spring boot 的属性配置类,就是常见的 mybatis.xxxx。

MybatisAutoConfiguration 自动配置类

MybatisAutoConfiguration 的定义及其生效条件:

  • 1.当前 classpath 下必须有 SqlSessionFactory 和 SqlSessionFactoryBean 这两个类
  • 2.存在 DataSource bean 实例
  • 3.有配置类 MybatisProperties 实例
  • 4.在 DataSourceAutoConfiguration 和 MybatisLanguageDriverAutoConfiguration 两个自动配置类之后刷新
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class })
public class MybatisAutoConfiguration implements InitializingBean {
    // 定义 SqlSessionFactory bean
    @Bean
    @ConditionalOnMissingBean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        // 
    }
    // check
    @Override
    public void afterPropertiesSet() {
    checkConfigFileExists();
    }
    // 省略其他code
}

从上面的代码片段大体可以知道 MybatisAutoConfiguration 所做的事情主要包括以下几点:1、刷新 SqlSessionFactory 和 SqlSessionFactoryBean 两个 bean;2、afterPropertiesSet 中做一些准备或者检验工作(这里就是 check 了 mybatis 的配置文件是否配置了)

关于 DataSource 的 bean ,则是由 DataSourceAutoConfiguration 这个配置类中来定义。

具体代码有兴趣的读者可以自己查阅相关源码,这里就不展开了。

所以整体看来, MybatisAutoConfiguration 及其所依赖的 xxxConfiguration 会帮助用户定义 bean 和解析配置。

mybatis 自动配置的 bean 是如何生效的

上面分析到 MybatisAutoConfiguration 及其依赖的配置自动类会帮助创建运行时所需要的 bean,那么这些 bean 是如何被 SpringBoot 框架感知并加载的呢?

其实一般的项目工程中,如果我们在一个类上打了 @Configuration 注解的话,Spring 会直接能够加载到的(前提是这个类所在的包在启动类的子包下)。但是在框架层面,项目的包和所引入的组件包的包路径肯定是有差异的,所以在一些情况下会刷不到依赖中的 bean。

SpringBoot 中提供了一种类似于 SPI 机制的方式来帮忙加载 EnableAutoConfiguration、ApplicationListner、ApplicationContextInitializer 等类型的 bean。比如 mybatis 自动配置的配置如下:

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

其处理逻辑在 SpringApplication 类中,具体解析方法如下:

private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
            ClassLoader classLoader, Object[] args, Set<String> names)
 
{
    List<T> instances = new ArrayList<>(names.size());
    for (String name : names) {
        try {
            Class<?> instanceClass = ClassUtils.forName(name, classLoader);
            Assert.isAssignable(type, instanceClass);
            // 反射拿到构造函数
            Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
            // 创建 bean 
            T instance = (T) BeanUtils.instantiateClass(constructor, args);
            instances.add(instance);
        }
        catch (Throwable ex) {
            throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
        }
    }
    return instances;
}

如何编写自己的 starter

本小节将结合上面的描述,自定义一个 starter,让你的项目和 xml bean 配置说再见。

场景描述:有两个 bean,一个 parentBean,一个 childBean,parentBean 需要依赖 childBean,parentBean中又要依赖 http 包

原来的 xml 配置:

<bean id="parentBean" class="com.glmapper.bridge.boot.service.impl.ParentBean">
    <property name="childBean" ref="childBean"></property>
</bean>
<bean id="childBean" class="com.glmapper.bridge.boot.service.impl.ChildBean"/>

下面考虑的是将这些 bean 作为公共组件提供给其他项目工程用,从框架角度来看,最佳实践是:

  • 提供一个 autoconfigure 模块用于编写自动配置类代码
  • 提供一个 starter,用于提供给外部用户使用

编写 autoconfigure

  • 自动配置类
@Configuration
// parentBean 依赖 HttpClient,所以如果没有 HttpClient 则不会刷新当前自动配置类
@ConditionalOnClass(HttpClient.class)
public class GlmpperAutoConfiguration {
    // ParentBean bean 定义
    @Bean
    @ConditionalOnMissingBean   // 如果当前 Spring 容器中已经存在 parentBean则不会再创建
    public ParentBean parentBean(){
        return new ParentBean();
    }

    // ChildBean bean 定义
    @Bean
    @ConditionalOnMissingBean
    public ChildBean childBean(){
        return new ChildBean();
    }
}
  • 依赖 scope 使用 provided,不直接打在 autoconfigure 依赖中
<dependencies>
    <dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpclient</artifactId>
        <version>4.5.6</version>
        <scope>provided</scope>
    </dependency>
</dependencies>
  • 编写 spring.factories,在 resources/META-INF/ 新建一个 spring.factories 文件,配置如下:
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.glmapper.bridge.boot.autoconfigure.GlmpperAutoConfiguration

编写 starter

starter 里面没有代码,只做依赖管控

<dependency>
    <groupId>com.glmapper.bridge.boot</groupId>
    <artifactId>guides-autoconfigure</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.6</version>
</dependency>

starter 里面包括了自动配置的依赖和 httpclient 的依赖,所以用户在引入 starter 之后所有生效条件都满足了,就会在启动时直接刷新。

示例工程: https://github.com/glmapper/springboot-series-guides.git(guides-autoconfigure 模块和 guides-starter 模块)

小结

本篇是介于源码解析和实践系列之间的一篇,作为源码解析的终篇和实践的开篇。

本篇以 mybatis 为例,对 spring 环境和 SpringBoot 环境下的使用方式做了简单对比;以此为切入点,介绍了 SpringBoot 中的自动配置及 starter 最佳实践。

一家之言,如有任何错误,请批评指出,不胜感激 !也欢迎大家关注我的个人微信公众号,目前已经整理了 SpringBoot/SpringSession/SpringCloud/基础算法/JAVA 等系列文章,同时也会定期的送些小礼物(书或者我自己的书法联系手稿,欢迎一起交流~