本文来自网易云社区

SpringBoot之所以能够快速构建项目,得益于它的2个新特性,一个是起步依赖前面已经介绍过,另外一个则是自动配置。起步依赖用于降低项目依赖的复杂度,自动配置负责减少人工配置的工作量。

@EnableAutoConfiguration

前一篇留了一个注解没介绍,@EnableAutoConfiguration注解是开启自动配置的入口。其定义如下:

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

    ...
}

看到@Import注解,参数是AutoConfigurationImportSelector类。

先来补充下@Import的知识,最早是为了方便引入java配置类,后来进行了扩展,目前有一下功能:

  • 参数为@Configuration注解的bean,那么就引入这个配置类。相当于用xml配置时的

  • 参数为ImportBeanDefinitionRegistrar接口或者ImportSelector接口的实现类,那么就通过接口方法来实现自定义注入

  • 参数为普通类,直接将该类创建到Spring的IoC容器

这里的AutoConfigurationImportSelector类属于第二种情况,实现了ImportSelector接口。ImportSelector接口只声明了一个方法,要求返回一个包含类全限定名的String数组,这些类将会被Spring添加到IoC容器。

看下AutoConfigurationImportSelector的方法实现:

@Overridepublic String[] selectImports(AnnotationMetadata annotationMetadata) {    if (!isEnabled(annotationMetadata)) {        return NO_IMPORTS;
    }    try {
        AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
            .loadMetadata(this.beanClassLoader);
        AnnotationAttributes attributes = getAttributes(annotationMetadata);        // 获取候选配置的类路径
        List<String> configurations = getCandidateConfigurations(annotationMetadata,
                                                                 attributes);
        configurations = removeDuplicates(configurations);
        configurations = sort(configurations, autoConfigurationMetadata);
        Set<String> exclusions = getExclusions(annotationMetadata, attributes);
        checkExcludedClasses(configurations, exclusions);
        configurations.removeAll(exclusions);
        configurations = filter(configurations, autoConfigurationMetadata);
        fireAutoConfigurationImportEvents(configurations, exclusions);        return StringUtils.toStringArray(configurations);
    }    catch (IOException ex) {        throw new IllegalStateException(ex);
    }
}

通过某种方法获取配置类的路径,然后去重排序一系列处理之后返回出去交给Spring去处理。

接下来看下某种方法是个什么操作:

    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
            AnnotationAttributes attributes) {        // 从SpringFactoriesLoader取候选配置的类路径
        // 第一个参数getSpringFactoriesLoaderFactoryClass()得到的是EnableAutoConfiguration.class
        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;
    }

调用了SpringFactoriesLoader.loadFactoryNames():

public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {        // "org.springframework.boot.autoconfigure.EnableAutoConfiguration"
        String factoryClassName = factoryClass.getName();        // 先调了下面的loadSpringFactories方法,得到一个Map
        // 从Map中用EnableAutoConfiguration类的全限定名取一个String列表(其实也是一些类的全限定名列表)
        return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
    }    // 上面方法先从这里取了个Map
    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"
            // 加载所有META-INF包下的spring.factories文件
            Enumeration<URL> urls = (classLoader != null ?
                    classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                    ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
            result = new LinkedMultiValueMap<>();            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                UrlResource resource = new UrlResource(url);
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);                for (Map.Entry<?, ?> entry : properties.entrySet()) {                    // 逗号分隔的String转为List
                    List<String> factoryClassNames = Arrays.asList(
                            StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));
                    result.addAll((String) entry.getKey(), factoryClassNames);
                }
            }
            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去加到上下文中。

那么META-INF/spring.factories到底是个什么gui呢。以之前创建的Hello World Web为例,工程中一共有2个META-INF/spring.factories文件,一个在spring-boot包中,一个在spring-boot-autoconfigure包中。

看下spring-boot-autoconfigure包中的内容(片段):

# Initializersorg.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener# Application Listenersorg.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer# Auto Configuration Import Listenersorg.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener# Auto Configuration Import Filtersorg.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnClassCondition# Auto Configureorg.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\ ...

看到一堆的xxxConfiguration,没错,这些都注解了@Configuration。spring-boot-autoconfigure为我们引入了一大堆的java配置类作为组件的默认配置。

包括常见组件的默认配置,有rabbit相关的,有redis相关的,有Security相关的等等。

