Spring Boot:基本应用和源码解析

SpringBoot 基本应用

约定优于配置

约定优于配置(Convention over Configuration),又称按约定编程,是一种软件设计范式。

本质上是说,系统、类库或框架应该假定合理的默认值,而非要求提供不必要的配置。比如说模型中有一个名为 User 的类,那么数据库中对应的表就会默认命名为 User。只有在偏离这一个约定的时候,例如想要将该表命名为 person,才需要写有关这个名字的配置。

比如平时架构师搭建项目就是限制软件开发随便写代码,制定出一套规范,让开发人员按统一的要求进行开发编码测试之类的,这样就加强了开发效率与审查代码效率。所以说写代码的时候就需要按要求命名,这样统一规范的代码就有良好的可读性与维护性了。

约定优于配置简单来理解,就是遵循约定。

Spring Boot 是所有基于 Spring 开发的项目的起点。Spring Boot 的设计是为了尽可能快的跑起来 Spring 应用程序并且尽可能减少配置文件。

SpringBoot 概念

Spring 优缺点分析
  • 优点:Spring 是 Java 企业版(Java Enterprise Edition,JEE,也称 J2EE)的轻量级代替品。无需开发重量级的 Enterprise Java Bean - EJB,Spring 为企业级 Java 开发提供了一种相对简单的方法,通过依赖注入和面向切面编程,用简单的 Java 对象(Plain Old Java Object,POJO)实现了 EJB 的功能。

  • 缺点:虽然 Spring 的组件代码是轻量级的,但它的配置却是重量级的。一开始,Spring 用 XML 配置,而且是很多 XML 配 置。Spring 2.5 引入了基于注解的组件扫描,这消除了大量针对应用程序自身组件的显式 XML 配置。Spring 3.0 引入 了基于 Java 的配置,这是一种类型安全的可重构配置方式,可以代替 XML。

所有这些配置都代表了开发时的损耗。因为在思考 Spring 特性配置和解决业务问题之间需要进行思维切换,所以编写配置挤占了编写应用程序逻辑的时间。和所有框架一样,Spring 实用,但与此同时它要求的回报也不少。

除此之外,项目的依赖管理也是一件耗时耗力的事情。在环境搭建时,需要分析要导入哪些库的坐标,而且还需要分析导入与之有依赖关系的其他库的坐标,一旦选错了依赖的版本,随之而来的不兼容问题就会严重阻碍项目的开发进度。SSM 整合:Spring、Spring MVC、Mybatis、Spring-Mybatis 整合包、数据库驱动,引入依赖的数量繁多、容易存在版本冲突。

Spring Boot 解决上述 Spring 的问题

SpringBoot 对上述 Spring 的缺点进行的改善和优化,基于约定优于配置的思想,可以让开发人员不必在配置与逻辑业务之间进行思维的切换,全身心的投入到逻辑业务的代码编写中,从而大大提高了开发的效率,一定程度上缩短了项目周期。

  • 起步依赖

起步依赖本质上是一个 Maven 项目对象模型 (Project Object Model,POM),定义了对其他库的传递依赖,这些东西加在一起即支持某项功能。
简单的说,起步依赖就是将具备某种功能的依赖坐标打包到一起,并提供一些默认的功能。

  • 自动配置

springboot 的自动配置,指的是 springboot 会自动将一些配置类的 bean 注册进 ioc 容器,在需要的地方使用 @autowired 或者 @resource 等注解来使用它。
“自动”的表现形式就是只需要引想用功能的包,相关的配置完全不用管,springboot 会自动注入这些配置 bean,直接使用这些 bean 即可。

Springboot 可以简单、快速、方便地搭建项目;对主流开发框架的无配置集成;极大提高了开发、部署效率。

SpringBoot 入门案例

案例需求:请求 Controller 中的方法,并将返回值响应到页面。
1. 依赖管理

pom.xml

<!--
    所用的 springBoot 项目都会直接或者间接的继承 spring-boot-starter-parent
    1.指定项目的编码格式为 UTF-8
    2.指定 JDK 版本为 1.8
    3.对项目依赖的版本进行管理,当前项目再引入其他常用的依赖时就需要再指定版本号,避免版本冲突的问题
    4.默认的资源过滤和插件管理
-->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.2.RELEASE</version>
</parent>

<dependencies>
    <!-- 引入 Spring Web 及 Spring MVC 相关的依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

<!-- 可以将 project 打包为一个可以执行的 jar -->
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

2. 启动类
com.zm.SpringBootDemo1Application

/**
 * SpringBoot 的启动类通常放在二级包中,
 * 比如:com.zm.SpringBootDemo1Application。
 * 因为 SpringBoot 项目在做包扫描,
 * 会扫描启动类所在的包及其子包下的所有内容。
 *
 * @author ZM
 * @since 2021-10-28 23:11
 */
@SpringBootApplication // 标识当前类为 SpringBoot 项目的启动类
public class SpringBootDemo1Application {

    public static void main(String[] args) {
        // 样板代码
        SpringApplication.run(SpringBootDemo1Application.class, args);
    }

}

3. Controller
com.zm.controller.HelloController

@RestController
@RequestMapping("/hello")
public class HelloController {

    @RequestMapping("/boot")
    public String helloSpringBoot() {
        return "Hello Spring Boot";
    }

}

SpringBoot 快速构建

案例需求:请求 Controller 中的方法,并将返回值响应到页面。

1. 使用 Spring Initializr 方式构建 Spring Boot 项目

本质上说,Spring Initializr 是一个 Web 应用,它提供了一个基本的项目结构,能够快速构建一个基础的 Spring Boot 项目。

