本文来自网易云社区

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. shiro集成spring&工作流程&DelegatingFilterProxy

    1.集成Spring 参考文献: 新建web工程: ehcache-core来自Hibernate wen.xml <?xml version="1.0" encoding= ...

  2. web项目继承ServletContainerInitializer进行访问HttpServlet(WebServlet)

    java使用web项目不需要加web.xml 配置javax.servlet.ServletContainerInitializer 1.在src目录创建META-INF,META-INF目录下创建s ...

  3. Reading Notes : 180214 计算机的总线结构

    读书<计算机组成原理>,百度百科 基本上接触过计算机的人,都多少知道计算机的具体构成,但是真正能讲明白的却说了很多,本节将讲解一下计算机的基本硬件构成和一些基本信息,简单认识,以后再深入了 ...

  4. GoldenGate -- OGG EXTRACT进程 OGG-00446 报错

    -- :: INFO OGG- Positioning to (Thread ) Sequence , RBA , SCN 0.470706262. Source Context : SourceMo ...

  5. python打印99乘法表

    代码如下: print(XXX,end="\t") #表示打印不换行 附带python部分转义字符:

  6. keepalived+nginx+tomcat+redis实现负载均衡和session共享(原创)

    keepalived+nginx+tomcat+redis实现负载均衡和session共享 直接上链接,码了一天,就不再重写了,希望能帮到大家,有问题欢迎留言交流.

  7. jQuery基本toggle() toggleClass() 使用

    今天来学习一下jQuery的基本函数的使用,很简单. 首先写一个button做控制按钮,然后写一个div用按钮控制idv做动画,从而测试JQuery的动画函数 <head> <met ...

  8. 如何快速生成数据库字典(thinkphp5.0)

    本教程将教你快速生成数据库字典 示例代码使用PHP框架:Thinkphp5.0 PHP代码: /** * 生成数据库字典html * 可直接另存为再copy到word文档中使用 * * @return ...

  9. Delphi并行库System.Threading 之ITask 1

    不知什么时候,也许是XE8,也许是XE8之前 .Delphi里面多了个System.Threading的并行库. 虽然己经有非常棒的第三方并行库QWorker,但我还是更喜欢官方的东西. 下面是一段使 ...

  10. s3c2440系统时钟详解

    一.S3C2440系统时钟体系 S3C2440的时钟控制逻辑可以外接晶振,然后通过内部电路产生时钟源:也可以直接使用内部提供的时钟源,他们通过引脚的设置来选择.时钟逻辑给整个芯片提供了3中时钟:FCL ...