关于 Spring Boot 自动装配你知道多少?

Posted 流楚丶格念

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于 Spring Boot 自动装配你知道多少?相关的知识,希望对你有一定的参考价值。

文章目录

什么是springboot自动装配?

自动装配是springboot的核心,一般提到自动装配就会和springboot联系在一起。实际上 Spring Framework 早就实现了这个功能。Spring Boot 只是在其基础上,通过 SPI 的方式,做了进一步优化。

SpringBoot 定义了一套接口规范,这套规范规定:SpringBoot 在启动时会扫描外部引用 jar 包中的META-INF/spring.factories文件,将文件中配置的类型信息加载到 Spring 容器,并执行类中定义的各种操作。对于外部 jar 来说,只需要按照 SpringBoot 定义的标准,就能将自己的功能装置进 SpringBoot。

没有 Spring Boot 的情况下,如果我们需要引入第三方依赖,需要手动配置,非常麻烦。但是,Spring Boot 中,我们直接引入一个 starter 即可。比如你想要在项目中使用 redis 的话,直接在项目中引入对应的 starter 即可。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

引入 starter 之后,我们通过少量注解和一些简单的配置就能使用第三方组件提供的功能了。

总的来说:使用Spring Boot时,我们只需引入对应的Starters,Spring Boot启动时便会自动加载相关依赖,配置相应的初始化参数,以最快捷、简单的形式对第三方软件进行集成,这便是Spring Boot的自动配置功能

Spring Boot自动装配的过程

我们拿springboot 2.7.2.RELEASE 举例分析源码

启动类的@SpringBootApplication注解由@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan三个注解组成,三个注解共同完成自动装配;

  • @SpringBootConfiguration 注解标记启动类为配置类
  • @ComponentScan 注解实现启动时扫描启动类所在的包以及子包下所有标记为bean的类由IOC容器注册为bean
  • @EnableAutoConfiguration 通过 @Import 注解导入 AutoConfigurationImportSelector类,然后通过AutoConfigurationImportSelector 类的 selectImports 方法去读取需要被自动装配的组件依赖下的spring.factories文件配置的组件的类全名,并按照一定的规则过滤掉不符合要求的组件的类全名,将剩余读取到的各个组件的类全名集合返回给IOC容器并将这些组件注册为bean

下面来详细看一下源码:

一个Springboot项目暴露在启动类上的只有一个注解了:

我们追踪进去可以看到内部其实调用了很多注解:

源码如下所示:

@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 

	@AliasFor(annotation = EnableAutoConfiguration.class)
	Class<?>[] exclude() default ;

	@AliasFor(annotation = EnableAutoConfiguration.class)
	String[] excludeName() default ;

	@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
	String[] scanBasePackages() default ;

	@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
	Class<?>[] scanBasePackageClasses() default ;

	@AliasFor(annotation = Configuration.class)
	boolean proxyBeanMethods() default true;



可以看出所以说@SpringBootApplication是一个复合注解,除了一些元注解外:

元注解:其主要作用就是负责注解其他注解,为其他注解提供了相关的解释说明
@Target(ElementType.TYPE):用来限制注解的使用范围
@Retention(RetentionPolicy.RUNTIME)):指定其所修饰的注解的保留策略
@Documented:一个标记注解,用于指示一个注解将被文档化
@Inherited:使父类的注解能被其子类继承

@SpringBootApplication最主要的是由三个注解组成的,

  • @ComponentScan注解则是spring原生注解,其作用是扫描启动类所在的包以及子包所有Bean组件并注册到IOC容器中

  • @SpringBootConfiguration注解:我们分析其源码,可以看出该注解内部就是@Configuration注解,用于标记springboot启动类为一个配置类

  • @EnableAutoConfiguration注解:我们分析其源码,可以看到该注解导通过@lmport注解入了AutoConfigurationlmportSelector类,它是实现自动装配的核心功能类:

@SpringBootApplication注解由三个注解共同完成自动装配,各个注解作用如下

  • @SpringBootConfiguration: 标记启动类为一个spring配置类

  • @EnableAutoConfiguration: 实现自动装配的核心注解(重头戏)

  • @ComponentScan: 扫描启动类所在的包以及子包下所有标记为Bean的组件并注册到IOC容器中。

    标记为Bean:比如扫描包下的类中添加了@Component (@Service,@Controller,@Repostory,@RestController)注解的类

自动装配源码分析

刚才我们是做了一个大致了解,下面我们来详细看一下这三个注解的详细说明:

@ComponentScan

@EnableAutoConfiguration

@EnableAutoConfiguration注解是实现自动装配的核心注解,上面看了他的源码是通过@Import注解导入AutoConfigurationImportSelector类,那我们来看看AutoConfigurationImportSelector源码:


再往上追踪父类:

我们可以看到,其实AutoConfigurationImportSelectorImportSelector的一个子类,@EnableAutoConfiguration@Import注解的一大用处就是导入一个ImportSelector类,通过selectImports方法获取所有符合条件的类的全限定类名,然后将这些类需要被加载到 IoC 容器

