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

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

@SpringBootApplication

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

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
// package org.springframework.boot.autoconfigure; import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.core.annotation.AliasFor; @Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration //自动配置注解
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
....
Class<?>[] scanBasePackageClasses() default {};
}

@EnableAutoConfiguration

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

  • @AutoConfigurationPackage
  • @Import({EnableAutoConfigurationImportSelector.class})
package org.springframework.boot.autoconfigure;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import; @Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({EnableAutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; Class<?>[] exclude() default {}; String[] excludeName() default {};
}

@AutoConfigurationPackage

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

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


package org.springframework.boot.autoconfigure; import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.autoconfigure.AutoConfigurationPackages.Registrar;
import org.springframework.context.annotation.Import; @Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
}

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

 static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
Registrar() {
} public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
AutoConfigurationPackages.register(registry, (new AutoConfigurationPackages.PackageImport(metadata)).getPackageName());
} public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new AutoConfigurationPackages.PackageImport(metadata));
}
}

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



AnnotationMetadata:SpringBoot注解元数据

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

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

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

在Application类包外写个Controller测试类

@RestController
public class TestController { @GetMapping("/hello")
public String hello(){
return "hello world!";
}
}

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

@Import({EnableAutoConfigurationImportSelector.class})

package org.springframework.boot.autoconfigure;

import org.springframework.core.type.AnnotationMetadata;

/** @deprecated */
@Deprecated
public class EnableAutoConfigurationImportSelector extends AutoConfigurationImportSelector {
public EnableAutoConfigurationImportSelector() {
} protected boolean isEnabled(AnnotationMetadata metadata) {
return this.getClass().equals(EnableAutoConfigurationImportSelector.class) ? (Boolean)this.getEnvironment().getProperty("spring.boot.enableautoconfiguration", Boolean.class, true) : true;
}
}

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

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

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

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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;
}

loadFactoryNames方法代码,

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

然后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. Spring Boot (六): 为 JPA 插上翅膀的 QueryDSL

    在前面的文章中,我们介绍了 JPA 的基础使用方式,<Spring Boot (三): ORM 框架 JPA 与连接池 Hikari>,本篇文章,我们由入门至进阶的介绍一下为 JPA 插上 ...

  2. 《图解HTTP》读后记

    看了一遍又一遍…………不如记一下 用做笔记的方式来看,发现了好多之前没发现的知识点.....感觉前几次看了跟没看一样... 1.1HTTP通常被译为超文本传输协议,但这种译法并不严谨.严谨的译名应该为 ...

  3. java-整型数值 用 16进制转换、2进制转换-Integer.toHexString

    负数为什么要用补码表示 可以将符号位和其它位统一处理 减法也可按加法来处理 另外,两个用补码表示的数相加时,如果最高位(符号位)有进位,则进位被舍弃 正数:原码.反码.补码相同. 负数:反码符号位不变 ...

  4. Java 代理模式 (二) 动态代理

    代理模式 代理(Proxy)是一种设计模式, 提供了对目标对象另外的访问方式:即通过代理访问目标对象. 这样好处: 可以在目标对象实现的基础上,增强额外的功能操作.(扩展目标对象的功能). 代理模式的 ...

  5. poj-2232 New Stone-Forfex-Cloth Game 思维题

    Acm is a clever boy, and he developed a new game form the old Stone-Forfex-Cloth game. In this game, ...

  6. windows如何利用计划任务自动关机?

    第一步打开控制面板,然后选择计划任务,打开它 选择创建基本任务 输入任务名称,描述,选择下一步 根据需要选择,我这里选择的是每天,然后选择下一步 选择任务开始时间,然后选择下一步 选择启动程序,然后选 ...

  7. freemarker模版引擎技术总结

    FreeMarker语言概述 FreeMarker是一个模板引擎,一个基于模板生成文本输出的通用工具,使用纯Java编写. FreeMarker被设计用来生成HTML Web页面,特别是基于MVC模式 ...

  8. Golang 接口与反射知识要点

    目录 Golang 接口与反射知识要点 1. 接口类型变量 2. 类型断言 3. 鸭子类型 4. 反射机制 5. reflect 包 TypeOf().ValueOf() Type().Kind() ...

  9. CMD的最佳“代替品”

    让CMD成为历史 Windows用户大多都使用过"cmd",cmd被称为"阉割版"的DOS系统~ 很多用户除此之外,还喜欢Linux命令行~但是CMD的命令和L ...

  10. C# HttpWebRequest 后台调用接口上传大文件以及其他参数

    直接上代码,包各位看客能用!!! 1.首先请求参数的封装 /// <summary> /// 上传文件 - 请求参数类 /// </summary> public class ...