Spring Boot 自动配置

目录

什么是自动配置?

[Spring 加载 Bean](#Spring 加载 Bean)

@ComponentScan

@Import

导入类

[导入 ImportSelector 接口的实现类](#导入 ImportSelector 接口的实现类)

[SpringBoot 原理分析](#SpringBoot 原理分析)

@EnableAutoConfiguration

@Import(AutoConfigurationImportSelector.class)?

AutoConfigurationPackage

[SpringBoot 自动配置流程](#SpringBoot 自动配置流程)


什么是自动配置?

Spring Boot 的自动配置 :当 Spring 容器启动后,一些配置类、bean 对象 等就自动存入 Ioc 容器中,而不再需要我们手动去声明,从而简化了程序开发过程,省去了繁琐的配置操作

也就是说,Spring Boot 的自动配置,就是 SpinrgBoot依赖 jar 包 中的配置类 以及 Bean 加载到Spring Ioc 容器中的过程

在本篇文章中,我们主要学习一下两个方面:

  1. Spring 如何将对象加载到 Spring Ioc 容器中

  2. SpringBoot 是如何进行实现的

我们首先来看 Spring 是如何加载 Bean 的

Spring 加载 Bean

当我们在项目中引入第三方的包时,其实就是在该项目下引入第三方的代码,我们通过在该项目下创建不同的目录来模拟第三方代码的引入:

当前项目目录为 com.example.springautoconfig ,模拟第三方代码文件在 com.example.autoconfig 目录下

第三方文件代码:

复制代码
@Component
public class AutoConfig {
    public void test() {
        System.out.println("test...");
    }
}

获取 AutoConfig:

复制代码
@SpringBootTest
class SpringAutoconfigApplicationTests {
    @Autowired
    private ApplicationContext context;
    @Test
    void contextLoads() {
        AutoConfig bean = context.getBean(AutoConfig.class);
        System.out.println(bean);
    }
}

运行结果:

此时显示没有com.example.autoconfig.AutoConfig 这个类型的 bean

为什么会报错呢?

Spring 使用 类注解(@Controller、@Service、@Repository、@Component、@Configuration) 和 @Bean 注解 帮助我们将 Bean 加载到 Spring Ioc 容器中时,有一个前提**:这些注解需要和 SpringBoot 启动类(@SpringBootApplication 标注的类)在同一个目录下**

而在上述项目中,启动类所在目录为com.example.springautoconfig,而 AutoConfig 类位于com.example.autoconfig 目录下,因此,SpringBoot 并没有扫描到

可是,当我们引入第三方的 jar 包时,第三方的 jar 代码目录也不在启动类的目录下,那么,如何让 Spring 帮我们管理这些 Bean 的呢?

我们可以通过指定路径或引入的文件告诉 Spring,让 Spring 进行扫描

常见的实现方法有两种:

  1. @ComponentScan 组件扫描

  2. @Import 导入

@ComponentScan

使用**@ComponentScan** 注解,指定 Spring 扫描路径:

复制代码
@SpringBootApplication
@ComponentScan("com.example.autoconfig")
public class SpringAutoconfigApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringAutoconfigApplication.class, args);
    }
}

运行程序并观察结果:

成功获取到AutoConfig bean

那么,Spring Boot 是否使用了这种方式呢?

显然没有。若 Spring Boot 采用这种方式,当我们引入大量的第三方依赖,如 MyBatis、jackson 等时,就需要在启动类上配置不同依赖需要扫描的包,非常繁琐

@Import

@Import 导入主要有以下几种形式:

  1. 导入类

  2. 导入 ImportSelector 接口的实现类

导入类
复制代码
@Import(AutoConfig.class)
@SpringBootApplication
public class SpringAutoconfigApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringAutoconfigApplication.class, args);
    }

}

运行程序,观察结果:

成功获取到 AutoConfig Bean

若在文件中有多个配置项:

复制代码
@Component
public class AutoConfig2 {
    public void test() {
        System.out.println("test...");
    }
}

此时就需要导入多个类:

复制代码
@Import({AutoConfig.class, AutoConfig2.class})
@SpringBootApplication
public class SpringAutoconfigApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringAutoconfigApplication.class, args);
    }

}