到这里也就是说:自动装配核心功能的实际是通过 AutoConfigurationImportSelector类实现的

那么下面我们再来看看AutoConfigurationImportSelector类的selectImports方法,源码如下:

public String[] selectImports(AnnotationMetadata annotationMetadata) 
	// 判断自动装配开关是否打开
    if (!this.isEnabled(annotationMetadata)) 
        return NO_IMPORTS;
     else 
    	// 获取所有符合条件的类的全限定类名(即获取所有需要装配的bean)
        AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    

首先是判断自动装配开关是否打开,如果打开了就去获取所有需要装配的bean。

这里我们需要重点关注一下getAutoConfigurationEntry()方法,这个方法主要负责加载自动配置类的

我们来看一下它的源码:

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) 
    //第1步:判断自动装配开关是否打开
    if (!isEnabled(annotationMetadata)) 
        return EMPTY_ENTRY;
     else 
        //第2步:用于获取注解中的exclude和excludeName。
        //获取注解属性
        AnnotationAttributes attributes = getAttributes(annotationMetadata);
        //第3步:获取需要自动装配的所有配置类,读取META-INF/spring.factories
        //读取所有预配置类
        List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
        //第4步:符合条件加载
        //去掉重复的配置类
        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);
    

我们在这打个断点调试一下,看下一具体运行的细节

第 1 步:

判断自动装配开关是否打开。默认spring.boot.enableautoconfiguration=true,可在 application.propertiesapplication.yml 中设置


第 2 步 :

用于获取EnableAutoConfiguration注解中的 exclude 和 excludeName。

exclude和excludeName用于 排除不需要配置的类,但是它俩只能排除auto-configuration类型的类,没法去排除自定义的类

第 3 步

获取需要自动装配的所有配置类,读取META-INF/spring.factories

关键来了,我们可以先运行完这一步,发现他加载了所有要获取的Bean的组件


再看一下这一步的具体流程:

先进入 getCandidateConfigurations() 方法中,调用SpringFactoriesLoader的loadFactoryNames方法,入参为EnableAutoConfiguration类和ClassLoader类加载器

进入 loadFactoryNames() 方法中,首先获取EnableAutoConfiguration类全名,调用该方法返回一个Map并获取key为类全名value(即需要被装配的类的类全名数组集合)

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) 
    ClassLoader classLoaderToUse = classLoader;
    if (classLoader == null) 
        classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
    
    // 首先获取EnableAutoConfiguration类全名
    String factoryTypeName = factoryType.getName();
    // 调用该方法返回一个Map并获取key为类全名value(即需要被装配的类的类全名数组集合)
    return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());

再进入 loadSpringFactories() 方法中:

可以看到这个方法就会读取META-INF/spring.factories下的类了,执行完这个方法,我们可以看到这个文件的配置内容都被我们读取到了,如下图所示。

不光是这个依赖下的META-INF/spring.factories被读取到,所有 Spring Boot Starter 下的META-INF/spring.factories都会被读取到

第 4 步 :

到这里可能面试官会问你:“spring.factories中这么多配置,每次启动都要全部加载么?”。

很明显,这是不现实的。我们 debug 到后面你会发现,configurations 的值变小了。

因为,这一步有经历了一遍筛选过滤,@ConditionalOnXXX 中的所有条件都满足,该类才会生效。

小总结

Spring Boot实现该运作机制所涉及的核心部分如下图所示:


整个自动装配的过程是:Spring Boot通过@EnableAutoConfiguration注解开启自动配置,加载spring.factories中注册的各种AutoConfiguration类,当某个AutoConfiguration类满足其注解@Conditional指定的生效条件(Starters提供的依赖、配置或Spring容器中是否存在某个Bean等)时,实例化该AutoConfiguration类中定义的Bean(组件等),并注入Spring容器,就可以完成依赖框架的自动配置。

细节问题

为什么不使用 @Import 直接引入自动配置类

有两个原因:

  1. 让主配置类和自动配置类变成了强耦合,主配置类不应该知道有哪些从属配置
  2. 直接用 @Import(自动配置类.class),引入的配置解析优先级较高,自动配置类的解析应该在主配置没提供时作为默认配置

因此,采用了 @Import(AutoConfigurationImportSelector.class)

  • AutoConfigurationImportSelector.class 去读取 META-INF/spring.factories 中的自动配置类,实现了弱耦合。
  • 另外 AutoConfigurationImportSelector.class 实现了 DeferredImportSelector 接口,让自动配置的解析晚于主配置的解析

以上是关于关于 Spring Boot 自动装配你知道多少?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 DataSource 不能在 Spring Boot 应用程序中自动装配?

Spring boot 自动装配

Spring boot 自动装配

Spring Boot 自动装配定义与自定义starter原理,及如何实现自定义装配

Spring Boot 自动装配定义与自定义starter原理,及如何实现自定义装配

Spring Boot核心特性之组件自动装配