【SpringBoot】自动装配

202 阅读3分钟

什么是自动装配

这里的自动,指的是无需显示配置,装配则是指将第三方包中的类装配成Java Bean,最常见的使用场景就是我们引入的各种starter。

在Spring出现的很长一段时间,如果要将一个三方包中的类装配成一个Java Bean,我们需要一个一个显示进行装配 。

假设我们的工程三方包中有一个Person类,我们将其装配成一个Java Bean的代码大致如下。

  • Person.java
public class Person {
    private String name;
    
    private Integer age;
}

  • MyConfig.java
@Configuration
public class MyConfig {
    @Bean
    public Person person() {
        Person person = new Person();
        return person;
    }
}
  • xml文件
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context/ http://www.springframework.org/schema/context/spring-context.xsd
    http://www.springframework.org/schema/mvc/ http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <context:component-scan base-package="xxx" />
    <mvc:annotation-driven />

    <!-- JSON Support -->
    <bean name="person" class="com.cy.service.Person"/>

</beans>

而在我们平时使用的Spring Boot框架中,只需要引入第三方包提供的starter,就能完成相关类的自动装配。

我们以分页插件PageHelper为例,在gradle(或maven)中引入相关starter,就能完成相关类的Java Bean装配。

  • build.gradl
implementation "com.github.pagehelper:pagehelper-spring-boot-starter:$pagehelperVersion"

自动装配原理

  • SpringBoot 工程入口
@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}
  • PageHelper jar包 image.png

  • PageHelperAutoConfiguration image.png

自动装配的关键在于注解@SpringBootApplication 与三方包中的文件 spring.factories

@SpringBootApplication 是SpringBoot提供的一个核心注解,简化了在web项目开发中的诸多配置。

image.png

 @SpringBootApplication由多个注解组成,其中最重要的三个是 @SpringBootConfiguration@EnableAutoConfiguration@ComponentScan 。

  • @EnableAutoConfiguration:启用 SpringBoot 的自动配置机制

  • @SpringBootConfiguration:允许在上下文中注册额外的 bean 或导入其他配置类

  • @ComponentScan: 扫描被@Component (@Service,@Controller)注解的 bean,注解默认会扫描启动类所在的包下所有的类 ,可以自定义不扫描某些 bean。

@EnableAutoConfiguration是实现自动配置的核心注解,AutoConfigurationImportSelector是该注解的重要切面。

AutoConfigurationImportSelector实现了一个重要方法selectImports,该方法主要用于获取所有符合条件的类的全限定类名,这些类需要被加载到 IoC 容器中。

image.png

image.png

image.png

这是源码中的一条核心调用链路:selectImports --> getAutoConfigurationEntry --> getCandidateConfigurations.

从Assert的提示中,我们已经可以看出这段代码必然和 spring.factories文件有着某种关联。

image.png

image.png

沿着 loadFactoryNames 往下走,我们最终会看到这么一行代码classLoader.getResources(FACTORIES_RESOURCE_LOCATION),这里常量FACTORIES_RESOURCE_LOCATION的值为 META-INF/spring.factories.

自此,我们从@SpringBootApplication注解出发,最终找到了加载spring.factories的相关代码。

稍微总结一下:

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

自定义starter

了解完自动装配的基本原理,我们可以尝试自己来构建一个starter。

第一步:创建配置类

@Configuration
public class CustomHttpClientConfig {

    @Bean
    @ConditionalOnProperty(prefix = "custom",name = "createHttpClient",value = "true")
    public Object specialHttpClient() {
        OkHttpClient okHttpClient = new OkHttpClient();
        return okHttpClient;
    }

}

第二步:工程的resources 包下创建META-INF/spring.factories

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
xxxx.CustomHttpClientConfig

第三步:打包,此处省略打包命令.......

第四步:在另一个SpringBoot工程中引入该包,当 application.propertiesapplication.yml中custom.createHttpClient为true时,会进行自动装配

拓展阅读(getAutoConfigurationEntry简析)

image.png

  1. 115行,获取EnableAutoConfiguration注解中的 exclude 和 excludeName
  2. 116行,读取META-INF/spring.factories,获取需要自动装配的所有配置类
  3. 117行--122行,根据条件注解,将不需要加载的类过滤掉(各种@ConditionalOnXXX注解)

常见条件注解:

  • @ConditionalOnBean:当容器里有指定 Bean 的条件下
  • @ConditionalOnMissingBean:当容器里没有指定 Bean 的情况下
  • @ConditionalOnSingleCandidate:当指定 Bean 在容器中只有一个,或者虽然有多个但是指定首选 Bean
  • @ConditionalOnClass:当类路径下有指定类的条件下
  • @ConditionalOnMissingClass:当类路径下没有指定类的条件下
  • @ConditionalOnProperty:指定的属性是否有指定的值
  • @ConditionalOnResource:类路径是否有指定的值
  • @ConditionalOnExpression:基于 SpEL 表达式作为判断条件
  • @ConditionalOnJava:基于 Java 版本作为判断条件

参考文章

聊聊 SpringBoot 自动装配原理