SpringBoot原理

目录

1. 配置优先级

application.properties

2.Bean管理

2.1 获取Bean

2.2 Bean作用域

2.3 第三方Bean

3. SpringBoot原理

3.1 起步依赖

3.2 自动配置

3.2.1 概述

3.2.2 常见方案

3.2.2.1 概述

3.2.2.2 方案一

3.2.2.3 方案二

3.2.3 原理分析

3.2.3.1 源码跟踪

3.2.3.2 @Conditional


SpringBoot原理 主要偏向底层原理

主要记录以下三部分

1.首先就是配置优先级  SpringBoot中常见的配置方式以及配置当中的优先级

2.Bean管理

3.SpringBoot底层原理  面试加工资必备

1. 配置优先级

SpringBoot项目当中支持的三类配置文件:

  • application.properties

  • application.yml

  • application.yaml

启动SpringBoot程序,测试下三个配置文件中哪个Tomcat端口号生效:

  • properties、yaml、yml三种配置文件同时存在

SpringBoot原理_第1张图片

properties、yaml、yml三种配置文件,优先级最高的是properties

  • yaml、yml两种配置文件同时存在

SpringBoot原理_第2张图片

配置文件优先级排名(从高到低):

  1. properties配置文件

  2. yml配置文件

  3. yaml配置文件

注意事项:虽然springboot支持多种格式配置文件,但是在项目开发时,推荐统一使用一种格式的配置。(yml是主流)

在SpringBoot项目当中除了以上3种配置文件外,SpringBoot为了增强程序的扩展性,除了支持配置文件的配置方式以外,还支持另外两种常见的配置方式:

  1. Java系统属性配置 (格式: -Dkey=value)

-Dserver.port=9000

  1. 命令行参数 (格式:--key=value)

--server.port=10010

SpringBoot原理_第3张图片

SpringBoot原理_第4张图片

SpringBoot原理_第5张图片

删除命令行参数配置,重启SpringBoot服务: SpringBoot原理_第6张图片

所以

优先级: 命令行参数 > 系统属性参数 > properties参数 > yml参数 > yaml参数

如果项目已经打包上线了,这个时候我们来设置Java系统属性和命令行参数

运行jar程序:

  • 同时设置Java系统属性和命令行参数

SpringBoot原理_第7张图片

  • 仅设置Java系统属性

SpringBoot原理_第8张图片

注意事项:

  • Springboot项目进行打包时,需要引入插件 spring-boot-maven-plugin (基于官网骨架创建项目,会自动添加该插件)

在SpringBoot项目当中,常见的属性配置方式有5种, 3种配置文件,加上2种外部属性的配置(Java系统属性、命令行参数)。通过以上的测试,我们也得出了优先级(从低到高):

  • application.yaml(忽略)基本遇不见  赶紧忘掉

  • application.yml

  • application.properties

  • java系统属性(-Dxxx=xxx)

  • 命令行参数(--xxx=xxx)

2.Bean管理

可以通过Spring当中提供的注解@Component以及它的三个衍生注解(@Controller、@Service、@Repository)来声明IOC容器中的bean对象,同时我们为应用程序注入运行时所需要依赖的bean对象,也就是依赖注入DI。

  1. 从IOC容器中手动的获取到bean对象

  2. bean的作用域配置

  3. 管理第三方的bean对象

接第一方面,从IOC容器中获取bean对象。

2.1 获取Bean

默认情况下,SpringBoot项目在启动的时候会自动的创建IOC容器(也称为Spring容器),并且在启动的过程当中会自动的将bean对象都创建好,存放在IOC容器当中。应用程序在运行时需要依赖什么bean对象,就直接进行依赖注入就可以了。

而在Spring容器中提供了一些方法,可以主动从IOC容器中获取到bean对象,下面介绍3种常用方式:

  1. 根据name获取bean

Object getBean(String name)

  1. 根据类型获取bean

T getBean(Class requiredType)

  1. 根据name获取bean(带类型转换)

T getBean(String name, Class requiredType)

  • 想获取到IOC容器,直接将IOC容器对象注入进来就可以了

控制器:DeptController

@RestController
@RequestMapping("/depts")
public class DeptController {

