【Spring】SpringBoot自动注入原理分析,@SpringBootApplication、@EnableAutoConfiguration详解

文章目录

SpringBoot 原理分析

源码阅读

SpringBoot 是如何帮助我们做的呢?一切来自起源 SpringBoot 的启动类开始

  • @SpringBootApplication 标注的类就是 SpringBoot 项目的启动类
java 复制代码
@SpringBootApplication
public class SpringIocApplication {

    public static void main(String[] args) {
        // 1. 获取Spring上下文对象(启动应用)
        ApplicationContext context = 
            SpringApplication.run(SpringIocApplication.class, args);
        
        // 2. 从容器中获取Bean实例
        BeanLifeComponent beanLifeComponent = context.getBean(BeanLifeComponent.class);
        
        // 3. 使用Bean
        beanLifeComponent.use();
    }
}

这个类和普通类唯一的区别就是 @SpringBootApplication 注解,这个注解也是 SpringBoot 实现自动配置的核心

java 复制代码
@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 {
	// ...代码省略
}

@SpringBootApplication

这是一个组合注解,里面包含了

  1. 元注解
  2. @SpringBootConfiguration
  3. EnableAutoConfiguration(开启自动配置)
  4. ComponentScan (包扫描)
1. 元注解

JDK 中提供了 4 个标准的用来对注解类型进行注解的注解类 ,我们称之为 meta-annotation(元注解),他们分别是:

  1. @Target:描述注解的使用范围(即被修饰的注解可以用在什么地方)
  2. Retention:描述注解保留的时间范围
  3. @Documented:描述在使用 javadoc 工具为类生成帮助文档时是否要保留其注解信息
  4. Inherited:使被它修饰的注解具有继承性(如果每个类使用了被 @Inherited 修饰的注解,则其子类将自动具有该注解)
2. @SpringBootConfiguration

里面就是 @Configuration,标注当前类为配置类,其实只是做了一层封装,改了个名字而已

  • @Indexed 注解,是用来加速应用启动的,不用关心
java 复制代码
@Target({ElementType.TYPE})  
@Retention(RetentionPolicy.RUNTIME)  
@Documented  
@Configuration  
@Indexed  
public @interface SpringBootConfiguration {  
    @AliasFor(  
        annotation = Configuration.class  
    )  
    boolean proxyBeanMethods() default true;  
}
3. @EnableAutoConfiguration

Spring 自动配置的核心注解,下面详细讲解

4. @ComponentScan
  • 可以通过 basePackageClassesbasePackages 来定义需要扫描的特定包
  • 如果没有定义特定的包,将从声明该注解的类的包开始扫描,这也是为什么 SpringBoot 项目声明的注解类必须要在启动类的目录下
  • excludeFilters 自定义过滤器,通常用于排除一些类,注解等

@EnableAutoConfiguration 详解

我们看其注解的实现:

java 复制代码
@Target({ElementType.TYPE})  
@Retention(RetentionPolicy.RUNTIME)  
@Documented  
@Inherited  
@AutoConfigurationPackage  
@Import({AutoConfigurationImportSelector.class})  
public @interface EnableAutoConfiguration {  
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";  
  
    Class<?>[] exclude() default {};  
  
    String[] excludeName() default {};  
}

这个注解包含两部分:

  1. @Import({AutoConfigurationImportSelector.class})
  2. @AutoConfigurationPackage
@Import ({AutoConfigurationImportSelector. class})

使用 @Import 注解,导入了实现 ImportSelector 接口的实现类

java 复制代码
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {  
  
    public String[] selectImports(AnnotationMetadata annotationMetadata) {  
        if (!this.isEnabled(annotationMetadata)) {  
            return NO_IMPORTS;  
        } else {  
            // 获取自动配置的配置类信息
            AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);  
            return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());  
        }  
    }
  • selectImport() 方法底层调用 getAutoConfigurationEntry() 方法,获取可自动配置的配置类信息集合

点进去:

java 复制代码
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {  
    if (!this.isEnabled(annotationMetadata)) {  
        return EMPTY_ENTRY;  
    } else {  
        AnnotationAttributes attributes = this.getAttributes(annotationMetadata);  
        List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);  
        configurations = this.<String>removeDuplicates(configurations);  
        Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);  
        this.checkExcludedClasses(configurations, exclusions);  
        configurations.removeAll(exclusions);  
        configurations = this.getConfigurationClassFilter().filter(configurations);  
        this.fireAutoConfigurationImportEvents(configurations, exclusions);  
        return new AutoConfigurationEntry(configurations, exclusions);  
    }  
}

getAutoConfigurationEntry() 方法通过调用 getCandidateConfigurations(annotationMetadata, attributes) 方法获取在配置文件中配置的所有自动配置类的集合

点进去:

java 复制代码
//获取所有基于
// META-INF/spring/aot.factories文件
// META-INF/spring.factories
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {  
    ImportCandidates importCandidates = ImportCandidates.load(this.autoConfigurationAnnotation,  
          getBeanClassLoader());  
    List<String> configurations = importCandidates.getCandidates();  
    Assert.state(!CollectionUtils.isEmpty(configurations),  
          "No auto configuration classes found in " + "META-INF/spring/"  
                + this.autoConfigurationAnnotation.getName() + ".imports. If you "  
                + "are using a custom packaging, make sure that file is correct.");  
    return configurations;  
}
  • getCandidateConfigurations 方法的功能:
    • 获取所有基于 META-INF/spring/aot.factories 文件,META-INF/spring.factories 文件中配置类的集合

