本文来自网易云社区

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.classList<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());}    // 上面方法先从这里取了个Mapprivate 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转为ListList<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.factoriessetInitializers((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

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

转载于:https://www.cnblogs.com/163yun/p/9529149.html

SpringBoot入门(四)——自动配置相关推荐

  1. Spring Boot(1) 入门、自动配置

    Hello,Spring Boot 1.创建一个普通的maven项目 2.pom.xml引入依赖 <parent><groupId>org.springframework.bo ...

  2. 雷神SpringBoot入门和自动装配原理

    SpringBoot-helloWord! 首先让当前的工程作为Springboot的子工程 <parent><groupId>org.springframework.boot ...

  3. SpringBoot SimpleCacheConfiguration的自动配置原理

    引言   在之前的博客中分享了简单的SpringBoot缓存的HelloWorld程序,在篇博客中主要来分析一下SpringBoot对于缓存自动配置的原理 缓存自动配置原理   首先在SpringBo ...

  4. SpringBoot入门与常用配置

    目录 入门 常用配置 配置数据库连接池 MyBatisPuls开启驼峰映射 MyBatisPuls开启打印SQL 在springboot中设置过滤器 在springboot中设置监听器 设置自动填充 ...

  5. SpringBoot面试杀手锏——自动配置原理

    欢迎关注方志朋的博客,回复"666"获面试宝典 来源:blog.csdn.net/u014745069/ article/details/83820511 引言 不论在工作中,亦或 ...

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

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

  7. SpringBoot之SpringMVC自动配置

    关于SpringBoot中的SpringMVC自动配置的一些思考 : 自动配置 Spring Boot 自动配置好了SpringMVC 以下是SpringBoot对SpringMVC的默认配置:(We ...

  8. Springboot面试杀手锏-自动配置原理

    前言 随着互联网越来越流行,springboot已经成为我们无论是工作,还是面试当中,不得不掌握的技术.说起springboot笔者认为最重要的功能非自动配置莫属了,为什么这么说?如果参与过以前spr ...

  9. springboot 入门二- 读取配置信息一

    在上篇入门中简单介绍下springboot启动使用了大量的默认配置,在实际开发过程中,经常需要启动多个服务,那端口如何手动修改呢? 此篇就是简单介绍相关的配置文件信息. Spring Boot允许外部 ...

  10. springboot系列四、配置模板引擎、配置热部署

    一.配置模板引擎 在之前所见到的信息显示发现都是以 Rest 风格进行显示,但是很明显在实际的开发之中,所有数据的显示最终都应该交由页面完成,但是这个页面并不是*.jsp 页面,而是普通的*.html ...

最新文章

  1. 历史有资产忘记折旧如何处理_紧急提醒! 500万以下固定资产一次性计入“管理费用”的,会计抓紧调账!...
  2. 标准纯C++实现简单的词法分析器(三)
  3. .NET设计模式(2):单件模式(Singleton Pattern)
  4. elementui 搭建布局页面路由_【项目实践】使用Vue.js和ElementUI快速实现后台管理系统的界面布局...
  5. php is_post,PHP发送get、post请求的6种方法简明总结
  6. php一对多聊天程序代码,微信小程序实现一对多发消息
  7. 三十四 Python分布式爬虫打造搜索引擎Scrapy精讲—scrapy信号详解
  8. python来源是什么_python起源?为什么使用python?直至爱上python的五个理由
  9. 架构的变迁,从分层架构先聊起
  10. 【文文殿下】 [USACO08MAR]土地征用 题解
  11. C++入门系列博客二 C++ 控制流
  12. C++:使用vector::reserve来避免不必要的重新分配
  13. python pdf转txt_Python实现pdf文档转txt的方法示例
  14. CAJ格式文献转成PDF格式
  15. 基于3DGIS的智慧“云”综合产业园区建设
  16. 用C语言求和、找数组中的最大值以及求平均值
  17. python输入负数_如何让python使用负数
  18. mysql left join和or_mysql – 在LEFT JOIN中使用带OR条件的索引
  19. 北京周边自行车骑行线路大全
  20. 浪潮精彩亮相第十届中国云计算大会

热门文章

  1. DataList项模板中的div在后台得到方法
  2. C# SNMP 编程
  3. 关联性挖掘--Apriori算法详解
  4. 神经网络为什么需要激活函数
  5. 深度学习的实用层面 —— 1.12 梯度的数值逼近
  6. 06-用两个栈实现队列
  7. CSS基础必备知识点03
  8. 贝叶斯分层回归模型的推理、EM求解和Java编程
  9. Java实现数据批量导入数据库(优化速度-2种方法)
  10. Multi-thread--多线程运行实例