本文来自网易云社区

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. 【剑指offer】字符串的组合

    版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/mmc_maodun/article/details/26405471 转载请注明出处:http:// ...

  2. Android HttpClient自己主动登陆discuz论坛!

    你登陆论坛的时候,我们先看看浏览器干了什么事儿: 用Firefox打开HiPda 的登陆页面,输入用户名和password,点登陆. 以下是通过firebug插件获取的数据: 能够看到浏览器这个htt ...

  3. ADF中VO的查询方法比较

    getRowCount(),getQueryHitCount(oracle.jbo.server.ViewRowSetImpl),getEstimatedRangePageCount,getCappe ...

  4. PHP数组 转 对象/对象 转 数组

    /** * 数组 转 对象 * * @param array $arr 数组 * @return object */ function array_to_object($arr) { if (gett ...

  5. Filter实现字符集统一设置

    Filter实现字符集统一设置 其实是对request和response请求进行了拦截 1.创建Filter类,实现javax.Servlet接口 doFilter方法 //设置字符集 request ...

  6. 两种方式(xml+代码)构建SqlSessionFactory+完整实现

    首先创建类.接口.数据库: entity包下Admin类: package com.wbg.springJavaConfig.entity; public class Admin { private ...

  7. Gradle Goodness: Working with Live Task Collection

    Gradle support the definition of so called live collections. These collections are mostly created ba ...

  8. 小程序内嵌H5——判断小程序环境的坑

    现在各种小程序风靡,这边H5的需求还没有搞定,产品又要求做小程序版本,做可以,关键是618前上线,我-- whatever,618要做推广,日期订了,剩下的就只能是排期,定方案,尽可能完成. 最后和产 ...

  9. javaSpring知识点总结

    1 js 概述 js是一门基于对象和事件驱动的脚本语言,主要应用在客户端 js特点: 交互性(信息的动态交互) 安全性(不允许直接访问本地硬盘) 跨平台(只要是可以解释js的浏览器都可以执行,和平台无 ...

  10. 如何在运行jar指定使用的JDK

    写一个.bat文件 例如在同文件夹下,新建一个run.bat文件 run.bat 的内容是如下: set JAVA_HOME=C:\jdk1.7.0_67set CLASSPATH=.;%JAVA_H ...