注意使用快速方式创建 Spring Boot 项目时,所在主机须在联网状态下;本质上是在开发工具执行各项参数后,由 Spring 提供的 URL 所对应的服务器生成, IDEA 将服务器生成的 SpringBoot 项目下载到本地的工作空间中。

Project SDK 用于设置创建项目使用的 JDK 版本,这里,使用之前初始化设置好的 JDK 版本即可;在 Choose Initializr Service URL 下使用默认的初始化服务地址 https://start.spring.io 进行 Spring Boot 项目创建。

创建完成后的 pom 文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.zm</groupId>
    <artifactId>springbootdemo2</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springbootdemo2</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>11</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

创建完成后,可以删除自动生成的 .mvn、.gitignore、HELP.md、mvnw、mvnw.cmd。

创建好的 Spring Boot 项目结构如图:

/src/main/java/
       com.zm.Springbootdemo2Application - 项目主程序启动类
/src/main/resources/
     static - 静态资源文件夹
     templates - 模板页面文件夹
     application.properties - 全网配置文件
/src/test/java/
     com.zm.Springbootdemo2ApplicationTests - 项目测试类

使用 Spring Initializr 方式构建的 Spring Boot 项目会默认生成项目启动类、存放前端静态资源和页面的文件夹、编写项目配置的配置文件以及进行项目单元测试的测试类。

2. 创建一个用于 Web 访问的 Controller

注意:确保项目启动类 Springbootdemo2Application 在 com.zm包下。

com.zm.controller.HelloController

@RestController
@RequestMapping("/hello")
public class HelloController {

    @RequestMapping("/boot")
    public String hello() {
        return "What's up! Spring Boot!";
    }

}

3. 运行项目

application.properties 中修改 tomcat 的默认端口号:

server.port=8888

运行主程序启动类 Springbootdemo2Application,项目启动成功后,在控制台上会发现 Spring Boot 项目默认启动的端口号为 8888,此时,可以在浏览器上访问 http://localhost:8888/hello/boot

页面输出的内容是 “What's up! Spring Boot!”,至此,构建 Spring Boot 项目就完成了。

单元测试与热部署

单元测试

开发中,每当完成一个功能接口或业务方法的编写后,通常都会借助单元测试验证该功能是否正确。
1. 添加 spring-boot-starter-test 测试依赖启动器,在项目的 pom.xml 文件中添加 spring-boot-starter-test 测试依赖启动器,示例代码如下 :

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    <exclusions>
        <exclusion>
            <groupId>org.junit.vintage</groupId>
            <artifactId>junit-vintage-engine</artifactId>
        </exclusion>
    </exclusions>
</dependency>

注意:使用 Spring Initializr 方式搭建的 Spring Boot 项目,会自动加入 spring-boot-starter-test 测试依赖启动器,无需再手动添加。

2. 编写单元测试类和测试方法:

/**
 *  SpringJUnit4ClassRunner.class: Spring 运行环境
 *  JUnit4.class: JUnit 运行环境
 *  SpringRunner.class: Spring Boot 运行环境
 */
@RunWith(SpringRunner.class) // @RunWith: 运行器
@SpringBootTest // 标记为当前类为 SpringBoot 测试类,加载项目的 ApplicationContext 上下文环境
class Springbootdemo2ApplicationTests {

    @Autowired
    private HelloController helloController;

    @Test
    void contextLoads() {
        String result = helloController.hello();
        System.out.println(result);
    }

}

上述代码中,先使用 @Autowired 注解注入了 HelloController 实例对象,然后在 contextLoads() 方法中调用了 HelloController 类中对应的请求控制方法 hello(),并输出打印结果。

热部署

在开发过程中,通常会对一段业务代码不断地修改测试,在修改之后往往需要重启服务,有些服务需要加载很久才能启动成功,这种不必要的重复操作极大的降低了程序开发效率。为此,Spring Boot 框架专门提供了进行热部署的依赖启动器,用于进行项目热部署,而无需手动重启项目 。

热部署:在修改完代码之后,不需要重新启动容器,就可以实现更新。

使用步骤:
1)添加 spring-boot-devtools 热部署依赖启动器。

在 Spring Boot 项目进行热部署测试之前,需要先在项目的 pom.xml 文件中添加 spring-boot-devtools 热部署依赖启动器:

<!-- 引入热部署依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
</dependency>

由于使用的是 IDEA 开发工具,添加热部署依赖后可能没有任何效果,接下来还需要针对 IDEA 开发工具进行热部署相关的功能设置。
2)开启 IDEA 的自动编译。

选择 IDEA 工具界面的 【File】->【Settings】 选项,打开 Compiler 面板设置页面。

选择 Build 下的 Compiler 选项,在右侧勾选 “Build project automatically” 选项将项目设置为自动编译,单击 【Apply】->【OK】 按钮保存设置。

3)开启 IDEA 的在项目运行中自动编译的功能。

在项目任意页面中使用组合快捷键 “Ctrl+Shift+Alt+/” 打开 Maintenance 选项框,选中并打开 Registry 页面。

列表中找到 “compiler.automake.allow.when.app.running”,将该选项后的 Value 值勾选,用于指定 IDEA 工具在程序运行过程中自动编译,最后单击 【Close】 按钮完成设置。

热部署效果测试

启动 Springbootdemo2Application http://localhost:8888/hello/boot

页面原始输出的内容是 “What's up! Spring Boot!”。

为了测试配置的热部署是否有效,接下来,在不关闭当前项目的情况下,将 HelloController 类中的请求处理方法 hello() 的返回值修改为 “Hello! Spring Boot!” 并保存,查看控制台信息会发现项目能够自动构建和编译,说明项目热部署生效。

再次刷新浏览器,输出了 “Hello! Spring Boot!”,说明项目热部署配置成功。

全局配置文件

