美文网首页SpringBoot极简教程 · Spring Boot Spring源码分析
结合tk.mybatis插件源码,说说spring boot如何

结合tk.mybatis插件源码,说说spring boot如何

作者: yesAnd_ | 来源:发表于2020-02-24 22:54 被阅读0次

    一、前言

    自动配置是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<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 new AutoConfigurationEntry(configurations, exclusions);
        }
    

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

        /**
         * Return the auto-configuration class names that should be considered
         * 返回认为需要自动配置的类
         */
        protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
                AnnotationAttributes attributes) {
        //主要方法,加载配置
            List<String> 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<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
            String factoryClassName = factoryClass.getName();
            return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
        }
    
    //loadFactoryNames主要调用的方法
        private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
            MultiValueMap<String, String> result = cache.get(classLoader);
            if (result != null) {
                return result;
            }
    
            try {
          //FACTORIES_RESOURCE_LOCATION的值 "META-INF/spring.factories"
                Enumeration<URL> 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为例:

    <img src="https://img.haomeiwen.com/i14480907/b912eb91a942dab1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="1.png" style="zoom: 67%;" />

    打开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<Interceptor[]> interceptorsProvider,
                                       ResourceLoader resourceLoader,
                                       ObjectProvider<DatabaseIdProvider> databaseIdProvider,
                                       ObjectProvider<List<ConfigurationCustomizer>> 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如何

        本文链接:https://www.haomeiwen.com/subject/kilkqhtx.html