源码学习系列之SpringBoot自动配置源码学习(篇一)

ok,本博客尝试跟一下Springboot的自动配置源码,做一下笔记记录,自动配置是Springboot的一个很关键的特性,也容易被忽略的属性,因为这个属性被包括在@SpringBootApplication注解里,所以不去跟一下源码都不知道还有这个属性,ps:本博客源码基于SpringBoot1.5.7版本

@SpringBootApplication

ok,跟一下@SpringBootApplication,发现@SpringBootApplication其实是一个复合的注解,由很多注解构成,@EnableAutoConfiguration其实只是其一部分,@EnableAutoConfiguration就是开启自动配置的注解

  1. //
  2. // Source code recreated from a .class file by IntelliJ IDEA
  3. // (powered by Fernflower decompiler)
  4. //
  5. package org.springframework.boot.autoconfigure;
  6. import java.lang.annotation.Documented;
  7. import java.lang.annotation.ElementType;
  8. import java.lang.annotation.Inherited;
  9. import java.lang.annotation.Retention;
  10. import java.lang.annotation.RetentionPolicy;
  11. import java.lang.annotation.Target;
  12. import org.springframework.boot.SpringBootConfiguration;
  13. import org.springframework.boot.context.TypeExcludeFilter;
  14. import org.springframework.context.annotation.ComponentScan;
  15. import org.springframework.context.annotation.FilterType;
  16. import org.springframework.context.annotation.ComponentScan.Filter;
  17. import org.springframework.core.annotation.AliasFor;
  18. @Target({ElementType.TYPE})
  19. @Retention(RetentionPolicy.RUNTIME)
  20. @Documented
  21. @Inherited
  22. @SpringBootConfiguration
  23. @EnableAutoConfiguration //自动配置注解
  24. @ComponentScan(
  25. excludeFilters = {@Filter(
  26. type = FilterType.CUSTOM,
  27. classes = {TypeExcludeFilter.class}
  28. ), @Filter(
  29. type = FilterType.CUSTOM,
  30. classes = {AutoConfigurationExcludeFilter.class}
  31. )}
  32. )
  33. public @interface SpringBootApplication {
  34. ....
  35. Class<?>[] scanBasePackageClasses() default {};
  36. }

@EnableAutoConfiguration

点进@EnableAutoConfiguration,比较重要的列出来:

  • @AutoConfigurationPackage
  • @Import({EnableAutoConfigurationImportSelector.class})
  1. package org.springframework.boot.autoconfigure;
  2. import java.lang.annotation.Documented;
  3. import java.lang.annotation.ElementType;
  4. import java.lang.annotation.Inherited;
  5. import java.lang.annotation.Retention;
  6. import java.lang.annotation.RetentionPolicy;
  7. import java.lang.annotation.Target;
  8. import org.springframework.context.annotation.Import;
  9. @Target({ElementType.TYPE})
  10. @Retention(RetentionPolicy.RUNTIME)
  11. @Documented
  12. @Inherited
  13. @AutoConfigurationPackage
  14. @Import({EnableAutoConfigurationImportSelector.class})
  15. public @interface EnableAutoConfiguration {
  16. String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
  17. Class<?>[] exclude() default {};
  18. String[] excludeName() default {};
  19. }

@AutoConfigurationPackage

先看@AutoConfigurationPackage源码,这个注解是开启自动配置包的,关注点在@Import({Registrar.class}),核心在Registrar类

备注:@import注解是Spring的底层注解,作用是导入一个组件到容器里


  1. package org.springframework.boot.autoconfigure;
  2. import java.lang.annotation.Documented;
  3. import java.lang.annotation.ElementType;
  4. import java.lang.annotation.Inherited;
  5. import java.lang.annotation.Retention;
  6. import java.lang.annotation.RetentionPolicy;
  7. import java.lang.annotation.Target;
  8. import org.springframework.boot.autoconfigure.AutoConfigurationPackages.Registrar;
  9. import org.springframework.context.annotation.Import;
  10. @Target({ElementType.TYPE})
  11. @Retention(RetentionPolicy.RUNTIME)
  12. @Documented
  13. @Inherited
  14. @Import({Registrar.class})
  15. public @interface AutoConfigurationPackage {
  16. }

Registrar 类是AutoConfigurationPackages类的静态内部类,

  1. static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
  2. Registrar() {
  3. }
  4. public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
  5. AutoConfigurationPackages.register(registry, (new AutoConfigurationPackages.PackageImport(metadata)).getPackageName());
  6. }
  7. public Set<Object> determineImports(AnnotationMetadata metadata) {
  8. return Collections.singleton(new AutoConfigurationPackages.PackageImport(metadata));
  9. }
  10. }

看一下(new AutoConfigurationPackages.PackageImport(metadata)).getPackageName()获取的是什么,用idea的工具,计算表达式,可以看到其实获取的是SpringBoot启动类上面的包名



AnnotationMetadata:SpringBoot注解元数据