那么问题来了,难道所有的配置类都会被加载吗,答案是显而易见的。那么来看看是如何实现的,以Redis的配置类RedisAutoConfiguration为例,看下类源码:

@Configuration// 当存在RedisOperations时,才会加载该配置类@ConditionalOnClass(RedisOperations.class)@EnableConfigurationProperties(RedisProperties.class)@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })public class RedisAutoConfiguration {    @Bean
    // 当不存在名为"redisTemplate"的bean时,才会创建该bean
    @ConditionalOnMissingBean(name = "redisTemplate")    public RedisTemplate<Object, Object> redisTemplate(
            RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);        return template;
    }    @Bean
    // 当Spring上下文中不存在StringRedisTemplate类实例的时候,才会创建该bean
    @ConditionalOnMissingBean(StringRedisTemplate.class)    public StringRedisTemplate stringRedisTemplate(
            RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);        return template;
    }
}

这里利用了Spring条件注解的特性,通过设定一定的条件来实现不同场景下加载不同的配置。

自动配置类生效的条件通常是我们引入了相关的组件,如果没有引入组件,那么就算包含在spring.factories文件中也不会被加载。

而是否要注入Bean则要看当前上下文中是否已经存在相应的Bean。如果不存在,那么由默认配置来补充。如果已经存在了,自动配置会不满足注解条件,就不会被创建。

有了这两点,可以做到当我们不做任何配置的时候可以用默认配置来运用新组件,而当我们需要对配置进行调整的时候用自定义的配置来覆盖即可。

Tips

上面源码中标记了一个缓存。在读取META-INF/spring.factories文件后相关数据是会保存到SpringFactoriesLoader类的缓存中的。而这里第一次读取META-INF/spring.factories的时机并不是在自动配置这里,早在上一篇的SpringApplication构造方法中就已经缓存了:

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));    this.webApplicationType = deduceWebApplicationType();    // 设置初始化器,这里就已经读取了META-INF/spring.factories
    setInitializers((Collection) getSpringFactoriesInstances(
        ApplicationContextInitializer.class));
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));    this.mainApplicationClass = deduceMainApplicationClass();
}

小结

这一节通过@EnableAutoConfiguration注解分析了SpringBoot自动配置的实现。通过@Import注解添加自动配置选择器(AutoConfigurationImportSelector),选择器中首先读取META-INF路径下的spring.factories文件。在spring.factories文件中,SpringBoot官方提供了许多常见组件的默认配置,以java配置类形式存在。在这些java配置类中又利用了Spring的条件注解,让我们可以在默认配置和自定义配置之间灵活切换。

可以这么认为:SpringBoot在Spring原有的基础上,通过拼凑组合又实现了一个强大的特性——自动配置。

自动配置让我们可以在不做任何配置的情况下直接使用一个新的类库(前提是足够普遍),也能满足我们自定义配置的需求。除此之外,我们还可以利用这个思路,实现具有团队特色的自动配置,让团队开发也更加高效。

相关阅读:SpringBoot入门(一)——开箱即用

SpringBoot入门(二)——起步依赖

SpringBoot入门(三)——入口类解析

SpringBoot入门(四)——自动配置

SpringBoot入门(五)——自定义配置

网易云新用户大礼包:https://www.163yun.com/gift

本文来自网易实践者社区,经作者金港生授权发布。