在引入的起步依赖中,通常都有包含以上两个文件

  • 这里面包含了很多第三方依赖的配置文件(连续按两下 shift 可以查看对应的源码)
    1. 在加载自动配置类的时候,并不是将所有的配置全部加载进来,而是通过 @Conditional 等注解的判断进行动态加载。
      @ConditionalSpring 底层注解,意思就是根据不同的条件,来进行自己不同的条件判断,如果满足指定的条件,那么配置类里边的配置才会生效

    2. META-INF/spring/aot.factories 文件是 Spring 内部提供的一个约定俗成的加载方式,只需要在模块的 META-INF/spring/aot.factories 文件中配置即可,Spring 就会把响应的实现类注入到 Spring 容器中

:会加载所有 jar 包下的 classpath 路径下的 META-INF/spring/aot.factories 文件,这样文件不止一个

  • 比如 redis 的配置:RedisAutoConfiguration
  • 可以看到,配置文件中使用 @Bean 声明了一些对象,Spring 就会自动调用配置类中使用 @Bean 标识的方法,并把对象注册到 Spring IoC 容器中
  • 在加载自动配置类的时候,并不是将所有的配置全部加载进来,而是通过 @Conditional 等注解的判断进行动态加载
  • @ConditionalSpring 底层注解,意思就是会根据不同的条件,来进行自己不同的条件判断,如果满足指定的条件,那么配置类里面的配置才会生效
@AutoConfigurationPacket

源码如下:

java 复制代码
@Target({ElementType.TYPE})  
@Retention(RetentionPolicy.RUNTIME)  
@Documented  
@Inherited  
@Import({AutoConfigurationPackages.Registrar.class})  
public @interface AutoConfigurationPackage {  
    String[] basePackages() default {};  
  
    Class<?>[] basePackageClasses() default {};  
}

这个注解主要是导入了一个配置文件 AutoConfigurationPackages.Registrar.class

java 复制代码
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {  
    Registrar() {  
    }  
  
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {  
        AutoConfigurationPackages.register(registry, (String[])(new PackageImports(metadata)).getPackageNames().toArray(new String[0]));  
    }  
  
    public Set<Object> determineImports(AnnotationMetadata metadata) {  
        return Collections.singleton(new PackageImports(metadata));  
    }  
}
  • 这个类实现了 ImportBeanDefinitionRegistrar 类,就可以被注解 @Import 注解导入到 Spring 容器里
  • (String[])(new PackageImports(metadata)).getPackageNames().toArray(new String[0]):当前启动类所在的包名

所以:@AutoConfigurationPacket 就是将启动类所在的包下面的所有组件都扫描注册到 Spring 容器中

总结

SpringBoot 自动配置原理的大概流程如下:

SpringBoot 程序启动时,会加载配置文件当中所定义的配置类,通过 @Import 注解将这些配置类全部加载到 SpringIoC 容器中,交给 IoC 容器管理

总结

  1. Bean 的作用域共分为 6 中:singletonprototyperequestsessionapplicationwebsocket
  2. Bean 的生命周期共分为 5 大部分:实例化、属性复制、初始化、使用和销毁
  3. SpringBoot 的自动配置原理源码口是:@SpringBootApplication 注解,这个注解封装了 3 个注解
    • @SpringBootConfiguration 标志当前类为配置类
    • @ComponentScan 进行包扫描(默认扫描的是启动类所在的当前包及其子包)
    • @EnableAutoConfiguration
      • @Import:读取当前项目下所有依赖 jar 包中 META-INF/spring.facctoriesMETA-INF/spring/aot.factories 两个文件里面定义的配置类(配置类中定义了 @Bean 注解标识的方法)
      • @AutoConfigurationPacket:把启动类所在包下面所有的组件都注入到 Spring 容器中
相关推荐
Java&Develop18 分钟前
Java中给List<T> 对象集合去重
java·开发语言
poemyang19 分钟前
“代码跑着跑着,就变快了?”——揭秘Java性能幕后引擎:即时编译器
java·java虚拟机·编译原理·jit·即时编译器
都叫我大帅哥21 分钟前
全面深入解析Hystrix:Java分布式系统的"防弹衣" 🛡️
java·spring boot·spring cloud
idolyXyz42 分钟前
[spring-cloud: 服务发现]-源码解析
spring·spring cloud
杨DaB1 小时前
【项目实践】在系统接入天气api,根据当前天气提醒,做好plan
java·后端·spring·ajax·json·mvc
liweiweili1264 小时前
Tomcat 服务器日志
java·运维·服务器·tomcat
LZQqqqqo5 小时前
C# 中生成随机数的常用方法
java·算法·c#
葵续浅笑5 小时前
LeetCode - 合并两个有序链表 / 删除链表的倒数第 N 个结点
java·算法·leetcode
2301_793086876 小时前
Springboot 04 starter
java·spring boot·后端