    @Autowired
    private DeptService deptService;

    public DeptController(){
        System.out.println("DeptController constructor ....");
    }

    @GetMapping
    public Result list(){
        List deptList = deptService.list();
        return Result.success(deptList);
    }

    @DeleteMapping("/{id}")
    public Result delete(@PathVariable Integer id)  {
        deptService.delete(id);
        return Result.success();
    }

    @PostMapping
    public Result save(@RequestBody Dept dept){
        deptService.save(dept);
        return Result.success();
    }
}

业务实现类:DeptServiceImpl

@Slf4j
@Service
public class DeptServiceImpl implements DeptService {
    @Autowired
    private DeptMapper deptMapper;

    @Override
    public List list() {
        List deptList = deptMapper.list();
        return deptList;
    }

    @Override
    public void delete(Integer id) {
        deptMapper.delete(id);
    }

    @Override
    public void save(Dept dept) {
        dept.setCreateTime(LocalDateTime.now());
        dept.setUpdateTime(LocalDateTime.now());
        deptMapper.save(dept);
    }
}

Mapper接口:

@Mapper
public interface DeptMapper {
    //查询全部部门数据
    @Select("select * from dept")
    List list();

    //删除部门
    @Delete("delete from dept where id = #{id}")
    void delete(Integer id);

    //新增部门
    @Insert("insert into dept(name, create_time, update_time) values (#{name},#{createTime},#{updateTime})")
    void save(Dept dept);
}

测试类

@SpringBootTest
class SpringbootWebConfig2ApplicationTests {

    @Autowired
    private ApplicationContext applicationContext; //IOC容器对象

    //获取bean对象
    @Test
    public void testGetBean(){
        //根据bean的名称获取
        DeptController bean1 = (DeptController) applicationContext.getBean("deptController");
        System.out.println(bean1);

        //根据bean的类型获取
        DeptController bean2 = applicationContext.getBean(DeptController.class);
        System.out.println(bean2);

        //根据bean的名称 及 类型获取
        DeptController bean3 = applicationContext.getBean("deptController", DeptController.class);
        System.out.println(bean3);
    }
}

看 !!程序运行后控制台日志:

输出的bean对象地址值是一样的,说明IOC容器当中的bean对象  只有一个。 (默认情况下,IOC中的bean对象是单例)

也可以  将bean对象设置为非单例的(每次获取的bean都是一个新对象)  涉及到bean作用域  往后在记录

2.2 Bean作用域

在前面提到的IOC容器当中,默认bean对象是单例模式(只有一个实例对象)。那么设置bean对象为非单例  需要设置bean的作用域。

在Spring中支持五种作用域,后三种在web环境才生效:(只需记录第一种情况!!!!!)

作用域 说明
singleton 容器内同名称的bean只有一个实例(单例)(默认)
prototype 每次使用该bean时会创建新的实例(非单例)
request 每个请求范围内会创建新的实例(web环境中,了解)
session 每个会话范围内会创建新的实例(web环境中,了解)
application 每个应用范围内会创建新的实例(web环境中,了解)
  • 可以借助Spring中的@Scope注解来进行配置作用域

SpringBoot原理_第9张图片

1). 测试一

  • 控制器

//默认bean的作用域为:singleton (单例)
@Lazy //延迟加载(第一次使用bean对象时,才会创建bean对象并交给ioc容器管理)
@RestController
@RequestMapping("/depts")
public class DeptController {

    @Autowired
    private DeptService deptService;

    public DeptController(){
        System.out.println("DeptController constructor ....");
    }

    //省略其他代码...
}
  • 测试类

@SpringBootTest
class SpringbootWebConfig2ApplicationTests {

    @Autowired
    private ApplicationContext applicationContext; //IOC容器对象

    //bean的作用域
    @Test
    public void testScope(){
        for (int i = 0; i < 10; i++) {
            DeptController deptController = applicationContext.getBean(DeptController.class);
            System.out.println(deptController);
        }
    }
}

重启SpringBoot服务,运行测试方法,查看控制台打印的日志:

SpringBoot原理_第10张图片

