结合tk.mybatis插件源码,说说spring boot如何实现自动注入

一、前言

自动配置是spring boot 一个重要特性,所谓“自动”就是我们直接引用功能所需jar包,除了极个别的核心配置外,几乎不用额外的配置,从而减少繁琐配置项,也就是常说的约定大于配置思想的体现。下面结合mybatis插件--tk.mybatis说说spring boot如何实现的自动配置的。

tk.mybatis可以看做是mybatis框架的一个插件,项目提供了常规的增删改查操作以及Example 相关的单表操作。通用 Mapper 是为了解决 MyBatis 使用中 90% 的基本操作,使用它可以很方便的进行开发,可以节省开发人员大量的时间。项目地址

二、实现原理

1、spring boot入口

既然是自动配置,肯定是在spring boot启动中实现的注入,我们来从spring的入口找寻答案:

@SpringBootApplication //spring boot启动的核心注解
public class NewMediaMpApiApplication 

    public static void main(String[] args) {
        SpringApplication.run(NewMediaMpApiApplication.class, args);
    }

}

这是spring boot 最简单的启动类,点开@SpringBootApplication这个注解看下。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration//开启自动配置类
@ComponentScan(excludeFilters = {
        @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
  //omit code ...
}

2、自动配置入口

这个注解是有多个注解复合而成,其中@EnableAutoConfiguration由名字可以看它应该就是实现自动配置的入口,顺便说一下,@Enable**开头的注解在spring boot中往往都是开启xx功能的入口,我们在看源码的时候可以从这里入手。跟进去看看:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
   //omit code ...
}

这个注解的描述官方给出的描述是:

Enable auto-configuration of the Spring Application Context, attempting to guess and configure beans that you are likely to need

即,为spring容器上下文开启自动配置,尝试猜测你需要加载哪些配置bean。

3、收集需要配的类

顺着@Import可以找到,整个自动配置的实现都是通过AutoConfigurationImportSelector这个选择器实现把jar中需要的组件导入到spring IOC容器中这一功能。(@Import是通过导入的方式将类加载到spring容器中的),AutoConfigurationImportSelector实现了ImportSelector接口,官方文档是这么描述这个接口的:

Interface to be implemented by types that determine which @{@link Configuration}, class(es) should be imported based on a given selection criteria, usually one or more annotation attributes.

文档说的很明白,实现这个接口的类按照给定的选择条件(通常是注解中的一个或多个属性),筛选出需要注入的配置类。该接口定义了一个方法:返回满足条件配置类的数组。我们看下AutoConfigurationImportSelector是如何实现这个方法的:

    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        }
        AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
                .loadMetadata(this.beanClassLoader);
    //核心方法,获取满足条件的配置信息
        AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(
                autoConfigurationMetadata, annotationMetadata);
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }

核心方法就是获取配置类信息,我们往下继续跟进到底是怎么获取的配置:

    protected AutoConfigurationEntry getAutoConfigurationEntry(
            AutoConfigurationMetadata autoConfigurationMetadata,
            AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        }
        AnnotationAttributes attributes = getAttributes(annotationMetadata);
    //获取所有候选的配置信息
        List configurations = getCandidateConfigurations(annotationMetadata,
                attributes);
    //过滤掉重复的
        configurations = removeDuplicates(configurations);
    //排除掉明确需要排除的配置
        Set exclusions = getExclusions(annotationMetadata, attributes);
        checkExcludedClasses(configurations, exclusions);
        configurations.removeAll(exclusions);
        configurations = filter(configurations, autoConfigurationMetadata);
        fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationEntry(configurations, exclusions);
    }