全局配置文件能够对一些默认配置值进行修改。Spring Boot 使用一个 application.properties 或者 application.yaml 的文件作为全局配置文件,该文件存放在 src/main/resource 目录或者类路径的 /config,一般会选择 resource 目录。

Spring Boot 配置文件的命名及其格式:

  • application.properties
  • application.yaml
  • application.yml
application.properties 配置文件

引入相关依赖:

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

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>6.0.6</version>
</dependency>

使用 Spring Initializr 方式构建 Spring Boot 项目时,会在 resource 目录下自动生成一个空的 application.properties 文件,Spring Boot 项目启动时会自动加载 application.properties 文件。

# 修改 tomcat 的版本号
server.port=8080
# 定义数据库的连接信息 JdbcTemplate
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/zm01
spring.datasource.username=root
spring.datasource.password=password

可以在 application.properties 文件中定义 Spring Boot 项目的相关属性,当然,这些相关属性可以是系统属性、环境变量、命令参数等信息,也可以是自定义配置文件名称和位置。

预先准备了两个实体类文件,后续会演示将 application.properties 配置文件中的自定义配置属性注入到 Person 实体类的对应属性中。

  1. 先在项目的 com.zm包下创建一个 pojo 包,并在该包下创建两个实体类 Pet 和 Person。
/**
 * 宠物类
 *
 * @author ZM
 * @since 2021-10-29 1:03
 */
public class Pet {
    // 品种
    private String type;
    // 名称
    private String name;
    // setter and getter, toString ...
}

/**
 * 人类
 *
 * @author ZM
 * @since 2021-10-29 1:04
 */
@Component
/**
 * 将配置文件中所有以 person 开头的配置信息注入当前类中
 * 前提 1:必须保证配置文件中 person.xx 与当前 Person 类的属性名一致
 * 前提 2:必须保证当前 Person 中的属性都具有 set 方法
 */
@ConfigurationProperties(prefix = "person")
public class Person {
    // id
    private int id;
    // 名称
    private String name;
    // 爱好
    private List hobby;
    // 家庭成员
    private String[] family;
    private Map map;
    // 宠物
    private Pet pet;
    // setter and getter, toString ...
}

@ConfigurationProperties(prefix = "person") 注解的作用是将配置文件中以 person 开头的属性值通过 setXX() 方法注入到实体类对应属性中。

@Component 注解的作用是将当前注入属性值的 Person 类对象作为 Bean 组件放到 Spring 容器中,只有这样才能被 @ConfigurationProperties 注解进行赋值。

  1. 打开项目的 resources 目录下的 application.properties 配置文件,在该配置文件中编写需要对 Person 类设置的配置属性。
# 自定义配置信息注入到 Person 对象中
person.id=100
person.name=张三
## list
person.hobby=看书,写代码,吃饭
## String[]
person.family=兄弟,父母
## map
person.map.k1=v1
person.map.k2=v2
## pet 对象
person.pet.type=dog
person.pet.name=旺财
  1. 查看 application.properties 配置文件是否正确,同时查看属性配置效果,打开通过 IDEA 工具创建的项目测试类,在该测试类中引入 Person 实体类 Bean,并进行输出测试。
@RunWith(SpringRunner.class)
@SpringBootTest
class Springbootdemo2ApplicationTests {

    @Autowired
    private Person person;

    @Test
    void showPersonInfo() {
        System.out.println(person);
    }

}

可以看出,测试方法 showPersonInfo() 运行成功,同时正确打印出了 Person 实体类对象。至此,说明 application.properties 配置文件属性配置正确,并通过相关注解自动完成了属性注入。

  1. 解决浏览器请求出现中文乱码问题。

调整文件编码格式:Settings -> Editor -> File Encodings -> 确保 Global Encoding 和 Project Encoding 为 UTF-8,修改 Default ecoding for properties files 为 UTF-8 并勾选 Transparent native-to-ascii conversion。

application.properties 文件中增加配置:

# 解决中文乱码
server.tomcat.uri-encoding=UTF-8
spring.http.encoding.force=true
spring.http.encoding.charset=UTF-8
spring.http.encoding.enabled=true
application.yaml 配置文件

YAML 文件格式是 Spring Boot 支持的一种 JSON 文件格式,相较于传统的 Properties 配置文件,YAML 文件以数据为核心,是一种更为直观且容易被电脑识别的数据序列化格式。application.yaml 配置文件的工作原理和 application.properties 是一样的,只不过 yaml 格式配置文件看起来更简洁一些。

  • YAML 文件的扩展名可以使用 .yml 或者 .yaml。
  • application.yml 文件使用 key: value 格式配置属性,使用缩进控制层级关系。

SpringBoot 的三种配置文件是可以共存的:application.properties、application.yaml、application.yml。

1)value 值为普通数据类型(例如数字、字符串、布尔等)

当 YAML 配置文件中配置的属性值为普通数据类型时,可以直接配置对应的属性值,同时对于字符串类型的属性值,不需要额外添加引号,示例代码如下:

server:
  port: 8080
  servlet:
    context-path: /hello

2)value 值为数组和单列集合

当 YAML 配置文件中配置的属性值为数组或单列集合类型时,主要有两种书写方式:缩进式写法和行内式写法。

其中,缩进式写法还有两种表示形式,示例代码如下:

person: 
  hobby: 
    - play
    - read
    - sleep 

或者使用如下示例形式:

person:
  hobby:
    play,
    read,
    sleep

上述代码中,在 YAML 配置文件中通过两种缩进式写法对 person 对象的单列集合(或数组)类型的爱好 hobby 赋值为 play、read 和 sleep。其中一种形式为 “- 属性值”,另一种形式为多个属性值之前加英文逗号分隔;注意,最后一个属性值后不要加逗号。

person: 
  hobby: [play,read,sleep]