所以,@AutoConfigurationPackage开启后,就可以将主配置类(@SpringBootApplication)所在包及其子包里面的所有组件都扫描到Spring容器里

  1. public static void register(BeanDefinitionRegistry registry, String... packageNames) {
  2. if (registry.containsBeanDefinition(BEAN)) {
  3. BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
  4. ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
  5. constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
  6. } else {// Spring容器里没有找到对应组件
  7. /* 将组件注册到Spring容器里 */
  8. GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); beanDefinition.setBeanClass(AutoConfigurationPackages.BasePackages.class);
  9. beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
  10. beanDefinition.setRole(2);
  11. registry.registerBeanDefinition(BEAN, beanDefinition);
  12. }
  13. }

ok,跟了源码,当然只是简单跟一下,没有特别细的跟,这个过程可以看出这里的组件扫描只是扫描主配置类(@SpringBootApplication)所在包及其子包里面的所有组件,所以,写了例子验证一下:

在Application类包外写个Controller测试类

  1. @RestController
  2. public class TestController {
  3. @GetMapping("/hello")
  4. public String hello(){
  5. return "hello world!";
  6. }
  7. }

启动项目,进行访问,发现都是404找不到这个接口,再将这个Controller放在包里面,才可以扫描到

@Import({EnableAutoConfigurationImportSelector.class})

  1. package org.springframework.boot.autoconfigure;
  2. import org.springframework.core.type.AnnotationMetadata;
  3. /** @deprecated */
  4. @Deprecated
  5. public class EnableAutoConfigurationImportSelector extends AutoConfigurationImportSelector {
  6. public EnableAutoConfigurationImportSelector() {
  7. }
  8. protected boolean isEnabled(AnnotationMetadata metadata) {
  9. return this.getClass().equals(EnableAutoConfigurationImportSelector.class) ? (Boolean)this.getEnvironment().getProperty("spring.boot.enableautoconfiguration", Boolean.class, true) : true;
  10. }
  11. }

主要看一下其基类AutoConfigurationImportSelector代码,看一下selectImport方法:

  1. /**装载很多自动配置类,以全类名的方式返回一个字符数组**/
  2. public String[] selectImports(AnnotationMetadata annotationMetadata) {
  3. if (!this.isEnabled(annotationMetadata)) {
  4. return NO_IMPORTS;
  5. } else {
  6. try {
  7. //获取自动配置的元数据
  8. AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
  9. //装载配置属性
  10. AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
  11. //通过类加载器读取所有的配置类全类名
  12. List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
  13. configurations = this.removeDuplicates(configurations);
  14. configurations = this.sort(configurations, autoConfigurationMetadata);
  15. Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
  16. this.checkExcludedClasses(configurations, exclusions);
  17. configurations.removeAll(exclusions);
  18. configurations = this.filter(configurations, autoConfigurationMetadata);
  19. this.fireAutoConfigurationImportEvents(configurations, exclusions);
  20. return (String[])configurations.toArray(new String[configurations.size()]);
  21. } catch (IOException var6) {
  22. throw new IllegalStateException(var6);
  23. }
  24. }
  25. }
  • AutoConfigurationMetadata信息,获取整个JavaEE体系的一些配置类,当然是Springboot集成的,比如有WebMvcAutoConfiguration自动配置类

跟一下getCandidateConfigurations方法,SpringFactoriesLoader是Spring-code工程的工厂加载类,使用SpringFactoriesLoader,需要在模块的META-INF/spring.factories文件自己配置属性,这个Properties格式的文件中的key是接口、注解、或抽象类的全名,value是以逗号 “ , “ 分隔的实现类,使用SpringFactoriesLoader可以实现将相应的实现类注入Spirng容器

  1. protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
  2. List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
  3. 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.");
  4. return configurations;
  5. }

loadFactoryNames方法代码,

  1. public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
  2. /* 将spring.factories的类都装载到Spring容器*/
  3. public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
  4. String factoryClassName = factoryClass.getName();
  5. try {
  6. //将META-INF/spring.factories文件里配置的属性都装载到Enumeration数据结构里
  7. Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
  8. ArrayList result = new ArrayList();
  9. //遍历获取属性,然后再获取对应的配置类全类名
  10. while(urls.hasMoreElements()) {
  11. URL url = (URL)urls.nextElement();
  12. Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
  13. String factoryClassNames = properties.getProperty(factoryClassName);
  14. result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
  15. }
  16. return result;
  17. } catch (IOException var8) {
  18. throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() + "] factories from location [" + "META-INF/spring.factories" + "]", var8);
  19. }
  20. }

然后META-INF/spring.factories文件放在哪?很显然是放在Springboot的自动配置模块里,如图:

所以,@Import({EnableAutoConfigurationImportSelector.class})开启之后,主要是EnableAutoConfigurationImportSelector这个类的作用就是在SpringBoot启动时候将从SpringBoot自动配置工程的META-INF/spring.factories文件中获取指定的值,经过SpringFactoriesLoader加载之后将很多自动配置类加载到Spring容器,所以我们不需要配置,mvc等等默认配置就已经随着SpringBoot启动而自动生效

ok,Springboot的自动配置类都在这个包里,源码很多,所以本博客只是简单跟一下源码