注意事项:

  • IOC容器中的bean默认使用的作用域:singleton (单例)

  • 默认singleton的bean,在容器启动时被创建,可以使用@Lazy注解来延迟初始化(延迟到第一次使用时)

2). 测试二

修改控制器DeptController代码:

@Scope("prototype") //bean作用域为非单例
@Lazy //延迟加载
@RestController
@RequestMapping("/depts")
public class DeptController {

    @Autowired
    private DeptService deptService;

    public DeptController(){
        System.out.println("DeptController constructor ....");
    }

    //省略其他代码...
}

重启SpringBoot服务,再次执行测试方法,查看控制吧打印的日志:

SpringBoot原理_第11张图片

注意事项:

  • prototype的bean,每一次使用该bean的时候都会创建一个新的实例

  • 实际开发当中,绝大部分的Bean是单例的,也就是说绝大部分Bean不需要配置scope属性

2.3 第三方Bean

第三方bean的配置

配置的bean,像controller、service,dao三层体系下编写的类,这些类都是在项目当中自己定义的类(自定义类)。当要声明这些bean,也非常简单,只需要在类上加上@Component以及它的这三个衍生注解(@Controller、@Service、@Repository),就可以来声明这个bean对象了。 但是在项目开发当中,还有一种情况就是这个类它不是自己编写的,而是引入的第三方依赖当中提供的。

需要在pom.xml文件中,引入dom4j:



    org.dom4j
    dom4j
    2.1.3

dom4j就是第三方组织提供的。 dom4j中的SAXReader类就是第三方编写的。

当需要使用到SAXReader对象时,直接进行依赖注入就可以

  • 按照之前做法,需要在SAXReader类上添加一个注解@Component(将当前类交给IOC容器管理)?  不对

SpringBoot原理_第12张图片

结论:第三方提供的类是只读的。无法在第三方类上添加@Component注解或衍生注解。

如果要管理的bean对象来自于第三方(不是自定义的),是无法用@Component 及衍生注解声明bean的,就需要用到@Bean注解。

1:在启动类上添加@Bean标识的方法

@SpringBootApplication
public class SpringbootWebConfig2Application {

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

    //声明第三方bean
    @Bean //将当前方法的返回值对象交给IOC容器管理, 成为IOC容器bean
    public SAXReader saxReader(){
        return new SAXReader();
    }
}

xml文件:



    Tom
    18

测试类:

@SpringBootTest
class SpringbootWebConfig2ApplicationTests {

    @Autowired
    private SAXReader saxReader;

    //第三方bean的管理
    @Test
    public void testThirdBean() throws Exception {
        Document document = saxReader.read(this.getClass().getClassLoader().getResource("1.xml"));
        Element rootElement = document.getRootElement();
        String name = rootElement.element("name").getText();
        String age = rootElement.element("age").getText();

        System.out.println(name + " : " + age);
    }

    //省略其他代码...
}

重启SpringBoot服务,执行测试方法后,控制台输出日志:

Tom : 18

说明:以上在启动类中声明第三方Bean的作法,不建议使用(项目中要保证启动类的纯粹性)

2:在配置类中定义@Bean标识的方法

  • 如果需要定义第三方Bean时, 通常会单独定义一个配置类

@Configuration //配置类  (在配置类当中对第三方bean进行集中的配置管理)
public class CommonConfig {

    //声明第三方bean
    @Bean //将当前方法的返回值对象交给IOC容器管理, 成为IOC容器bean
          //通过@Bean注解的name/value属性指定bean名称, 如果未指定, 默认是方法名
    public SAXReader reader(DeptService deptService){
        System.out.println(deptService);
        return new SAXReader();
    }

}

注释掉SpringBoot启动类中创建第三方bean对象的代码,重启服务,执行测试方法,查看控制台日志:

Tom : 18

在方法上加上一个@Bean注解,Spring 容器在启动的时候,它会自动的调用这个方法,并将方法的返回值声明为Spring容器当中的Bean对象。

注意事项 :

  • 通过@Bean注解的name或value属性可以声明bean的名称,如果不指定,默认bean的名称就是方法名。

  • 如果第三方bean需要依赖其它bean对象,直接在bean定义方法中设置形参即可,容器会根据类型自动装配。