通过上述示例对比发现,YAML 配置文件的行内式写法更加简明、方便。另外,包含属性值的中括号 “[]” 还可以进一步省略,在进行属性赋值时,程序会自动匹配和校对。

3)value 值为 Map 集合和对象

当 YAML 配置文件中配置的属性值为 Map 集合或对象类型时,YAML 配置文件格式同样可以分为两种书写方式 - 缩进式写法和行内式写法。

其中,缩进式写法的示例代码如下:

person: 
  map: 
    k1: v1
    k2: v2

对应的行内式写法示例代码如下:

person:
  map: {k1: v1,k2: v2}

在YAML配置文件中,配置的属性值为Map集合或对象类型时,缩进式写法的形式按照YAML文件格式编写即可,而行内式写法的属性值要用大括号“{}”包含。

接下来,在 Properties 配置文件演示案例基础上,注释掉 Properties 中跟 Person 相关的注入,然后通过配置 application.yaml 配置文件对 Person 对象进行赋值,具体使用如下。

在项目的 resources 目录下,新建一个 application.yaml 配置文件,在该配置文件中编写为 Person 类设置的配置属性:

person:
  id: 1000
  name: 张三
  hobby:
    - 跑步
    - 瑜伽
    - 游泳
  family:
    - 张小明
    - 李小红
  map:
    k1: 这是 k1 对应的 value
    k2: 这是 k2 对应的 value
  pet:
    type: dog
    name: 金毛

再次执行测试。

可以看出,测试方法 showPersonInfo() 同样运行成功,并正确打印出了 Person 实体类对象。

需要说明的是,本次使用 application.yaml 配置文件进行测试时需要提前将 application.properties 配置文件中编写的配置注释,这是因为 application.properties 配置文件会覆盖 application.yaml 配置文件。

配置文件属性值的注入

从 spring-boot-starter-parent 的 pom 文件可以知道配置文件的优先级从低到高如下:

<includes>
    <include>**/application*.yml</include>
    <include>**/application*.yaml</include>
    <include>**/application*.properties</include>
</includes>

使用 Spring Boot 全局配置文件设置属性时:

如果配置属性是 Spring Boot 已有属性,例如服务端口 server.port,那么 Spring Boot 内部会自动扫描并读取这些配置文件中的属性值并覆盖默认属性。

如果配置的属性是用户自定义属性,例如刚刚自定义的 Person 实体类属性,还必须在程序中注入这些配置属性方可生效。

Spring Boot 支持多种注入配置文件属性的方式,下面来介绍如何使用注解 @ConfigurationProperties@Value 注入属性。

使用 @ConfigurationProperties 注入属性

Spring Boot 提供的 @ConfigurationProperties 注解用来快速、方便地将配置文件中的自定义属性值批量注入到某个 Bean 对象的多个对应属性中。假设现在有一个配置文件,如果使用 @ConfigurationProperties 注入配置文件的属性,示例代码如下:

@Component
/**
 * 将配置文件中所有以 person 开头的配置信息注入当前类中
 * 前提 1:必须保证配置文件中 person.xx 与当前 Person 类的属性名一致
 * 前提 2:必须保证当前 Person 中的属性都具有 set 方法
 */
@ConfigurationProperties(prefix = "person")
public class Person {
    private int id;
    private String name;
    private List hobby;
    private String[] family;
    private Map map;
    private Pet pet;
    // setter and getter, toString ...
}

上述代码使用 @Component@ConfigurationProperties(prefix = “person”) 将配置文件中的每个属性映射到 person 类组件中。

使用 @Value 注入属性

@Value 注解是 Spring 框架提供的,用来读取配置文件中的属性值并逐个注入到 Bean 对象的对应属性中,Spring Boot 框架从 Spring 框架中对 @Value 注解进行了默认继承,所以在 Spring Boot 框架中还可以使用该注解读取和注入配置文件属性值。使用 @Value 注入属性的示例代码如下:

@Component
public class person{
    @Value("${person.id}")
    private int id;
    // getter setter toString ...
}

上述代码中,使用 @Component@Value 注入 Person 实体类的id属性。其中,@Value 不仅可以将配置文件的属性注入 Person 的 id 属性,还可以直接给 id 属性赋值,这点是@ConfigurationProperties 不支持的。

1)在 com.zm.pojo 包下新创建一个实体类 Student,并使用 @Value 注解注入属性:

@Component
public class Student {

   @Value("${person.id}")
   private String number;

   @Value("${person.name}")
   private String name;
   
   // getter setter toString ...
}

Student 类使用 @Value 注解将配置文件的属性值读取和注入。

从上述示例代码可以看出,使用 @Value 注解方式需要对每一个属性注入设置,同时又免去了属性的 setXX() 方法。

2)再次打开测试类进行测试:

@RunWith(SpringRunner.class)
@SpringBootTest
class Springbootdemo2ApplicationTests {

    @Autowired
    private Student student;

    @Test
    public void showStudentInfo(){
        System.out.println(student);
    }

}

可以看出,测试方法 showStudentInfo() 运行成功,同时正确打印出了 Student 实体类对象。需要说明的是,本示例中只是使用 @Value 注解对实例中 Student 对象的普通类型属性进行了赋值演示,而 @Value 注解对于包含 Map 集合、对象以及 YAML 文件格式的行内式写法的配置文件的属性注入都不支持,如果赋值会出现错误。

自定义配置

Spring Boot 免除了项目中大部分的手动配置,对于一些特定情况,可以通过修改全局配置文件以适应具体生产环境,可以说,几乎所有的配置都可以写在 application.yml 文件中,Spring Boot 会自动加载全局配置文件从而免除手动加载的烦恼。但是,如果自定义配置文件,Spring Boot 是无法识别这些配置文件的,此时就需要手动加载。

