【SpringBoot的自动配置--上篇】自动配置的本质

本专栏将从基础开始,循序渐进,以实战为线索,逐步深入SpringBoot相关知识相关知识,打造完整的云原生学习步骤,提升工程化编码能力和思维能力,写出高质量代码。希望大家都能够从中有所收获,也请大家多多支持。
专栏地址:SpringBoot专栏
本文涉及的代码都已放在gitee上:gitee地址
如果文章知识点有错误的地方,请指正!大家一起学习,一起进步。


Spring Boot最大的功劳就在于自动配置,它的自动配置功能能根据类加载路径下的JAR依赖自动配置基础设施。例如,当Spring Boot检测到类加载路径下包含了MySQL依赖,且容器中没有配置其他任何DataSource时,Spring Boot就会自动启动HSQLDB数据库。

Spring Boot 的自动配置大多会根据特定依赖库自动触发,启用自动配置需要使用@EnableAutoConfiguration注解。注意,整个应用只需要添加一个该注解,因此,通常只要将该注解添加到主配置类(SpringApplication所运行的配置类)即可。

@SpringBootApplication注解是@Configuration、@EnableAutoConfiguration和@ComponentScan 这三个注解的组合体,因此,在实际项目中只要用@SpringBootApplication 注解修饰主配置类,也就自然为主配置类增加了@EnableAutoConfiguration注解,从而开启了自动配置。

1 自动配置的替换原则

Spring Boot自动配置通常有一个原则:只有当容器中不存在特定类型的Bean或特定Bean时,Spring Boot自动配置才会配置该类型的Bean或特定Bean。

例如,在项目的类加载路径下添加spring-boot-starter-jdbc依赖,Spring Boot将会尝试在容器中自动配置一个DataSource Bean;但如果显式在Spring容器中配置了一个DataSource Bean,那么Spring Boot就不再尝试去自动配置DataSource。

看上去似乎很智能,对不对?其实原理很简单,Spring Boot提供了一个@ConditionalOnMissingBean注解,该注解通常与@Bean注解同时使用,这意味着只有当某个Bean不存在时,才会创建@Bean注解配置的Bean。

比如如下配置片段:

image-20220608181505450

上面配置指定只有当容器中不存在类型为DataSource的Bean时,@Bean注解所配置的Bean才会生效。

再比如如下配置片段:

image-20220608181540754

上面配置指定只要容器中不存在ID为dataSource的Bean,@Bean注解所配置的Bean就会生效。

由于Spring Boot的自动配置具有一定的透明性(有时候无法准确地知道Spring Boot自动配置了哪些Bean),因此Spring Boot为应用程序提供了“–debug”开关。

如果通过“–debug”开关启动Spring Boot应用,则将为核心组件开启DEBUG级别的日志,并将自动配置的相关日志输出到控制台。

2 禁用特定的自动配置

在某些情况下,如果希望应用禁用特定的自动配置类,则可通过@EnableAutoConfiguration注解的如下属性来指定。

➢ exclude:该属性的值可以是一个Class数组,用于禁用一个或多个自动配置类。

➢ excludeName:与前一个属性的作用基本相同,只不过它指定一个或者多个自动配置类的完整类名的字符串形式。

而在实际项目中通常使用@SpringBootApplication注解,该注解已包含了@EnableAutoConfiguration注解,@SpringBootApplication注解的exclude和excludeName属性就是@EnableAutoConfiguration注解的这两个属性的别名。

例如,如下主类上的@SpringBootApplication注解可禁用DataSourceAutoConfiguration自动配置类。

image-20220608182620007

上面代码使用 exclude 属性指定了要禁用的自动配置类。

此外,Spring Boot也允许在application.properties文件中通过spring.autoconfigure.exclude属性来指定要禁用的自动配置类。例如如下配置:

image-20220608182739344

上面的配置片段表示禁用了DataSourceAutoConfiguration、RedisAutoConfiguration两个自动配置类。

