Spring Boot自动配置原理解析

目录

1、引导加载自动配置类

2、@EnableAutoConfiguration注解:实现自动配置

2.1、@AutoConfigurationPackage 自动配置包

2.2、@Import(AutoConfigurationImportSelector.class) 自动配置组件导入选择器

3、按需开启自动配置项目

4、修改默认配置

5、SpringBoot最佳实践


1、引导加载自动配置类


我们都知道在创建一个 Spring Boot应用 的时候,一般类文件路径的最外层都会有一个启动类,也叫主配置类。通过主配置的 main() 方法可以启动 Spring Boot 应用程序,如果你足够细心的话,可以发现主配置上有一个 @SpringBootApplication 注解

@SpringBootApplication
public class HelloWorldMainApplication {
    public static void main(String[] args) {
        // 启动Spring应用
        SpringApplication.run(HelloWorldMainApplication.class, args)
    }
}
@SpringBootApplication:这个注解会被标记在 Spring Boot 的启动类/主配置类上,在 IDEA 中点开这个注解的源码,我们可以发现它是一个嵌套注解,里面包含了很多其他的注解,其实这些注解有的就是用于引导和按需加载自动配置类的。
@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 {
}

表1. @SpringBootApplication 注解包含的嵌套注解及其作用

注解作用
@Target(ElementType.TYPE)JDK提供的注解,主要用来限制注解的作用类型,ElementType.TYPE 表示适用于类、接口或枚举,此外还有FIELD、METHOD 等等
 
@Retention(RetentionPolicy.RUNTIME)JDK提供的注解,用于限制注解的作用范围。RetentionPolicy.RUNTIME 表示注解会被编译在 class 的类文件并且保留到运行时的虚拟机中,所以可以通过反射获取,此外还有 SOURCE(注:在编译时就会被丢弃,无法通过反射获取)和 CLASS(注:默认值,注解会被编译在class的类文件中,但是不会保留到运行时的虚拟中)两种范围
@Documented
JDK的注解,表示该注解可以被javadoc文档和类似的工具所记录
@Inherited
JDK的注解,表示该注解类型的子注解可以自动继承父注解的所有注解
@ComponentScan
Spring的组件扫描注解,一般和@Configuration注解一起使用,指定Spring扫描注解的package。如果没有指定包,那么默认会扫描此配置类所在的package。
  • includeFilters属性则可以指定扫描路径下没有加入Spring注解的类加入Spring容器

  • excludeFilters属性过滤出不用加入Spring容器的类

@Filter这个注解起到过滤器的作用,通常和 @ComponetScan注解一起使用,过滤掉不需要的自动配置类
@SpringBootConfiguration
底层就是
@Configuration 注解,表示被注解的类是一个配置类
@EnableAutoConfiguration
这个注解是 SpringBoot 实现自动配置的基础

以下这张图片粗略地展示了 @SpringBootApplication 注解的底层结构:

从上面的图片可以看出,@SpringBootApplication 注解最外层的嵌套注解包含了 @EnableAutoConfiguration@SpringBootConfiguration@ComponentScan 注解,从名字中可以看出 @EnableAutoConfiguration 注解,好像是和自动配置有关的,那么我们先来看看这个注解

2、@EnableAutoConfiguration注解:实现自动配置


点开 @EnableAutoConfiguration 注解的源码,我们可以发现很多嵌套注解,其中有两个注解非常重要:

  1. @AutoConfigurationPackage 自动配置包,指定了默认的包规则 
  2. @Import(AutoConfigurationImportSelector.class) 自动配置组件导入选择器 

实际上,就是这两个注解实现的自动配置。

2.1、@AutoConfigurationPackage 自动配置包


 我们先来看看看 @AutoConfigurationPackage 注解的源码

@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}

可以看到 @AutoConfigurationPackage 注解中有一个@Import(AutoConfigurationPackages.Registrar.class) 注解,查看其源码会发现有一个 Registrar类,这个类里面有一个 registerBeanDefinitions() 方法,通过源码注释和方法名称我们可以得知,这个方法用于批量注册 bean 定义

/**
 * 批量注册Bean定义
 * 将指定的一个包下的所有组件导入进来?
 * 1、利用Registrar给容器中导入一系列的组件
 * 2、相当于Registrar把HelloWorldMainApplication主配置类包下的组件批量注册进Spring容器
 */