使用 @PropertySource 加载配置文件

对于这种加载自定义配置文件的需求,可以使用 @PropertySource 注解来实现。@PropertySource 注解用于指定自定义配置文件的具体位置和名称。

如果需要将自定义配置文件中的属性值注入到对应类的属性中,可以使用 @ConfigurationProperties 或者 @Value 注解进行属性值注入。

1)打开 Spring Boot 项目的 resources 目录,在项目的类路径下新建一个 my.properties 自定义配置文件,在该配置文件中编写需要设置的配置属性。

# 对实体类对象 Product 进行属性配置
product.id=99
product.name=小米

2)在 com.zm.pojo 包下新创建一个配置类 Product,提供 my.properties 自定义配置文件中对应的属性,并根据 @PropertySource 注解的使用进行相关配置。

@Component
@PropertySource("classpath:my.properties")
@ConfigurationProperties(prefix = "product")
public class Product {
    private int id;
    private String name;
    // getter setter toString ...
}

主要是一个自定义配置类,通过相关注解引入了自定义的配置文件,并完成了自定义属性值的注入。针对示例中的几个注解,具体说明如下:

  • @PropertySource("classpath:my.properties") 注解指定了自定义配置文件的位置和名称,此示例表示自定义配置文件为 classpath 类路径下的 my.properties 文件。
  • @ConfigurationProperties(prefix = "product") 注解将上述自定义配置文件 my.properties 中以 product 开头的属性值注入到该配置类属性中。

3)进行测试。

@RunWith(SpringRunner.class)
@SpringBootTest
class Springbootdemo2ApplicationTests {

    @Autowired
    private Product product;

    @Test
    public void showProductInfo() {
        System.out.println(product);
    }

}
使用 @Configuration 编写自定义配置类

在 Spring Boot 框架中,推荐使用配置类的方式向容器中添加和配置组件。

在 Spring Boot 框架中,通常使用 @Configuration 注解定义一个配置类,Spring Boot 会自动扫描和识别配置类,从而替换传统 Spring 框架中的 XML 配置文件。

当定义一个配置类后,还需要在类中的方法上使用 @Bean 注解进行组件配置,将方法的返回对象注入到 Spring 容器中,并且组件名称默认使用的是方法名,当然也可以使用 @Bean 注解的 name 或 value 属性自定义组件的名称。

1)在项目下新建一个 com.zm.config 包,并在该包下新创建一个类 MyService,该类中不需要编写任何代码:

package com.zm.service;
public class MyService {
}

创建了一个空的 MyService 类,而该类目前没有添加任何配置和注解,因此还无法正常被 Spring Boot 扫描和识别。

2)在项目的 com.zm.config 包下,新建一个类 MyConfig,并使用 @Configuration 注解将该类声明一个配置类,内容如下:

@Configuration // 标识当前类是一个配置类,SpringBoot 会扫描该类,将所有标识 @Bean 注解的方法的返回值注入的容器中
public class MyConfig {

    @Bean // 注入的名称就是方法的名称,注入的类型就是返回值的类型
    public MyService myService(){
        return new MyService();
    }
    
    @Bean("service_")
    public MyService myService2(){
        return new MyService();
    }

}

MyConfig 是 @Configuration 注解声明的配置类(类似于声明了一个 XML 配置文件),该配置类会被 Spring Boot 自动扫描识别;使用 @Bean 注解的 myService() 方法,其返回值对象会作为组件添加到了 Spring 容器中(类似于 XML 配置文件中的标签配置),并且该组件的 id 默认是方法名 myService。

3)测试类:

@RunWith(SpringRunner.class)
@SpringBootTest
class Springbootdemo2ApplicationTests {

    @Autowired
    private ApplicationContext applicationContext;

    @Test
    public void testConfig(){
        System.out.println(applicationContext.containsBean("myService"));
        System.out.println(applicationContext.containsBean("service_"));
    }

}

上述代码中,先通过 @Autowired 注解引入了 Spring 容器实例 ApplicationContext,然后在测试方法 testConfig() 中测试查看该容器中是否包括 id 为 myService 和 service_ 的组件。

从测试结果可以看出,测试方法 testConfig() 运行成功,显示运行结果为 true,表示 Spirng 的 IOC 容器中也已经包含了 id 为 myService 和 service_的实例对象组件,说明使用自定义配置类的形式完成了向 Spring 容器进行组件的添加和配置。


SpringBoot 原理深入及源码剖析

依赖管理

在 Spring Boot 入门程序中,项目 pom.xml 文件有两个核心依赖,分别是 spring-boot-starter-parent 和 spring-boot-starter-web,关于这两个依赖的相关介绍具体如下:

  • spring-boot-starter-parent 依赖
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.2.RELEASE</version>
    <relativePath/>
</parent>

上述代码中,将 spring-boot-starter-parent 依赖作为 Spring Boot 项目的统一父项目依赖管理,并将项目版本号统一为 2.2.2.RELEASE,该版本号根据实际开发需求是可以修改的。

使用 “Ctrl+鼠标左键” 进入并查看 spring-boot-starter-parent 底层源文件,发现 spring-boot- starter-parent 的底层有一个父依赖 spring-boot-dependencies,核心代码具体如下

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>2.2.2.RELEASE</version>
    <relativePath>../../spring-boot-dependencies</relativePath>
</parent>

继续查看 spring-boot-dependencies 底层源文件,核心代码具体如下:

<properties>
    <activemq.version>5.15.11</activemq.version>
    ...
    <solr.version>8.2.0</solr.version>
    <spring-amqp.version>2.2.2.RELEASE</spring-amqp.version>
    <spring-batch.version>4.2.1.RELEASE</spring-batch.version>
    <spring-cloud-connectors.version>2.0.7.RELEASE</spring-cloud-connectors.version>
    <spring-data-releasetrain.version>Moore-SR3</spring-data-releasetrain.version>
    <spring-framework.version>5.2.2.RELEASE</spring-framework.version>
    <spring-hateoas.version>1.0.2.RELEASE</spring-hateoas.version>
    <spring-integration.version>5.2.2.RELEASE</spring-integration.version>
    <spring-kafka.version>2.3.4.RELEASE</spring-kafka.version>
    <spring-ldap.version>2.3.2.RELEASE</spring-ldap.version>
    <spring-restdocs.version>2.0.4.RELEASE</spring-restdocs.version>
    <spring-retry.version>1.2.4.RELEASE</spring-retry.version>
    <spring-security.version>5.2.1.RELEASE</spring-security.version>
    <spring-session-bom.version>Corn-RELEASE</spring-session-bom.version>
    <spring-ws.version>3.0.8.RELEASE</spring-ws.version>
    <sqlite-jdbc.version>3.28.0</sqlite-jdbc.version>
    <sun-mail.version>${jakarta-mail.version}</sun-mail.version>
    <thymeleaf.version>3.0.11.RELEASE</thymeleaf.version>
    <thymeleaf-extras-data-attribute.version>2.0.1</thymeleaf-extras-data-attribute.version>
    ...
</properties>

从 spring-boot-dependencies 底层源文件可以看出,该文件通过标签对一些常用技术框架的依赖文件进行了统一版本号管理,例如 activemq、spring、tomcat 等,都有与 Spring Boot 2.2.2 版本相匹配的版本,这也是 pom.xml 引入依赖文件不需要标注依赖文件版本号的原因。

需要说明的是,如果 pom.xml 引入的依赖文件不是 spring-boot-starter-parent 管理的,那么在 pom.xml 引入依赖文件时,需要使用标签指定依赖文件的版本号。

  • pring-boot-starter-web 依赖

spring-boot-starter-parent 父依赖启动器的主要作用是进行版本统一管理;项目运行依赖的 JAR 包是从 Spring Boot Starters 中导入。

查看 spring-boot-starter-web 依赖文件源码,核心代码具体如下:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
        <version>2.2.2.RELEASE</version>
        <scope>compile</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-json</artifactId>
        <version>2.2.2.RELEASE</version>
        <scope>compile</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-tomcat</artifactId>
        <version>2.2.2.RELEASE</version>
        <scope>compile</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
        <version>2.2.2.RELEASE</version>
        <scope>compile</scope>
        <exclusions>
            <exclusion>
                <artifactId>tomcat-embed-el</artifactId>
                <groupId>org.apache.tomcat.embed</groupId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-web</artifactId>
        <version>5.2.2.RELEASE</version>
        <scope>compile</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.2.2.RELEASE</version>
        <scope>compile</scope>
    </dependency>
</dependencies>

从上述代码可以发现,spring-boot-starter-web 依赖启动器的主要作用是提供 Web 开发场景所需的底层所有依赖。

正是如此,在 pom.xml 中引入 spring-boot-starter-web 依赖启动器时,就可以实现 Web 场景开发,而不需要额外导入 Tomcat 服务器以及其他 Web 依赖文件等。当然,这些引入的依赖文件的版本号还是由 spring-boot-starter-parent 父依赖进行的统一管理。

Spring Boot Starters:

https://github.com/spring-projects/spring-boot/tree/v2.1.0.RELEASE/spring-boot-project/spring-boot-starters

https://mvnrepository.com/search?q=starter
Spring Boot 除了提供有上述介绍的 Web 依赖启动器外,还提供了其他许多开发场景的相关依赖,可以打开 Spring Boot 官方文档,搜索 “Starters” 关键字查询场景依赖启动器。

需要说明的是,Spring Boot 官方并不是针对所有场景开发的技术框架都提供了场景启动器,例如数据库操作框架 MyBatis、阿里巴巴的 Druid 数据源等,Spring Boot 官方就没有提供对应的依赖启动器。为了充分利用 Spring Boot 框架的优势,在 Spring Boot 官方没有整合这些技术框架的情况下,MyBatis、Druid 等技术框架所在的开发团队主动与 Spring Boot 框架进行了整合,实现了各自的依赖启动器,例如 mybatis-spring-boot-starter、druid-spring-boot-starter 等。在 pom.xml 文件中引入这些第三方的依赖启动器时,切记要配置对应的版本号。

自动配置

概念:能够在添加 jar 包依赖的时候,自动配置一些组件的相关配置,无需手动配置或者只需要少量手动配置就能运行编写的项目。
Spring Boot 应用的启动入口是 @SpringBootApplication 注解标注类中的 main() 方法。

@SpringBootApplication:SpringBoot 应用标注在某个类上说明这个类是 SpringBoot 的主配置类, SpringBoot 就应该运行这个类的 main() 方法启动 SpringBoot 应用。
进入到 @SpringBootApplication 内:

// 注解的适用范围, Type 表示注解可以描述在类、接口、注解或枚举中
@Target(ElementType.TYPE)
// 表示注解的生命周期,Runtime 运行时
@Retention(RetentionPolicy.RUNTIME)
// 表示注解可以记录在 javadoc 中
@Documented
// 表示可以被子类继承该注解
@Inherited
// 标明该类为 Spring Boot 配置类
@SpringBootConfiguration
// 启动自动配置功能
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

    // 根据 class 来排除特定的类,使其不能加入 spring 容器,传入参数 value 类型是 class 类型。
    @AliasFor(annotation = EnableAutoConfiguration.class)
    Class<?>[] exclude() default {};

    // 根据 classname 来排除特定的类,使其不能加入 spring 容器,传入参数 value 类型是class的全类名字符串数组。
    @AliasFor(annotation = EnableAutoConfiguration.class)
    String[] excludeName() default {};

    // 指定扫描包,参数是包名的字符串数组。
    @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
    String[] scanBasePackages() default {};

    // 扫描特定的包,参数类似是 Class 类型数组。
    @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
    Class<?>[] scanBasePackageClasses() default {};

    ...   
}