3 创建自己的自动配置

Spring Boot的核心功能就是自动配置,只有真正掌握Spring Boot自动配置的原理,才算熟练掌握了 Spring Boot,如果仅仅会基于自动配置来开发应用,那么其实只能算“依葫芦画瓢”搞了几个“Hello World”例子。

在进行实际项目开发时,仅依靠Spring Boot的自动配置是远远不够的。举例来说,比如Spring Boot应用要访问多个数据源,自动配置就完全无能为力了。

自动配置确实非常方便,但它只能提供最通用的基础组件,而实际应用往往需要进行不同程度的扩展,完全依赖自动配置是绝对不够的。因此,开发者不仅需要熟练地使用自动配置,而且更需要掌握自动配置底层的原理,这样才能从容面对实际项目的各种扩展需求:当自动配置实现不了时,替换自动配置,改为项目自己的定制配置。

3.1 自动配置的本质

自动配置其实很简单,其本质就是在容器中预配置要整合的框架所需的基础Bean。

以整合常见的MyBatis为例,直接用Spring整合MyBatis无非就是完成如下事情:

➢ 配置SqlSessionFactory Bean,当然,该Bean需要注入一个DataSource。

➢ 配置SqlSessionTemplate Bean,将上面配置的SqlSessionFactory注入该Bean即可。

➢ 注册Mapper组件的自动扫描,就是相当于添加﹤mybatis:scan…/﹥元素。