显而易见,这种方式也比较繁琐,因此,Spring Boot 也没有采用

导入 ImportSelector 接口的实现类

实现 ImportSelector 接口:

复制代码
public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        // 返回需要导入的全限定类名
        return new String[]{"com.example.autoconfig.AutoConfig", "com.example.autoconfig.AutoConfig2"};
    }
}

导入:

复制代码
@Import(MyImportSelector.class)
@SpringBootApplication
public class SpringAutoconfigApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringAutoconfigApplication.class, args);
    }

}

运行程序,并观察结果:

这种方式也可以导入第三方依赖提供的 Bean

但是,它们都有一个明显的问题:使用者需要知道第三方依赖中有哪些 Bean 对象或配置类,若我们在导入过程中漏掉了一些 Bean,就可能会导致我们的项目出现问题

依赖中有哪些 Bean,使用时需要配置哪些 Bean,这些问题第三方依赖最为清楚,那么,能否由第三方依赖来做这些事情呢?

比较常见的方法是第三方依赖提供一个注解 ,而这个注解一般是以 @EnableXxx 开头的注解,而注解中封装的就是 @Import 注解

复制代码
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
// 指定要导入哪些类
@Import(MyImportSelector.class)
public @interface EnableAutoConfig {
}

在启动类上使用第三方提供的注解:

复制代码
@EnableAutoConfig
@SpringBootApplication
public class SpringAutoconfigApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringAutoconfigApplication.class, args);
    }

}

运行并观察结果:

可以看到,这种方式也可以导入第三方依赖提供的 Bean,且这种方式不需要使用方知道第三方依赖中有哪些 Bean 对象或配置类,而在 Spring Boot 中也是使用的这种方式

SpringBoot 原理分析

SpringBoot 是如何进行实现的?让我们从 SpringBoot 的启动类开始看:

由@SpringBootApplication 标注的类就是 SpringBoot 项目的启动类:

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

@SpringBootApplication是一个组合注解,注解中包含了:

1. 元注解:

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

分别为:

@Retention:描述注解保留的时间范围

@Target:描述注解的使用范围

@Documented :描述在使用 javadoc 工具为类生成帮助文档时是否保留其注解信息

@Inherited:使被其修饰的注解具有继承性(若某个类使用了 @Inherited,则其子类将自动具有该注解)

2.@SpringBootConfiguration:

标识当前类是一个配置类,里面其实就是@Configuration,只是做了进一步的封装

其中,@Indexed 注解是用来加速应用启动的

3.@EnableAutoConfiguration(开启自动配置)

是 spiring 自动配置的核心注解,我们在后续详细理解

4. ComponentScan(包扫描)

可以通过 basePackageClassesbasePackages 来定义要扫描的特定包,若没有定义特定的包,将从声明该注解的类的包开始扫描,这也是 SpringBoot 项目声明的注解类为什么必须在启动类目录下

也可以自定义过滤器,用于排查一些类、注解等

接下来,我们重点来看**@EnableAutoConfiguration(开启自动配置)**

@EnableAutoConfiguration

@EnableAutoConfiguration 中主要包含两部分:

@Import(AutoConfigurationImportSelector.class)

@AutoConfigurationPackage

我们先来看@Import(AutoConfigurationImportSelector.class)

@Import(AutoConfigurationImportSelector.class)

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

selectImports()方法中,调用了getAutoConfigurationEntry() 方法,获取可自动配置的配置类信息

我们继续看getAutoConfigurationEntry() 方法:

getAutoConfigurationEntry() 方法中,主要通过getCandidateConfigurations(annotationMetadata, attributes) 方法 和 fireAutoConfigurationImportEvents(configurations, exclusions) 方法,获取在配置文件中配置的所有自动配置类的集合

我们先看getCandidateConfigurations(annotationMetadata, attributes) 方法:

getCandidateConfigurations(annotationMetadata, attributes) 方法中,使用ImportCandidates 进行加载

那么,从哪里进行加载呢?

断言 的错误信息中我们可以找到答案:META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

也就是说,getCandidateConfigurations 方法会获取所有基于META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中配置类的集合

META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件:

其中包含了很多第三方依赖的配置文件

我们以 redis 为例:

查看RedisAutoConfiguration