从上述源码可以看出,@SpringBootApplication 注解是一个组合注解,前面 4 个是注解的元数据信息, 主要看后面 3 个注解:@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan 三个核心注解。

  • @SpringBootConfiguration 注解

@SpringBootConfiguration: SpringBoot 的配置类,标注在某个类上,表示这是一个 SpringBoot 的配置类。

查看 @SpringBootConfiguration 注解源码:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
    ...
}

从上述源码可以看出,@SpringBootConfiguration 注解内部有一个核心注解 @Configuration,该注解是 Spring 框架提供的,表示当前类为一个配置类(XML 配置文件的注解表现形式),并可以被组件扫描器扫描。由此可见,@SpringBootConfiguration 注解的作用与 @Configuration 注解相同,都是标识一个可以被组件扫描器扫描的配置类,只不过 @SpringBootConfiguration 是被 Spring Boot 进行了重新封装命名而已。

  • @EnableAutoConfiguration 注解

@EnableAutoConfiguration:开启自动配置功能,以前需要手动配置的东西,现在由 SpringBoot 自动配置,这个注解就是 Springboot 能实现自动配置的关键。

查看该注解内部查看源码信息,核心代码具体如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
// 自动配置包
@AutoConfigurationPackage
// Spring 的底层注解 @Import,给容器中导入一个组件;导入的组件是AutoConfigurationPackages.Registrar.class
@Import(AutoConfigurationImportSelector.class)
// 告诉 SpringBoot 开启自动配置功能,这样自动配置才能生效。
public @interface EnableAutoConfiguration {

    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    // 返回不会被导入到 Spring 容器中的类
    Class<?>[] exclude() default {};

    // 返回不会被导入到 Spring 容器中的类名
    String[] excludeName() default {};

}

可以发现它是一个组合注解, Spring 中有很多以 Enable 开头的注解,其作用就是借助 @Import 来收集并注册特定场景相关的 Bean ,并加载到 IOC 容器。@EnableAutoConfiguration 就是借助 @Import 来收集所有符合自动配置条件的 bean 定义,并加载到 IoC 容器。

  • @AutoConfigurationPackage 注解

查看 @AutoConfigurationPackage 注解内部源码信息,核心代码具体如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

}

从上述源码可以看出,@AutoConfigurationPackage 注解的功能是由 @Import 注解实现的,它是 spring 框架的底层注解,它的作用就是给容器中导入某个组件类,例如 @Import(AutoConfigurationPackages.Registrar.class),它就是将 Registrar 这个组件类导入到容器中,可查看 Registrar 类中 registerBeanDefinitions 方法,这个方法就是导入组件类的具体实现:

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        register(registry, new PackageImport(metadata).getPackageName());
    }

    @Override
    public Set<Object> determineImports(AnnotationMetadata metadata) {
        return Collections.singleton(new PackageImport(metadata));
    }

}

从上述源码可以看出,在 Registrar 类中有一个 registerBeanDefinitions() 方法,使用 Debug 模式启动项目, 可以看到选中的部分就是 com.zm。也就是说,@AutoConfigurationPackage 注解的主要作用就是将主程序类所在包及所有子包下的组件到扫描到 spring 容器中。

因此在定义项目包结构时,要求定义的包结构非常规范,项目主程序启动类要定义在最外层的根目录位置,然后在根目录位置内部建立子包和类进行业务开发,这样才能够保证定义的类能够被组件扫描器扫描。

  • @Import({AutoConfigurationImportSelector.class}) 注解

将 AutoConfigurationImportSelector 这个类导入到 Spring 容器中,AutoConfigurationImportSelector 可以帮助 Springboot 应用将所有符合条件的 @Configuration 配置都加载到当前 SpringBoot 创建并使用的 IOC 容器 ApplicationContext 中。

继续研究 AutoConfigurationImportSelector 这个类,通过源码分析这个类中是通过selectImports 这个方法告诉 springboot 都需要导入那些组件:

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    }
    // 获得自动配置元信息,需要传入 beanClassLoader 这个类加载器
    AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
        .loadMetadata(this.beanClassLoader);
    AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
                                                                              annotationMetadata);
    return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

深入研究 loadMetadata 方法 :

// 文件中为需要加载的配置类的类路径
protected static final String PATH = "META-INF/spring-autoconfigure-metadata.properties";

private AutoConfigurationMetadataLoader() {
}

static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
    return loadMetadata(classLoader, PATH);
}

static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
    try {
        // 读取 spring-boot-autoconfigure-2.1.5.RELEASE.jar 包中的 spring-autoconfigure-metadata.properties 的信息生成 url
        Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path)
            : ClassLoader.getSystemResources(path);
        Properties properties = new Properties();
        // 解析 urls 枚举对象中的信息封装成 properties 对象并加载
        while (urls.hasMoreElements()) {
            properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement())));
        }
        // 根据封装好的 properties 对象生成 AutoConfigurationMetadata 对象并返回
        return loadMetadata(properties);
    }
    catch (IOException ex) {
        throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex);
    }
}