关于Bean只需要保持一个原则:

  • 如果是在项目当中自己定义的类,想将这些类交给IOC容器管理,我们直接使用@Component以及它的衍生注解来声明就可以。

  • 如果这个类它不是我们自己定义的,而是引入的第三方依赖当中提供的类,而且我们还想将这个类交给IOC容器管理。此时我们就需要在配置类中定义一个方法,在方法上加上一个@Bean注解,通过这种方式来声明第三方的bean对象。

3. SpringBoot原理

SpringBoot使能够集中精力地去关注业务功能的开发,而不用过多地关注框架本身的配置使用。这部分内容偏向于底层的原理分析。 也就是约定大于配置

在剖析SpringBoot的原理之前,先来看下 Spring家族的框架。

如果直接基于Spring框架进行项目的开发,会比较繁琐。

这个繁琐主要体现在两个地方:

  1. 在pom.xml中依赖配置比较繁琐,在项目开发时,需要自己去找到对应的依赖,还需要找到依赖它所配套的依赖以及对应版本,否则就会出现版本冲突问题。

  2. 在使用Spring框架进行项目开发时,需要在Spring的配置文件中做大量的配置,这就造成Spring框架入门难度较大,学习成本较高。

SpringBoot原理_第13张图片SpringBoot原理_第14张图片

基于Spring存在的问题,官方在Spring框架4.0版本之后,又推出了一个全新的框架:SpringBoot。

通过 SpringBoot来简化Spring框架的开发(是简化不是替代)。直接基于SpringBoot来构建Java项目,会让项目开发更加简单,更加快捷。

SpringBoot框架之所以使用起来更简单更快捷,是因为SpringBoot框架底层提供了两个非常重要的功能:一个是起步依赖,一个是自动配置。

SpringBoot原理_第15张图片

通过SpringBoot所提供的起步依赖,就可以大大的简化pom文件当中依赖的配置,从而解决了Spring框架当中依赖配置繁琐的问题。

通过自动配置的功能就可以大大的简化框架在使用时bean的声明以及bean的配置。我们只需要引入程序开发时所需要的起步依赖,项目开发时所用到常见的配置都已经有了,直接使用就可以了。

3.1 起步依赖

SpringBoot原理_第16张图片

spring-webmvc依赖:这是Spring框架进行web程序开发所需要的依赖

servlet-api依赖:Servlet基础依赖

jackson-databind依赖:JSON处理工具包

如果要使用AOP,还需要引入aop依赖、aspect依赖

项目中所引入的这些依赖,还需要保证版本匹配,否则就可能会出现版本冲突问题。

如果使用了SpringBoot,就不需要像上面这么繁琐的引入依赖了。只需要引入一个依赖就可以了,那就是web开发的起步依赖:springboot-starter-web。

SpringBoot原理_第17张图片

  • 主要是Maven的依赖传递。起步依赖的原理就是Maven的依赖传递

  • 在SpringBoot给我们提供的这些起步依赖当中,已提供了当前程序开发所需要的所有的常见依赖(官网地址:Spring Boot Reference Documentation)。

  • 比如:springboot-starter-web,这是web开发的起步依赖,在web开发的起步依赖当中,就集成了web开发中常见的依赖:json、web、webmvc、tomcat等。我们只需要引入这一个起步依赖,其他的依赖都会自动的通过Maven的依赖传递进来。

3.2 自动配置

什么是自动配置。

3.2.1 概述

SpringBoot的自动配置就是当Spring容器启动后,一些配置类、bean对象就自动存入到了IOC容器中,不需要我们手动去声明,从而简化了开发,省去了繁琐的配置操作。

比如:我们要进行事务管理、要进行AOP程序的开发,此时就不需要我们再去手动的声明这些bean对象了,我们直接使用就可以从而大大的简化程序的开发,省去了繁琐的配置操作。

下面我们打开idea,一起来看下自动配置的效果:

  • 运行SpringBoot启动类

SpringBoot原理_第18张图片

SpringBoot原理_第19张图片

SpringBoot原理_第20张图片

有两个CommonConfig,在第一个CommonConfig类中定义了一个bean对象,bean对象的名字叫reader。