可以看到,在使用 redis 时常用的RedisTemplateStringRedisTemplate 就位于其中,在我们需要使用时直接使用 @Autowired 进行注入就可以了

但是,由于当前项目并没有引入 redis 相关 jar 包,此时这个类并不能被加载

也就是说在加载自动配置类 的时候,并不是将所有的配置全部加载进来 ,而是会先进行判断 ,根据**@ConditionalOnMissingBean** 、@ConditionalOnSingleCandidate 等注解的判断进行动态加载

即,在配置文件 中使用 @Bean 声明对象,spring 会自动调用配置类中使用 @Bean 标识的方法,并将对象存放到 Spring Ioc 中,但是,在加载自动配置类的时候,并不是将所有的配置全部加载进来,而是会通过 @Conditional 等注解的判断进行动态加载@Conditional是 spring 底层注解,会根据不同的条件,进行条件判断,若满足指定条件,配置类中的配置才会生效)

我们继续看fireAutoConfigurationImportEvents 方法,找到是从哪里获取配置类的:

可以看到,fireAutoConfigurationImportEvents 方法最终会从META-INF/spring.factories 中获取配置类的集合

我们来看META-INF/spring.factories

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

例如,有一个自动配置类,希望SpringBoot 在启动时自动加载这个配置,我们就可以在META-INF/spring.factories 文件按照指定格式进行配置,让SpringBoot 应用启动时,读取这个 spring.factories 文件,根据文件中指定的配置类来进行自动配置

spring 会加载所有 jar 包下的META-INF/spring.factories 文件

META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.importsMETA-INF/spring.factories 都在引入的起步依赖中:

我们继续看AutoConfigurationPackage

AutoConfigurationPackage

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

我们继续看Registrar

Registrar 实现了ImportBeanDefinitionRegistrar 接口,可以被 @Import 导入到 spring 容器中

new PackageImports(metadata).getPackageNames().toArray(new String[0]): 当前启动类所在的包名

也就是说,@AutoConfigurationPackage 的作用是将启动类所在的包下面所有的组件都扫描注册到 spring 容器中

最后,我们来总结一下SpringBoot 自动配置的流程

SpringBoot 自动配置流程

SpringBoot 的自动配置的入口是**@SpringBootApplication** 注解,在这个注解中,主要封装了 3 个注解:

@SpringBootConfiguration:标识当前类是配置类

@ComponentScan(包扫描):可以通过 basePackageClassesbasePackages 定义要扫描的特定包,若没有定义特定的包,将从声明该注解的类的包开始扫描 ,也就是说,默认情况下扫描的是启动类所在的当前包以及子包

**@EnableAutoConfiguration(开启自动配置):**主要包含两部分:

@Import(AutoConfigurationImportSelector.class) :读取META-INF/spring.factoriesMETA-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中定义的配置类

**@AutoConfigurationPackage:**将启动类所在的包下所有组件都注入到 Spring 容器中

相关推荐
码农周15 小时前
告别大体积PDF!基于PDFBox的Java压缩工具
java·spring boot
devilnumber15 小时前
java中Redisson ,jedis,Lettuce和Spring Data Redis的四种深度对比和优缺点详解
java·redis·spring
摇滚侠15 小时前
Java 进阶教程,全面剖析 Java 多线程编程
java·开发语言
yaaakaaang15 小时前
十四、命令模式
java·命令模式
小锋java123415 小时前
【技术专题】Matplotlib3 Python 数据可视化 - Matplotlib3 绘制饼状图(Pie)
java
wuminyu15 小时前
专家视角看JVM_StartThread
java·linux·c语言·jvm·c++
吕永强15 小时前
基于SpringBoot+Vue小区报修系统的设计与实现(源码+论文+部署)
spring boot·毕业设计·毕业论文·报修系统·小区报修
awljwlj15 小时前
黑马点评复习—缓存相关【包含可能的问题和基础知识复习】
java·后端·spring·缓存
Gofarlic_OMS15 小时前
ENOVIA基于Token的许可证消费模式分析与分点策略
java·大数据·开发语言·人工智能·制造
XY_墨莲伊15 小时前
【实战项目】基于B/S结构Flask+Folium技术的出租车轨迹可视化分析系统(文末含完整源代码)
开发语言·后端·python·算法·机器学习·flask