源码学习系列之SpringBoot自动配置(篇一)的更多相关文章

  1. 源码学习系列之SpringBoot自动配置(篇二)

    源码学习系列之SpringBoot自动配置(篇二)之HttpEncodingAutoConfiguration 源码分析 继上一篇博客源码学习系列之SpringBoot自动配置(篇一)之后,本博客继续 ...

  2. SpringBoot源码学习系列之SpringMVC自动配置

    目录 1.ContentNegotiatingViewResolver 2.静态资源 3.自动注册 Converter, GenericConverter, and Formatter beans. ...

  3. SpringBoot源码学习系列之异常处理自动配置

    SpringBoot源码学习系列之异常处理自动配置 1.源码学习 先给个SpringBoot中的异常例子,假如访问一个错误链接,让其返回404页面 在浏览器访问: 而在其它的客户端软件,比如postm ...

  4. SpringBoot源码学习系列之Locale自动配置

    目录 1.spring.messages.cache-duration 2.LocaleResolver 的方法名必须为localeResolver 3.默认LocaleResolver 4.指定默认 ...

  5. SpringBoot源码学习系列之嵌入式Servlet容器

    目录 1.博客前言简单介绍 2.定制servlet容器 3.变换servlet容器 4.servlet容器启动原理 SpringBoot源码学习系列之嵌入式Servlet容器启动原理 @ 1.博客前言 ...

  6. Spring5.0源码学习系列之事务管理概述

    Spring5.0源码学习系列之事务管理概述(十一),在学习事务管理的源码之前,需要对事务的基本理论比较熟悉,所以本章节会对事务管理的基本理论进行描述 1.什么是事务? 事务就是一组原子性的SQL操作 ...

  7. Spring5.0源码学习系列之浅谈BeanFactory创建

    Spring5.0源码学习系列之浅谈BeanFactory创建过程 系列文章目录 提示:Spring源码学习专栏链接 @ 目录 系列文章目录 博客前言介绍 一.获取BeanFactory主流程 二.r ...

  8. Java并发包源码学习系列:阻塞队列BlockingQueue及实现原理分析

    目录 本篇要点 什么是阻塞队列 阻塞队列提供的方法 阻塞队列的七种实现 TransferQueue和BlockingQueue的区别 1.ArrayBlockingQueue 2.LinkedBloc ...

  9. JDK源码学习系列05----LinkedList

                                             JDK源码学习系列05----LinkedList 1.LinkedList简介 LinkedList是基于双向链表实 ...

随机推荐

  1. 基于操作系统原理的Linux 的基本操作和常用命令的使用

    一.实验目的 1.学会不同Linux用户登录的方法. 2.掌握常用Linux命令的使用方法. 3.了解Linux命令中参数选项的用法和作用. 二.实验内容 1. 文件操作命令 (1) 查看文件与目录 ...

  2. 【Django】url(路由系统)

    1.单一路由对应 url(r'^index/',views.index), 2.基于正则的路由 url(r'^index/(\d*)', views.index), url(r'^manage/(?P ...

  3. @Transient的用法和格式化页面展示的数据格式

    一.Hibernate中:@Transient用法 用法1:使用@Transient这个注解添加表中不存在字段.将这个注解添加到自定义字段的get方法上 用法2:将该注解添加到定义该字段的头部即可,例 ...

  4. Python基础(十二)

    今日主要内容 推导式 生成器表达式 lambda匿名函数 内置函数介绍 一.推导式 (一)列表推导式 先来看一段代码 建立一个空列表,向空列表中添加元素 lst = list() for i in r ...

  5. 【爬虫小程序:爬取斗鱼所有房间信息】Xpath(协程池版)

    # 本程序亲测有效,用于理解爬虫相关的基础知识,不足之处希望大家批评指正 from gevent import monkey monkey.patch_all() from gevent.pool i ...

  6. TensorFlow2.0(四):填充与复制

    .caret, .dropup > .btn > .caret { border-top-color: #000 !important; } .label { border: 1px so ...

  7. 解决Git报错:error: You have not concluded your merge (MERGE_HEAD exists).

    Git fetch和git pull的区别, 解决Git报错:error: You have not concluded your merge (MERGE_HEAD exists). 2017年02 ...

  8. SpringMvc问题记录-Controller对于静态变量的访问分析

    问题描述 在于朋友的讨论中分析到一种场景,即:Controller对于一个类中的静态变量进行访问时,如果第一个接口修改该静态变量的数据,另外一个接口获取该静态变量的数据,那么返回的结果是什么? 操作步 ...

  9. SpringCache - 请求级别缓存的简易实现

    前言 在SpringCache缓存初探中我们研究了如何利用spring cache已有的几种实现快速地满足我们对于缓存的需求.这一次我们有了新的更个性化的需求,想在一个请求的生命周期里实现缓存. 需求 ...

  10. markdown + 七牛云,让写文更容易

    常常写博文的人, 总有这样的烦恼: * 文章格式问题,各种文本编辑器格式不统一,在一处写好的文章复制到其他编辑器中格式错乱 * 图片问题,在不同的平台的图片需要重复上传,如果多平台发布很繁琐 由于这样 ...