AutoConfigurationImportSelector 类 getAutoConfigurationEntry 方法:

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
    // 判断 EnabledAutoConfiguration 注解有没有开启, 默认开启
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }
    // 获得注解的属性信息
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    // 获取默认支持的自动配置类列表
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    // 去重
    configurations = removeDuplicates(configurations);
    // 去除一些多余的配置类,根据 @EnabledAutoConfiguration 的 exclusions 属性进行排除
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations, exclusions);
    configurations.removeAll(exclusions);
    // 根据 pom 文件中加入的依赖文件筛选中最终符合当前项目运行环境对应的自动配置类
    configurations = filter(configurations, autoConfigurationMetadata);
    // 触发自动配置导入监听事件
    fireAutoConfigurationImportEvents(configurations, exclusions);
    return new AutoConfigurationEntry(configurations, exclusions);
}

深入 getCandidateConfigurations 方法:

这个方法中有一个重要方法 loadFactoryNames,这个方法是让 SpringFactoryLoader 去加载一些组件的名字。

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    /**
     * loadFactoryNames:
     * 这个方法需要传入两个参数 getSpringFactoriesLoaderFactoryClass() 和 
     * getBeanClassLoader()。
     * getSpringFactoriesLoaderFactoryClass() 这个方法返回的是
     * @EnableAutoConfiguration.class。
     * getBeanClassLoader() 这个方法返回的是 beanClassLoader 类加载器
     */
    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;
}

/**
 * Return the class used by {@link SpringFactoriesLoader} to load configuration
 * candidates.
 * @return the factory class
 */
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
    return EnableAutoConfiguration.class;
}

...

@Override
public void setBeanClassLoader(ClassLoader classLoader) {
    this.beanClassLoader = classLoader;
}

protected ClassLoader getBeanClassLoader() {
    return this.beanClassLoader;
}

继续点开 loadFactoryNames 方法:

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
    // 获取出入的键
    String factoryTypeName = factoryType.getName();
    return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    MultiValueMap<String, String> result = cache.get(classLoader);
    if (result != null) {
        return result;
    }

    try {
        // 如果类加载器不为 null,则加载类路径下 spring.factories 文件,将其中设置的配置类的全路径信息封装为 Enumeration 类对象
        Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
        result = new LinkedMultiValueMap<>();
        // 循环 Enumeration 类对象,根据相应的节点信息生成 Properties 对象,通过传入的键获取值,在将值切割为一个个小的字符串转化为 Array,加入方法 result 集合中
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            UrlResource resource = new UrlResource(url);
            Properties properties = PropertiesLoaderUtils.loadProperties(resource);
            for (Map.Entry<?, ?> entry : properties.entrySet()) {
                String factoryTypeName = ((String) entry.getKey()).trim();
                for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                    result.add(factoryTypeName, factoryImplementationName.trim());
                }
            }
        }
        cache.put(classLoader, result);
        return result;
    }
    catch (IOException ex) {
        throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
}

上面代码需要读取的 FACTORIES_RESOURCE_LOCATION 最终路径的长这样,而这个是 spring 提供的一个工具类:

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

它其实是去加载一个外部的文件,而这文件是在 spring-boot-autoconfigure-2.2.2.RELEASE.jar 包下的 META-INF/spring.factories,打开 spring.factories 文件:

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
...

@EnableAutoConfiguration 就是从 classpath 中搜寻 META-INF/spring.factories 配置文件,并将其中 org.springframework.boot.autoconfigure.EnableutoConfiguration 对应的配置项通过反射 Java Refletion 实例化为对应的标注了 @Configuration 的 JavaConfig 形式的配置类,并加载到 IOC 容器中。

以入门项目为例,在项目中加入了 Web 环境依赖启动器,对应的 WebMvcAutoConfiguration 自动配置类就会生效,打开该自动配置类会发现,在该配置类中通过全注解配置类的方式对 Spring MVC 运行所需环境进行了默认配置,包括默认前缀、默认后缀、视图解析器、MVC 校验器等。而这些自动配置类的本质是传统 Spring MVC 框架中对应的 XML 配置文件,只不过在 Spring Boot 中以自动配置类的形式进行了预先配置。因此,在 Spring Boot 项目中加入相关依赖启动器后,基本上不需要任何配置就可以运行程序,当然,也可以对这些自动配置类中默认的配置进行更改。

总结:

因此 springboot 底层实现自动配置的步骤是:

  1. springboot 应用启动。
  1. @SpringBootApplication 起作用。
  1. @EnableAutoConfiguration。
  1. @AutoConfigurationPackage:这个组合注解主要是 @Import(AutoConfigurationPackages.Registrar.class),它通过将 Registrar 类导入到容器中,而 Registrar 类作用是扫描主配置类同级目录以及子包,并将相应的组件导入到 springboot 创建管理的容器中。
  1. @Import(AutoConfigurationImportSelector.class):它通过将AutoConfigurationImportSelector 类导入到容器中;AutoConfigurationImportSelector 类作用是通过 selectImports 方法执行的过程中,会使用内部工具类 SpringFactoriesLoader,查找 classpath 上所有 jar 包中的 META-INF/spring.factories 进行加载,实现将配置类信息交给 SpringFactory 加载器进行一系列的容器创建过程。
  • @ComponentScan 注解

@ComponentScan 注解具体扫描的包的根路径由 Spring Boot 项目主程序启动类所在包位置决定,在扫描过程中由前面介绍的 @AutoConfigurationPackage 注解进行解析,从而得到 Spring Boot 项目主程序启动类所在包的具体位置。

总结

@SpringBootApplication 的注解的功能简单来说就是 3 个注解的组合注解:

|- @SpringBootConfiguration
    |- @Configuration    // 通过 javaConfig 的方式来添加组件到 IOC 容器中
|- @EnableAutoConfiguration
    |- @AutoConfigurationPackage    // 自动配置包,与 @ComponentScan 扫描到的添加到 IOC
    |- @Import(AutoConfigurationImportSelector.class)    // 到 META-INF/spring.factories 中定义的 bean 添加到 IOC 容器中
|- @ComponentScan    // 包扫描

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容