在第二个CommonConfig中它的bean名字叫commonConfig,为什么还会有这样一个bean对象呢?原因是在CommonConfig配置类上添加了一个注解@Configuration,而@Configuration底层就是@Component

SpringBoot原理_第21张图片

所以配置类最终也是SpringIOC容器当中的一个bean对象

在IOC容器中除了自己定义的bean以外,还有很多配置类,这些配置类都是SpringBoot在启动的时候加载进来的配置类。这些配置类加载进来之后,它也会生成很多的bean对象。

SpringBoot原理_第22张图片

比如:配置类GsonAutoConfiguration里面有一个bean,bean的名字叫gson,它的类型是Gson。

com.google.gson.Gson是谷歌包中提供的用来处理JSON格式数据的。

要使用这些配置类中生成的bean对象时,可以使用@Autowired就自动注入了:

import com.google.gson.Gson;
import com.itheima.pojo.Result;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class AutoConfigurationTests {

    @Autowired
    private Gson gson;


    @Test
    public void testJson(){
        String json = gson.toJson(Result.success());
        System.out.println(json);
    }
}

添加断点,使用debug模式运行测试类程序:

SpringBoot原理_第23张图片

SpringBoot原理_第24张图片

上次面试问到的问题:在当前项目中我们并没有声明谷歌提供的Gson这么一个bean对象,然后我们却可以通过@Autowired从Spring容器中注入bean对象,那么这个bean对象怎么来的?

答案:SpringBoot项目在启动时通过自动配置完成了bean对象的创建。

其实分析自动配置原理就是来解析在SpringBoot项目中,在引入依赖之后是如何将依赖jar包当中所定义的配置类以及bean加载到SpringIOC容器中的。

3.2.2 常见方案
3.2.2.1 概述

剖析自动配置的原理。解析自动配置的原理就是分析在 SpringBoot项目当中,引入对应的依赖之后,是如何将依赖jar包当中所提供的bean以及配置类直接加载到当前项目的SpringIOC容器当中的。

接下来,就直接通过代码来分析自动配置原理。

SpringBoot原理_第25张图片

@Component
public class TokenParser {
    public void parse(){
        System.out.println("TokenParser ... parse ...");
    }
}

2、在测试类中,添加测试方法

@SpringBootTest
public class AutoConfigurationTests {

    @Autowired
    private ApplicationContext applicationContext;


    @Test
    public void testTokenParse(){
        System.out.println(applicationContext.getBean(TokenParser.class));
    }

    //省略其他代码...
}

3、执行测试方法 SpringBoot原理_第26张图片

异常信息描述: 没有com.example.TokenParse类型的bean

说明:在Spring容器中没有找到com.example.TokenParse类型的bean对象

引入进来的第三方依赖当中的bean以及配置类为什么没有生效?

  • 在类上添加@Component注解来声明bean对象时,还需要保证@Component注解能被Spring的组件扫描到。

  • SpringBoot项目中的@SpringBootApplication注解,具有包扫描的作用,但是它只会扫描启动类所在的当前包以及子包。

  • 当前包:com.itheima, 第三方依赖中提供的包:com.example(扫描不到)

两个方案

  • 方案1:@ComponentScan 组件扫描

  • 方案2:@Import 导入(使用@Import导入的类会被Spring加载到IOC容器中)

3.2.2.2 方案一

@ComponentScan组件扫描

@SpringBootApplication
@ComponentScan({"com.itheima","com.example"}) //指定要扫描的包
public class SpringbootWebConfig2Application {
    public static void main(String[] args) {
        SpringApplication.run(SpringbootWebConfig2Application.class, args);
    }
}

控制台日志输出: SpringBoot原理_第27张图片

进行项目开发时,当需要引入大量的第三方的依赖,就需要在启动类上配置N多要扫描的包,这种方式会很繁琐。而且这种大面积的扫描性能也比较低

缺点:

  1. 使用繁琐

  2. 性能低

所以 SpringBoot中并没有采用以上这种方案。

3.2.2.3 方案二

@Import导入

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

    1. 导入普通类

    2. 导入配置类

    3. 导入ImportSelector接口实现类

1). 使用@Import导入普通类:

@Import(TokenParser.class) //导入的类会被Spring加载到IOC容器中
@SpringBootApplication
public class SpringbootWebConfig2Application {
    public static void main(String[] args) {
        SpringApplication.run(SpringbootWebConfig2Application.class, args);
    }
}

控制台日志输出 SpringBoot原理_第28张图片

2). 使用@Import导入配置类:

  • 配置类

@Configuration
public class HeaderConfig {
    @Bean
    public HeaderParser headerParser(){
        return new HeaderParser();
    }

    @Bean
    public HeaderGenerator headerGenerator(){
        return new HeaderGenerator();
    }
}
  • 启动类

@Import(HeaderConfig.class) //导入配置类
@SpringBootApplication
public class SpringbootWebConfig2Application {
    public static void main(String[] args) {
        SpringApplication.run(SpringbootWebConfig2Application.class, args);
    }
}
  • 测试类

@SpringBootTest
public class AutoConfigurationTests {
    @Autowired
    private ApplicationContext applicationContext;
​
    @Test
    public void testHeaderParser(){
        System.out.println(applicationContext.getBean(HeaderParser.class));
    }
​
    @Test
    public void testHeaderGenerator(){
        System.out.println(applicationContext.getBean(HeaderGenerator.class));
    }
    
    //省略其他代码...
}

执行测试方法:

SpringBoot原理_第29张图片

3). 使用@Import导入ImportSelector接口实现类:

  • ImportSelector接口实现类

public class MyImportSelector implements ImportSelector {
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        //返回值字符串数组(数组中封装了全限定名称的类)
        return new String[]{"com.example.HeaderConfig"};
    }
}
  • 启动类

@Import(MyImportSelector.class) //导入ImportSelector接口实现类
@SpringBootApplication
public class SpringbootWebConfig2Application {
​
    public static void main(String[] args) {
        SpringApplication.run(SpringbootWebConfig2Application.class, args);
    }
}

执行测试方法:

SpringBoot原理_第30张图片

使用@Import注解通过这三种方式都可以导入第三方依赖中所提供的bean或者是配置类。

:如果基于以上方式完成自动配置,当要引入一个第三方依赖时,是不是还要知道第三方依赖中有哪些配置类和哪些Bean对象?

  • 答案:是的。 (对程序员来讲,很不友好,而且比较繁琐)

:当我们要使用第三方依赖,依赖中到底有哪些bean和配置类,谁最清楚?

  • 答案:第三方依赖自身最清楚。

所以:我们不用自己指定要导入哪些bean对象和配置类了,让第三方依赖它自己来指定。

怎么让第三方依赖自己指定bean对象和配置类?

  • 比较常见的方案就是第三方依赖给我们提供一个注解,这个注解一般都以@EnableXxxx开头的注解,注解中封装的就是@Import注解

4). 使用第三方依赖提供的 @EnableXxxxx注解

第三方依赖中提供的注解

@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Import(MyImportSelector.class)//指定要导入哪些bean对象或配置类 public @interface EnableHeaderConfig { }

在使用时只需在启动类上加上@EnableXxxxx注解即可

@EnableHeaderConfig  //使用第三方依赖提供的Enable开头的注解
@SpringBootApplication
public class SpringbootWebConfig2Application {
    public static void main(String[] args) {
        SpringApplication.run(SpringbootWebConfig2Application.class, args);
    }
}

执行测试方法:

SpringBoot原理_第31张图片

第4种方式会更方便更优雅,而这种方式也是SpringBoot当中所采用的方式。

3.2.3 原理分析
3.2.3.1 源码跟踪

通过源码跟踪的形式来剖析下SpringBoot底层到底是如何完成自动配置的

源码跟踪技巧:

在跟踪框架源码的时候,一定要抓住关键点,找到核心流程。一定不要从头到尾一行代码去看,一个方法的去研究,一定要找到关键流程,抓住关键点,先在宏观上对整个流程或者整个原理有一个认识,有精力再去研究其中的细节。

要搞清楚SpringBoot的自动配置原理,要从SpringBoot启动类上使用的核心注解@SpringBootApplication开始分析:

SpringBoot原理_第32张图片