所谓的自动配置,无非就是由框架提供一个@Configuration修饰的配置类(相当于传统的 XML 配置文件),在该配置类中用@Bean 预先配置默认的 SqlSessionFactory`SqlSessionTemplate,并注册Mapper组件的自动扫描即可。

打开MyBatis为整合Spring Boot提供的自动配置类:MybatisAutoConfiguration的源文件,可以看到如下源代码:

package org.mybatis.spring.boot.autoconfigure;

import java.beans.FeatureDescriptor;
...

@Configuration
    //当SqlSessionFactory、SqlSessionFactoryBean类存在时配置生效
@ConditionalOnClass({
    
    SqlSessionFactory.class, SqlSessionFactoryBean.class})
    //当单例的DataSource Bean存在时配置生效
@ConditionalOnSingleCandidate(DataSource.class)
    //启动MybatisProperties属性处理类
@EnableConfigurationProperties({
    
    MybatisProperties.class})
@AutoConfigureAfter({
    
    DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class})
    //InitializingBean接口中的afterPropertiesSet()生命周期方法将会在该Bean初始化完成后被自动调用
public class MybatisAutoConfiguration implements InitializingBean {
    
    
    private static final Logger logger = LoggerFactory.getLogger(MybatisAutoConfiguration.class);
    //MybatisProperties类负责加载配置属性
    private final MybatisProperties properties;
    //下面的成员变量用于保存MyBatis的拦截器、类型处理器等
    private final Interceptor[] interceptors;
    private final TypeHandler[] typeHandlers;
    private final LanguageDriver[] languageDrivers;
    private final ResourceLoader resourceLoader;
    private final DatabaseIdProvider databaseIdProvider;
    private final List<ConfigurationCustomizer> configurationCustomizers;

    public MybatisAutoConfiguration(MybatisProperties properties, ObjectProvider<Interceptor[]> interceptorsProvider, ObjectProvider<TypeHandler[]> typeHandlersProvider, ObjectProvider<LanguageDriver[]> languageDriversProvider, ResourceLoader resourceLoader, ObjectProvider<DatabaseIdProvider> databaseIdProvider, ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider) {
    
    
        this.properties = properties;
        this.interceptors = (Interceptor[])interceptorsProvider.getIfAvailable();
        this.typeHandlers = (TypeHandler[])typeHandlersProvider.getIfAvailable();
        this.languageDrivers = (LanguageDriver[])languageDriversProvider.getIfAvailable();
        this.resourceLoader = resourceLoader;
        this.databaseIdProvider = (DatabaseIdProvider)databaseIdProvider.getIfAvailable();
        this.configurationCustomizers = (List)configurationCustomizersProvider.getIfAvailable();
    }

    //重写InitializingBean接口中的afterPropertiesSet()方法
    public void afterPropertiesSet() {
    
    
        this.checkConfigFileExists();
    }

    // 检查配置文件是否存在
    private void checkConfigFileExists() {
    
    
        if (this.properties.isCheckConfigLocation() && StringUtils.hasText(this.properties.getConfigLocation())) {
    
    
            //根据MybatisProperties读取configLocation加载MyBatis配置文件
            Resource resource = this.resourceLoader.getResource(this.properties.getConfigLocation());
            //如果resource.exists()为false,则文件加载失败,抛出异常
            Assert.state(resource.exists(), "Cannot find config location: " + resource + " (please add config file or check your Mybatis configuration)");
        }

    }

    //配置SqlSessionFactory Bean
    @Bean
    //当SqlSessionFactory Bean不存在时配置生效
    @ConditionalOnMissingBean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
    
    
        //创建SqlSessionFactoryBean工厂Bean
        SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
        //注入DataSource Bean
        factory.setDataSource(dataSource);
        factory.setVfs(SpringBootVFS.class);
        //如果存在MyBatis配置文件,则应用该配置文件
        if (StringUtils.hasText(this.properties.getConfigLocation())) {
    
    
            factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
        }

        this.applyConfiguration(factory);
        //应用MyBatisProperties读取到的配置属性
        if (this.properties.getConfigurationProperties() != null) {
    
    
            factory.setConfigurationProperties(this.properties.getConfigurationProperties());
        }
	//应用所有的拦截器
        if (!ObjectUtils.isEmpty(this.interceptors)) {
    
    
            factory.setPlugins(this.interceptors);
        }
	//应用所有的DatabaseIdProvider
        if (this.databaseIdProvider != null) {
    
    
            factory.setDatabaseIdProvider(this.databaseIdProvider);
        }
	//根据包名应用TypeAlias
        if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
    
    
            factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
        }
	//根据父类型应用TypeAlias
        if (this.properties.getTypeAliasesSuperType() != null) {
    
    
            factory.setTypeAliasesSuperType(this.properties.getTypeAliasesSuperType());
        }
	//根据包名应用TypeHandler
        if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
    
    
            factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
        }

        //应用所有TypeHandler
        if (!ObjectUtils.isEmpty(this.typeHandlers)) {
    
    
            factory.setTypeHandlers(this.typeHandlers);
        }

        //设置Mapper的加载位置
        if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
    
    
            factory.setMapperLocations(this.properties.resolveMapperLocations());
        }

        Set<String> factoryPropertyNames = (Set)Stream.of((new BeanWrapperImpl(SqlSessionFactoryBean.class)).getPropertyDescriptors()).map(FeatureDescriptor::getName).collect(Collectors.toSet());
        Class<? extends LanguageDriver> defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver();
        if (factoryPropertyNames.contains("scriptingLanguageDrivers") && !ObjectUtils.isEmpty(this.languageDrivers)) {
    
    
            factory.setScriptingLanguageDrivers(this.languageDrivers);
            if (defaultLanguageDriver == null && this.languageDrivers.length == 1) {
    
    
                defaultLanguageDriver = this.languageDrivers[0].getClass();
            }
        }

        if (factoryPropertyNames.contains("defaultScriptingLanguageDriver")) {
    
    
            factory.setDefaultScriptingLanguageDriver(defaultLanguageDriver);
        }
	//返回SqlSessionFactory Bean
        return factory.getObject();
    }

    private void applyConfiguration(SqlSessionFactoryBean factory) {
    
    
        org.apache.ibatis.session.Configuration configuration = this.properties.getConfiguration();
        if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) {
    
    
            configuration = new org.apache.ibatis.session.Configuration();
        }

        if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {
    
    
            Iterator var3 = this.configurationCustomizers.iterator();

            while(var3.hasNext()) {
    
    
                ConfigurationCustomizer customizer = (ConfigurationCustomizer)var3.next();
                customizer.customize(configuration);
            }
        }

        factory.setConfiguration(configuration);
    }

    //配置SqlSessionTemplate Bean
    @Bean
    //但SqlSessionTemplate Bean不存在时配置生效
    @ConditionalOnMissingBean
    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
    
    
        ExecutorType executorType = this.properties.getExecutorType();
        //如果executorType属性存在,则使用该属性创建SqlSessionTemplate
        return executorType != null ? new SqlSessionTemplate(sqlSessionFactory, executorType) : new SqlSessionTemplate(sqlSessionFactory);
    }

    @Configuration
    //导入AutoConfiguredMapperScannerRegistrar注册类
    @Import({
    
    MybatisAutoConfiguration.AutoConfiguredMapperScannerRegistrar.class})
    //当MapperFactoryBean、MapperScannerConfigurer Bean不存在时配置生效
    @ConditionalOnMissingBean({
    
    MapperFactoryBean.class, MapperScannerConfigurer.class})
    public static class MapperScannerRegistrarNotFoundConfiguration 
        //实现InitializingBean接口,该接口中的afterPropertiesSet()生命周期方法将会在该Bean初始化完成后被自动调用
        implements InitializingBean {
    
    
        public MapperScannerRegistrarNotFoundConfiguration() {
    
    
        }
	//重写InitializingBean接口中的afterPropertiesSet()方法
        public void afterPropertiesSet() {
    
    
            MybatisAutoConfiguration.logger.debug("Not found configuration for registering mapper bean using @MapperScan, MapperFactoryBean and MapperScannerConfigurer.");
        }
    }

    //定义自动扫描Mapper组件的注册器类
    public static class AutoConfiguredMapperScannerRegistrar 
        //实现BeanFactoryAware接口可访问Spring容器
        //实现ImportBeanDeficitionReggistrar接口可配置额外的Bean
        implements BeanFactoryAware, ImportBeanDefinitionRegistrar {
    
    
        //用于保存获取到的Spring容器
        private BeanFactory beanFactory;

        public AutoConfiguredMapperScannerRegistrar() {
    
    
        }

        //重写ImportBeanDefinitionRegistrar接口中的方法
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    
    
            if (!AutoConfigurationPackages.has(this.beanFactory)) {
    
    
                MybatisAutoConfiguration.logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.");
            } else {
    
    
                MybatisAutoConfiguration.logger.debug("Searching for mappers annotated with @Mapper");
                //获取自动配置要处理的包
                List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
                if (MybatisAutoConfiguration.logger.isDebugEnabled()) {
    
    
                    packages.forEach((pkg) -> {
    
    
                        MybatisAutoConfiguration.logger.debug("Using auto-configuration base package '{}'", pkg);
                    });
                }

                //创建BeanDefinitionBuilder对象
                //它帮助开发者以反射方式创建任意类的实例
                //此处就是帮助创建MapperScannerConfigurer实例
                BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
                //为要创建的对象设置属性
                builder.addPropertyValue("processPropertyPlaceHolders", true);
                builder.addPropertyValue("annotationClass", Mapper.class);
                builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages));
                BeanWrapper beanWrapper = new BeanWrapperImpl(MapperScannerConfigurer.class);
                Stream.of(beanWrapper.getPropertyDescriptors()).filter((x) -> {
    
    
                    return x.getName().equals("lazyInitialization");
                }).findAny().ifPresent((x) -> {
    
    
                    builder.addPropertyValue("lazyInitialization", "${mybatis.lazy-initialization:false}");
                });
                //在容器中注册BeanDefinitionBuilder创建的对象
                registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());
            }
        }

        //重写该方法用于获取Spring容器
        public void setBeanFactory(BeanFactory beanFactory) {
    
    
            this.beanFactory = beanFactory;
        }
    }
}

上面的MybatisAutoConfiguration是一个典型的自动配置类,该类使用了如下注解修饰。

➢@Configuration:被修饰的类变成配置类。

➢@ConditionalOnClass:条件注解之一,接下来会详细介绍。

➢@ConditionalOnSingleCandidate:条件注解之一,接下来会详细介绍。

➢@EnableConfigurationProperties:用于启动属性处理类。

➢@AutoConfigureAfter:指定该自动配置类必须在Xxx自动配置类生效之后。此处指定该自动配置类必须在DataSourceAutoConfiguration、MybatisLanguageDriverAutoConfiguration两个类生效之后,其中DataSourceAutoConfiguration是Spring Boot本身提供的自动配置类,它负责在容器中配置一个DataSource Bean;而MybatisLanguageDriverAutoConfiguration则负责在容器中配置脚本语言驱动的Bean。

该自动配置类实现了InitializingBean生命周期接口,该接口中的afterPropertiesSet()生命周期方法将会在该 Bean 初始化完成后被自动调用,这也是Spring 框架的基本内容之一。该类实现了afterPropertiesSet()方法,该方法调用checkConfigFileExists()方法。

上面对MyBatis提供的自动配置类的源代码进行了非常详细的讲解。事实上,Spring Boot框架的所有自动配置类基本都是这个套路,这些配置类都会大量使用@Configuration、@Bean、@Import及@Conditional条件注解等注解,也会大量使用Spring框架及被整合框架的核心API。如果刚开始阅读这些源代码时存在一定的不适应,那是正常的;但如果静下心来认真看这些源代码还看不明白,那不是Spring Boot知识有缺陷,而是对Spring框架的API还不熟,建议先认真学习Spring框架本身。

当开发完自动配置类之后,还需要使用META-INF/spring.factories文件来定义自动配置类,应该在该文件中以“org.springframework.boot.autoconfigure.EnableAutoConfiguration”为key列出所有自动配置类。

还是以MyBatis整合Spring Boot的自动配置为例,打开其JAR包中的META-INF/spring.factories文件,可以看到如下代码:

image-20220608191911579

上面列出了MybatisLanguageDriverAutoConfiguration类和MybatisAutoConfiguration类,它们就是MyBatis为整合Spring Boot所提供的自动配置类。前面详细讲解了MybatisAutoConfiguration类,而MybatisLanguageDriverAutoConfiguration自动配置类则用于在容器中配置各种脚本语言驱动的Bean,该自动配置类的源代码更简单,就是通过大量@Configuration和@Bean注解在容器中配置Bean。

自动配置类只能通过META-INF/spring.factories进行加载,并确保它们处于一个特殊的包空间内,尤其是不能让它们变成普通@ComponentScan 的目标。此外,自动配置类也不应该用@ComponentScan来扫描其他组件,如果确实需要加载其他配置文件,则应使用@Import显式指定要加载的配置类。

如果要为自动配置类指定它们的加载顺序,Spring Boot则提供了如下两个注解。

➢@AutoConfigureAfter:指定被修饰的类必须在一个或多个自动配置类加载之后加载。

➢@AutoConfigureBefore:指定被修饰的类必须在一个或多个自动配置类加载之前加载。

如果自动配置包中包含多个自动配置类,且要求以特定的顺序来加载这些自动配置类,则可用@AutoConfigureOrder注解来修饰它们。@AutoConfigureOrder注解完全类似于Spring框架原有的@Order注解,只不过它专门用于修饰自动配置类。

由此可见,创建自动配置的关键就是开发自动配置类,而开发自动配置类除了要熟练掌握Spring及被整合框架的API,还要熟练使用Spring Boot提供的条件注解。下面就来详细介绍Spring Boot的条件注解。

猜你喜欢

转载自blog.csdn.net/Learning_xzj/article/details/125215137