// AnnotationMetadata metadata:注解原信息 —> AutoConfigurationPackage -> 主配置类 
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        // 通过注解原信息拿到包名封装到数组中,然后注册进去
        register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
    }

}

通过反射获取注解原信息,拿到注解配置所在的包名,然后批量注册 bean 定义。那么是不是真的是这样做的呢,我们可以打个断点调试看看。

启动应用之后可以看到,扫描的包名正好是主配置类所在的包下面

2.2、@Import(AutoConfigurationImportSelector.class) 自动配置组件导入选择器


这个注解其实是 Spring 用于决定导入哪些自动配置类的

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    }
    // 利用getAutoConfigurationEntry()给容器中批量导入组件
    AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
    return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

/**
 * 1、利用getAutoConfigurationEntry(annotationMetadata);给容器中批量导入组件
 * 2、调用getCandidateConfigurations(annotationMetadata, attributes);获取所有要导入容器
 * 中的类
 * 3、利用工厂加载 Map<String, List<String>> loadSpringFactories(ClassLoader
 * classLoader) 得到所有的组件 
 * 4、从 "META-INF/spring.factories"来加载一个文件
 * 		默认扫描我们系统里面所有 "META-INF/spring.factories"位置的文件
 *  	spring-boot-autoconfigure-2.4.4.RELEASE.jar包里面也有META-INF/spring.factories
 * 		扫描的就是这个文件里的 130个自动配置类
 * 		org.springframework.boot.autoconfigure.EnableAutoConfiguration
 *      启动时128个场景的应用默认全部加载,实际只有128个能按需配置 @ConditionalOnClass
 * 5、按需加载,条件装配(@Conditional)
 */
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    // 得到自动配置信息:获取所有要导入容器中的类
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    configurations = removeDuplicates(configurations);
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations, exclusions);
    configurations.removeAll(exclusions);
    configurations = getConfigurationClassFilter().filter(configurations);
    fireAutoConfigurationImportEvents(configurations, exclusions);
    return new AutoConfigurationEntry(configurations, exclusions);
}


protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    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;
}

查看此处,我们可以发现结果刚好就是130个

 当然,并不是所有的自动配置类都会配置进 Spring 容器,因为很多时候不是所有的自动配置都能用得上的。如果不需要的自动配置也配置进容器了,在运行程序时就会造成不必要的资源消耗

3、按需开启自动配置项目


  • 启动时130个场景的应用默认全部加载,实际只有128个能按需配置 @ConditionalOnClass

  • 按需加载,条件装配(@Conditional)

打开 Spring 自动配置依赖包,可以发现这里面有很多的自动配置类,我们随机查看其中一个就能发现一些以 @ConditionalOn 开头的注解,比如 @ConditionalOnClass@ConditionalOnMissingBean 等等

就是这些注解实现的按需加载自动配置,例如:

@ConditionalOnClass(Advice.class) 表示指定的类存在才会加载这个自动配置,@ConditionalOnProperty 则表示指定的属性存在才加载这个配置类,

我们现在知道了 Spring 是按需加载的自动配置类,那么我们能不能修改Spring的默认配置呢,答案是肯定的

4、修改默认配置


Spring Boot 默认底层配置所有必要的组件,但是如果用户有自定义配置,那么会以用户配置的优先

@Bean
@ConditionalOnBean(MultipartResolver.class) // 当容器中有这个组件
@ConditionalOnMissingBean(name = 			// 容器中没有这个名字的组件	DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
public MultipartResolver multipartResolver(MultipartResolver resolver) {
    // 给@Bean标注的方法传入了对象参数,这个参数的值就会从容器中找
    // SpringMvc multipartResolver,防止用户配置的文件上传解析器不符合规范
    // Detect if the user has created a MultipartResolver but named it incorrectly
    return resolver;
}

 5、SpringBoot最佳实践


  • 引入场景依赖
  • 查看SpringBoot自动配置了哪些
    • 自己分析,引入场景的配置一般都生效

    • 配置文件中 debug=true 开启自动配置报告,negative不生效 / positive生效

  • 是否需要修改

    • 参照文档修改配置项:Common Application Properties

    • 自己分析,xxxProperties 绑定了配置文件的哪些

    • 自定义加入或者替换组件:

      • @Bean@Component

    • 自定义器 xxxCustomizer

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值