我们先来看第一个注解:@SpringBootConfigurationSpringBoot原理_第33张图片

@SpringBootConfiguration注解上使用了@Configuration,表明SpringBoot启动类就是一个配置类。

@Indexed注解,是用来加速应用启动的(不用关心)。

接下来再先看@ComponentScan注解:

@ComponentScan注解是用来进行组件扫描的,扫描启动类所在的包及其子包下所有被@Component及其衍生注解声明的类。

SpringBoot启动类,之所以具备扫描包功能,就是因为包含了@ComponentScan注解。

最后我们来看看@EnableAutoConfiguration注解(自动配置核心注解):

SpringBoot原理_第34张图片

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

AutoConfigurationImportSelector类是ImportSelector接口的实现类。SpringBoot原理_第35张图片

AutoConfigurationImportSelector类中重写了ImportSelector接口的selectImports()方法:

SpringBoot原理_第36张图片

selectImports()方法底层调用getAutoConfigurationEntry()方法,获取可自动配置的配置类信息集合

SpringBoot原理_第37张图片

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

SpringBoot原理_第38张图片

getCandidateConfigurations方法的功能:

获取所有基于

META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件、META-INF/spring.factories文件中配置类的集合

这两个文件在哪里呢?

通常在引入的起步依赖中,都有包含以上两个文件SpringBoot原理_第39张图片

SpringBoot原理_第40张图片

第三方依赖中提供的GsonAutoConfiguration类: SpringBoot原理_第41张图片

在GsonAutoConfiguration类上,添加了注解@AutoConfiguration,通过查看源码,可以明确:GsonAutoConfiguration类是一个配置。 SpringBoot原理_第42张图片

原理就是在配置类中定义一个@Bean标识的方法,而Spring会自动调用配置类中使用@Bean标识的方法,并把方法的返回值注册到IOC容器中

自动配置源码小结

自动配置原理源码入口就是@SpringBootApplication注解,在这个注解中封装了3个注解,分别是:

  • @SpringBootConfiguration

    • 声明当前类是一个配置类

  • @ComponentScan

    • 进行组件扫描(SpringBoot中默认扫描的是启动类所在的当前包及其子包)

  • @EnableAutoConfiguration

    • 封装了@Import注解(Import注解中指定了一个ImportSelector接口的实现类)

      • 在实现类重写的selectImports()方法,读取当前项目下所有依赖jar包中META-INF/spring.factories、META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports两个文件里面定义的配置类(配置类中定义了@Bean注解标识的方法)。

当SpringBoot程序启动时,就会加载配置文件当中所定义的配置类,并将这些配置类信息(类的全限定名)封装到String类型的数组中,最终通过@Import注解将这些配置类全部加载到Spring的IOC容器中,交给IOC容器管理。

面试一个问题

在META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中定义的配置类非常多,而且每个配置类中又可以定义很多的bean,那这些bean都会注册到Spring的IOC容器中吗?

答案:并不是。 在声明bean对象时,上面有加一个以@Conditional开头的注解,这种注解的作用就是按照条件进行装配,只有满足条件之后,才会将bean注册到Spring的IOC容器中(下面会详细来讲解)

3.2.3.2 @Conditional

在跟踪SpringBoot自动配置的源码的时候,在自动配置类声明bean的时候,除了在方法上加了一个@Bean注解以外,还会经常用到一个注解,就是以Conditional开头的这一类的注解。以Conditional开头的这些注解都是条件装配的注解。下面就来介绍下条件装配注解。

@Conditional注解:

  • 作用:按照一定的条件进行判断,在满足给定条件后才会注册对应的bean对象到Spring的IOC容器中。

  • 位置:方法、类

  • @Conditional本身是一个父注解,派生出大量的子注解:

    • @ConditionalOnClass:判断环境中有对应字节码文件,才注册bean到IOC容器。

    • @ConditionalOnMissingBean:判断环境中没有对应的bean(类型或名称),才注册bean到IOC容器。

    • @ConditionalOnProperty:判断配置文件中有对应属性和值,才注册bean到IOC容器。

你可能感兴趣的:(java,SpringBoot,三层架构,spring,boot,java,spring)