这个方法主要的功能是获取了候选的配置类,然后再按规则过滤掉,这里只关注如何获取候选配置类的,往下走:

    /**
     * Return the auto-configuration class names that should be considered
     * 返回认为需要自动配置的类
     */
    protected List getCandidateConfigurations(AnnotationMetadata metadata,
            AnnotationAttributes attributes) {
    //主要方法,加载配置
        List configurations = SpringFactoriesLoader.loadFactoryNames(
                getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
        Assert.notEmpty(configurations,
                "No auto configuration classes found in META-INF/spring.factories. If you "
                        + "are using a custom packaging, make sure that file is correct.");
        return configurations;
    }

4、配置类是从哪里加载的呢?

我们看到加载配置类的方法了,胜利就在前方,进SpringFactoriesLoader.loadFactoryNames()方法:

public static List loadFactoryNames(Class factoryClass, @Nullable ClassLoader classLoader) {
        String factoryClassName = factoryClass.getName();
        return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
    }

//loadFactoryNames主要调用的方法
    private static Map> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap result = cache.get(classLoader);
        if (result != null) {
            return result;
        }

        try {
      //FACTORIES_RESOURCE_LOCATION的值 "META-INF/spring.factories"
            Enumeration urls = (classLoader != null ?
                    classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :                     
                    ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
            result = new LinkedMultiValueMap<>();
      //根据spring.factories配置中的value值获取候选的配置类
            while (urls.hasMoreElements()) {
        //循环遍历配置项的值
                URL url = urls.nextElement();
                UrlResource resource = new UrlResource(url);
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                for (Map.Entry entry : properties.entrySet()) {
                    String factoryClassName = ((String) entry.getKey()).trim();
                    for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
            //将配置类的文件路径加入到结果集中
                        result.add(factoryClassName, factoryName.trim());
                    }
                }
            }
            cache.put(classLoader, result);
            return result;
        }
        catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load factories from location [" +
                    FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
    }

至此,我们找到自动配置加载的起始位置: META-INF/spring.factories。spring通过SpringFactoriesLoader.loadFactoryNames()扫描Java jar包下META-INF/spring.factories文件,获取所有需要加载的配置类,从而初始化到spring容器中。

下面我们去看下使用自动配置的jar包中看是否是这样实现的,用前文说到我们以tk.mybaits为例:

1.png

打开spring.factories文件看看配置文件里的内容:

image.png

这个配置文件告诉spring,MapperAutoConfiguration就是tk.mybaits的配置类,根据此配置类将运行所需的内容注入到spring的IOC容器中。

三、tk加载到spring容器中

1、MapperAutoConfiguration配置文件详解

我们来详细学习下这个配置类,简化细节:

//声明这是一个配置类
@org.springframework.context.annotation.Configuration
//这两个配置类必须存在才能初始换当前配置类
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
//spring容器中必须存在DatcSource才会初始化当前配置类
@ConditionalOnBean(DataSource.class)
//导入MybatisProperties.class文件中的关于mybatis的配置
@EnableConfigurationProperties({MybatisProperties.class})
//当前配置类必须在DataSourceAutoConfiguration.class加载后才能初始化
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
//当前配置类必须在MybatisAutoConfiguration加载之前才能初始化
@AutoConfigureBefore(name = "org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration")
public class MapperAutoConfiguration {
  
  //omit code...
  
        //从spring中为MapperAutoConfiguration赋值
      public MapperAutoConfiguration(MybatisProperties properties,
                                   ObjectProvider interceptorsProvider,
                                   ResourceLoader resourceLoader,
                                   ObjectProvider databaseIdProvider,
                                   ObjectProvider> configurationCustomizersProvider) {
        this.properties = properties;
        this.interceptors = interceptorsProvider.getIfAvailable();
        this.resourceLoader = resourceLoader;
        this.databaseIdProvider = databaseIdProvider.getIfAvailable();
        this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable();
    }
 
    //如果spring容器中没有sqlSessionFactory,注入这个bean
    @Bean
    @ConditionalOnMissingBean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {

      //omit code ...
    }
  
  
    //如果spring容器中没有sqlSessionTemplate,注入这个bean
    @Bean  
    @ConditionalOnMissingBean
    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
      //omit code ...
    }
  
  //这个是整个tk.mybatis的核心配置文件,即扫描
  public static class AutoConfiguredMapperScannerRegistrar
            implements BeanFactoryAware, ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {
    
    //扫描所有被@Mapper注解修饰的接口,也是在这一个过程中,tk生成了内置的常用方法的sql,用于调用时在mybatis执行
    @Override
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

            logger.debug("Searching for mappers annotated with @Mapper");
          
            //omit code...
        }
    //omit code ...
  }

}

本配置类的注解描述部分,主要声明了所依赖的bean,需要以及初始化所需要的属性,是初始化到spring容器的先决条件。tk作为mybatis的插件,在加载数据源先关组件后,加载mybatis前插入tk加载过程,使用上述注解组合达到这个目的。前置条件满足后,最终目的还是AutoConfiguredMapperScannerRegistrar配置的加载,即tk生成了内置的常用方法的sql,用于调用时在mybatis执行。

2、@Configuration配合使用的常见注解

另外,还有写配合@Configuration使用的常见注解:

  1. 属性类注解

    • EnableConfigurationProperties

    • @ConfigurationProperties(prefix = "xxx")

      在需要注入配置的类上加上这个注解,prefix的意思是,以该前缀打头的配置,@EnableConfigurationProperties({MybatisProperties.class}) 开启tk.mybatis的配置项

  2. 条件系列注解,@Conditon*

    • @ConditionalOnBean 仅仅在当前上下文中存在某个对象时,才会实例化一个Bean

      @ConditionalOnBean(DataSource.class) 表示容器中已经注入了数据源的对象

    • @ConditionalOnClass 某个class位于类路径上,才会实例化一个Bean),该注解的参数对应的类必须存在,否则不解析该注解修饰的配置类

      @ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class}) 表示必须要存在这两个和数据路连接相关的类

    • @ConditionalOnExpression 当表达式为true的时候,才会实例化一个Bean

    • @ConditionalOnMissingBean 仅仅在当前上下文中不存在某个对象时,才会实例化一个Bean

    • @ConditionalOnMissingClass 某个class类路径上不存在的时候,才会实例化一个Bean

    • @ConditionalOnNotWebApplication 不是web应用时,才会执行

  3. 顺序系列注解

    • @AutoConfigureAfter 在加载配置的类之后再加载当前类

      @AutoConfigureAfter(DataSourceAutoConfiguration.class) 表示在加载数据源的配置之后加载tk.mybatis的配置类

    • @AutoConfigureBefore 在加载配置的类之后再加载当前类

      @AutoConfigureBefore(name = "org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration") 表示要在mybatis配置类加载前完成tk.mybatis的注入

至此,spring将tk运行所需的全部组件在启动时注入到spring的IOC容器中。

3、spring boot内置的自动配置项

从而实现了spring boot的自动配置,了解整个过程后,我们就可以知道spring boot中那些导入包简单配置下就能用的原理了,比如Spring data jpa 、Spring data redis等配置上连接地址就可以用了,其实秘密就在于此,我们来看下spring boot 中的内置的自动配置:

spring boot 内置.png

是不是看着很眼熟,这就是spring boot常说的“简化配置”的关键所在。

四、自动配置的思维导图

我将自动配置的过程整理成思维导图帮助理解和理解,如下:

spring boot 自动配置.png

欢迎拍砖,欢迎交流~

注:转载请注明出处

你可能感兴趣的:(结合tk.mybatis插件源码,说说spring boot如何实现自动注入)