SpringBoot入门(四)——自动配置的更多相关文章

  1. 01.springboot入门--启用自动配置注解EnableAutoConfiguration

    springboot入门 <parent> <groupId>org.springframework.boot</groupId> <artifactId&g ...

  2. Springboot学习03-SpringMVC自动配置

    Springboot学习03-SpringMVC自动配置 前言 在SpringBoot官网对于SpringMVCde 自动配置介绍 1-原文介绍如下: Spring MVC Auto-configur ...

  3. 【玩转SpringBoot】给自动配置来个整体大揭秘

    上一篇文章中提到的条件注解,只是自动配置整体解决方案中的一个环节而已,可以说是管中窥豹. 本文就逐步擦除迷雾,让整体浮现出来,这样就会有一个宏观的认识. 除了写代码之外,还能干点什么? 提到“配置”这 ...

  4. Springboot 禁用数据库自动配置

    转载至:https://blog.csdn.net/wyw815514636/article/details/80846545 https://blog.csdn.net/knqi007/articl ...

  5. 【SpringBoot】SpringBoot与SpringMVC自动配置(五)

    本文介绍SpringBoot对Spring MVC自动配置,SpringBoot自动配置原理可以参考:[SpringBoot]SpringBoot配置与单元测试(二) 首先新建一个SpringBoot ...

  6. springboot 入门八-自定义配置信息(编码、拦截器、静态资源等)

    若想实际自定义相关配置,只需要继承WebMvcConfigurerAdapter.WebMvcConfigurerAdapter定义些空方法用来重写项目需要用到的WebMvcConfigure实现.具 ...

  7. 【springboot】之自动配置原理

    使用springboot开发web应用是很方便,只需要引入相对应的GAV就可以使用对应的功能,springboot默认会帮我们配置好一些常用配置.那么springboot是怎么做到的呢?这篇文章将一步 ...

  8. SpringBoot扩展SpringMVC自动配置

    SpringBoot中自动配置了 ViewResolver(视图解析器) ContentNegotiatingViewResolver(组合所有的视图解析器) 自动配置了静态资源文件夹.静态首页.fa ...

  9. IntelliJ IDEA 2017版 SpringBoot的关闭自动配置和自定义Banner

    一.关闭自动配置 在jar包下找下边的名字    设置关闭自动配置jar    多个的时候配置       二.自定义Banner   (1)网站搜索一个图案.网址:http://patorjk.co ...

  10. SpringBoot日记——SpringMvc自动配置与扩展篇

    为了让SpringBoot保持对SpringMVC的全面支持和扩展,而且还要维持SpringBoot不写xml配置的优势,我们需要添加一些简单的配置类即可实现: 通常我们使用的最多的注解是: @Bea ...

随机推荐

  1. sql时间格式转换

    sql server2000中使用convert来取得datetime数据类型样式(全) 日期数据格式的处理,两个示例: CONVERT(varchar(16), 时间一, 20) 结果:2007-0 ...

  2. BZOJ 1878 [SDOI2009]HH的项链 【莫队】

    任意门:https://www.lydsy.com/JudgeOnline/problem.php?id=1878 1878: [SDOI2009]HH的项链 Time Limit: 4 Sec  M ...

  3. HUD 1288 Hat's Tea(反向的贪心,非常好的一道题)

    传送门:http://acm.hdu.edu.cn/showproblem.php?pid=1288 Hat's Tea Time Limit: 2000/1000 MS (Java/Others)  ...

  4. 使用Storyboard拖线容易出错的地方

    使用Storyboard拖线容易出错的地方: 在Storyboard中,选中某个控件,按住ctrl键进行拖线,建立Outlet和Action后,不能手动再去修改自动生成的代码,然后再次进行连线,这样会 ...

  5. Java JVM技术

    .1.     java监控工具使用 .1.1.    jconsole jconsole是一种集成了上面所有命令功能的可视化工具,可以分析jvm的内存使用情况和线程等信息. 启动jconsole 通 ...

  6. java基本方法

    Java 方法 在前面几个章节中我们经常使用到 System.out.println(),那么它是什么呢? println() 是一个方法. System 是系统类. out 是标准输出对象. 这句话 ...

  7. mysql错误errno:121

    121错误是因为外键名重复.在同一个库中外键是不允许与其他外键重名的. 遇到这个错误请给你定义的外键换唯一无重复的名字. 同时查阅到外键也有可能导致150错误. Can't create table ...

  8. Python 学习笔记(十一)Python语句(一)

    运算符和条件语句 算术运算符 运算符 描述 实例 + 加 - 两个对象相加 a + b 输出结果 30 - 减 - 得到负数或是一个数减去另一个数 a - b 输出结果 -10 * 乘 - 两个数相乘 ...

  9. Linux系统NBD驱动安装拓展篇

    前言: 最近在安装中标麒麟机器的时候,发现麒麟的操作系统找不到src.rpm包,且系统内部也没有内核文件,导致正常方法安装NBD驱动无法实施.故这里找了另一种办法帮助此类型操作系统安装NBD驱动. 一 ...

  10. iOS:URL Scheme(完结)(18-1-3更)

    1.APP跳转 2.APP功能跳转 3.系统功能跳转 1.APP跳转 1.被打开方 设置APP的URL Types(设置位置在 “项目 - TARGETS - APP icon - info - (拉 ...