# Spring Boot

# 参考文档

# 前置知识

  • Spring
  • Maven

# 要求

这里以 Spring Boot 2.5.3 为例,官方对系统的配置要求。

  • Java 8
  • Spring 5.3.9+
  • Maven 3.5+

PS

Spring 5 基于 Java 8,Java 8 有一个新特性,接口有了默认实现。 在 Java 8 之前,如果一个接口有 5 个方法,但是有两个实现类, 实现类三只想实现前两个方法,而实现类二只想实现后三个方法。 那么这时候实现类一除了它要实现的两个方法外,也要实现后三个方法的空实现。 所以,会采用适配器解决这个问题,即适配器(Adapter)实现所有的接口方法,实现类再分别重写适配器(Adapter)。 但是 Java 8 的新特性(接口可以有默认实现 default),直接解决了这种问题,所以,Adapter 就过时了。

# 简介

Spring Boot 是简化 Spring 开发的一个框架,简化 Spring 技术栈的快速开发脚手架,是整个 Spring 技术栈的大整合。

# 手动创建一个 SpringBoot 应用

TIP

这里以创建一个 web 项目为例:

  1. 创建一个 Maven 项目

  2. 在 POM 文件中 添加:

    <parent>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-parent</artifactId>
      <version>2.5.4</version>
    </parent>
    
    1
    2
    3
    4
    5

    因为这是一个 web 工程,所以还需要添加:

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
    
    1
    2
    3
    4
    5
    6
  3. 创建包结构,com.yyy.xxx,在 xxx 下编写你的应用

    • 必须在 java 包写创建自己的包,然后主启动类必须放在你自己创建的包下,否则会报错。(Spring Boot 的约定)
  4. 在 xxx 下创建主程序类:

    // 该注解是告诉 SpringBoot 框架,当前是一个 SpringBoot 应用,当前类是一个主程序类。
    @SpringBootApplication
    public class MyApplication {
        public static void main(String[] args) {
            // 让 Spring 启动起来
            SpringApplication.run(MyApplication.class, args);
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
  5. 在 xxx 下创建 controller 包

  6. 在 controller 包下创建 Controller 类:

    // @RestController = @Controller + @ResponseBody
    @RestController
    public class DemoController {
        // 路由映射
        @RequestMapping("/")
        public String hello() {
            return "hello world";
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
  7. 在 resources 包下创建 application.properties 或者 application.yml 配置文件:

    <!-- 这里以创建 application.properties 为例 -->
    server.port = 8801
    
    1
    2
  8. 运行 MyApplication.main() 启动工程

# <parent> <dependencyManagement> 标签

使用 <parent> 标签可以继承指定的依赖。

比如创建一个 Spring Boot 项目的第一步就是引入:

   <parent>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-parent</artifactId>
     <version>2.5.4</version>
   </parent>
1
2
3
4
5

点进去可以发现这个 spring-boot-starter-parent 中已经引入了 spring-boot-starter-web, 而且使用 <parent> 标签可以继承指定的依赖,那么为什么还要在自己的 POM.xml 中引入依赖 spring-boot-starter-web, 项目才可以启动呢。

因为 <dependencyManagement> </dependencyManagement>,这个标签的作用是管理依赖,所以在 POM.xml 中引入依赖 spring-boot-starter-web 的时候, 不再需要填写版本号,因为不写版本号的时候 maven 会沿着 <parent> 向上一直找到带有这个标签的依赖为止。

那么不写版本号可以理解,为什么使用了 <parent> 标签已经继承依赖,为什么还要自己写一遍呢,因为 <dependencyManagement> </dependencyManagement> 不会被继承,不会被引入。所以在父模块的 POM 中还需要再添加 web 依赖,但是这个父模块的子模块则只需要在子模块 POM 中使用 <parent> 引入父模块,就不需要再引入 web 了,因为它已经继承了父模块的 web,如果父模块中使用 <dependencyManagement> 将引入的 web 管理,那么在子模块中还需要再次引入 web 才能使用。

# 可执行 jar

# 创建步骤

  1. 打包使用 spring-boot-maven-plugin,在 POM 引入如下:

    <build>
      <plugins>
        <plugin>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
      </plugins>
    </build>
    
    1
    2
    3
    4
    5
    6
    7
    8
  2. clean -> install -> java -jar <jar-name>

# fat jar

spring boot 应用打的包就是 fat jar。

就是将当前的程序和它依赖的 jar 打包在一起称为一个 jar。

# 自动配置

# 自动配置-POM 分析

  • starter 是指一类包集合。
  • 官方的包命名 spring-boot-starter-*,这里的 * 指的是场景
    • 例如 spring-boot-starter-web 指的是 web 场景所能使用到的所有包的一个集合。
  • 第三方包命名 *-spring-boot-starter

# 自动配置-包扫描

默认的包扫描规则,是扫描主类所在的包下的所有文件以及子包。

# 自动配置-组件

@SpringBootApplication
public class BootApplication {
  public static void main(String[] args) {
    // 返回 Spring 容器实例
    ConfigurableApplicationContext run = SpringApplication.run(BootApplication.class, args);
    // 获取 Spring Boot 启动以后,加载的所有组件,即初始化的所有 Bean
    for (String beanDefinitionName : run.getBeanDefinitionNames()) {
      System.out.println(beanDefinitionName);
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11

# 自动配置-注解

# @Configuration

@Configuration 注解类在容器中是一个代理类。

  • 替代配置文件,使用 @Bean 注解进行 Bean 的注册。
  • @Bean 只会执行一次,默认是单例。
  • proxyBeanMethods 属性:
    • true,单例(默认)
    • false,多例

Full,全配置,即 @Configuration(proxyBeanMethods = true)。 Lite,轻量级配置,即 @Configuration(proxyBeanMethods = false)

@Configuration 注解的配置类有如下要求:

  • @Configuration 不可以是 final 类型;
  • @Configuration 不可以是匿名类;
  • 嵌套的 configuration 必须是静态类。

@Configuration 注解的 spring 容器加载方式,用 AnnotationConfigApplicationContext 替换 ClassPathXmlApplicationContext

示例:ApplicationContext context = new AnnotationConfigApplicationContext(TestConfiguration.class);

# @Import

使用在被 Spring 管理的组件上面。

  • 参数,class array 类型
  • 通过创建无参构造的方式导入类实例到容器

@ImportResource 可以导入 Spring 的 XML 配置文件到 Config 类。(主要用于升级中)

# 通过 ImportSelector 方式导入类

不同于 @Configuration + @Bean 的方式将 Bean 加载进容器,也可以使用 @Import + ImportSelector 的方式加载。

示例:

package com.aa.model;

public class User {
  private String name;
  ...
}
---
public class MySelector implements ImportSelector {

  @Override
  public String[] selectImports(AnnotationMetadata annotationMetadata) {
    return new String[]{
      "com.aa.model.User",
      ...
    };
  }
}
---
@Import({MySelector.class})
@Configuration
public class ImportConfig {
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# @Conditional

条件装配,符合条件才进行装配,如果将这个注解加载配置类上,表示当前的配置文件中是否符合当前条件,否则整个配置文件的 Bean 都不会被注册。

自定义装配条件,编写一个类 MyCondition implements Condition,重写 matches 方法,自定义逻辑,true 就注册,false 就不会注册。

还有其它 Spring 提供的注解:

  • @ConditionalOnBean,如果存在这个 Bean 的话,才进行注入。
  • @ConditionalOnClass
  • ...

示例:

// 这里只注册了 stu2conditional
@Configuration
public class StuConfig {

  public Stu stu1() {
    return new Stu();
  }

  @Bean
  public Stu stu2() {
    return new Stu();
  }
}
---
@SpringBootApplication
public class DemoApplication {
  public static void main(String[] args) {
    ConfigurableApplicationContext run = SpringApplication.run(DemoApplication.class, args);
    boolean stu1 = run.containsBean("stu1");
    System.out.println("stu1 是否存在" + stu1);
    boolean stu2 = run.containsBean("stu2");
    System.out.println("stu2 是否存在" + stu2);
  }
}
---
stu1 是否存在 false
stu2 是否存在 true
---
@Configuration
public class StuConfig {

  public Stu stu1() {
    return new Stu();
  }

  @Bean
  @ConditionalOnBean(name = "stu1")
  public Stu stu2() {
    return new Stu();
  }
}
---
@SpringBootApplication
public class DemoApplication {
  public static void main(String[] args) {
    ConfigurableApplicationContext run = SpringApplication.run(DemoApplication.class, args);
    boolean stu1 = run.containsBean("stu1");
    System.out.println("stu1 是否存在" + stu1);
    boolean stu2 = run.containsBean("stu2");
    System.out.println("stu2 是否存在" + stu2);
  }
}
---
stu1 是否存在false
stu2 是否存在false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

# @ConfigurationProperties

配置绑定: 将配置文件(application.properties 或者 application.yml)中的内容绑定到 JavaBean 中。

注意: 这个注解只能使用在 Component 上

使用方式:

  • @Bean + @ConfigurationProperties 实现

    application.yml:
    user:
      name: 666
    ---
    @Data
    public class AaaProperties {
      private String name;
    }
    ---
    public class XxxConfig() {
    
      @Bean
      @ConfigurationProperties(prefix = "user")
      public AaaProperties aProperties () {
          AaaProperties a = new AaaProperties();
          a.setName("999");
          return a;
      }
    }
    ---
    实际结果: a.getName = 666
    
    详见 ConfigurationProperties 实现原理
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
  • @Component + @ConfigurationProperties 实现

  • @EnableConfigurationProperties(XxxProperty.class) + @ConfigurationProperties 实现

    @EnableConfigurationProperties(XxxProperty.class) 作用:

    • 相当于是开启配置绑定 + 将传入类注册到容器
    • 适用于第三方 Bean

注意:

  • @Component + @ConfigurationProperties 时,容器中注入的将会是一个 name=xxxProperty 的 Bean;
  • 但是 @EnableConfigurationProperties(XxxProperty.class) + @ConfigurationProperties 的方式注入的 Bean 名不会是类名首字母小写的形式,而是 name=xxx-com.a.b.XxxProperty

# ConfigurationProperties 实现原理

当在一个 bean 组件上使用了 @ConfigurationProperties 注解时,指定的配置属性将会被设置到该 bean。

这一注解背后的工作机制,主要是由框架通过 BeanPostProcessor ConfigurationPropertiesBindingPostProcessor 来完成的。

ConfigurationPropertiesBindingPostProcessor 的作用是绑定 PropertySources@ConfigurationProperties 注解的 bean 实例。 这是一个框架内部工具,在实例化每一个 bean 时,框架会使用它将 @ConfigurationProperties 注解中指定前缀的外部配置属性项加载进来设置到 bean 相应的属性上。

这里的 PropertySources 能够从上下文中的 Environment 对象获取外部配置属性项。

# @SpringBootApplication

@SpringBootApplication 相当于以下三个注解:

  • @SpringBootConfiguration,实质就是个 @Configuration,实质就是个 Component

  • @ComponentScan,扫描指定范围/包下的组件

  • @EnableAutoConfiguration

    • 借助 @Import 的支持,收集和注册特定场景相关的 bean 定义
    • @AutoConfigurationPackage
    • @Import({AutoConfigurationImportSelector.class})

    @EnableAutoConfiguration 实质: 从 classpath 中搜寻所有 META-INF/spring.factories 配置文件,并将其中 org.spring-framework.boot.autoconfigure.EnableAutoConfiguration 对应的配置项通过反射(Java Reflection)实例化为对应的标注了 @Configuration 的 JavaConfig 形式的 IoC 容器配置类,然后汇总为一个并加载到 IoC 容器。

# 自动配置原理

Spring Boot 启动的时候,默认加载进来所有的 AutoConfiguration 类,(通过 @Import 加载进来所有的配置类) 这些配置类都是 @Configuration + @Conditionalxxx 组合,按条件进行加载的。

即:扫入全部配置文件 -> 按需加载

# 自动配置-启动分析

# 自动配置项分析Import

打印 CONDITIONS EVALUATION REPORT,条件评估报告。

配置文件中配置 debug=true,开启调试模式,然后启动程序,这时候控制台可以打印自动配置了那些配置。

# starter

spring 的 starter 依赖会将指定场景可能用到的所有依赖添加进去。

例如 spring-boot-starter-web 会将 tomcat 等相关依赖全部集成进去,也就是集成了在 web 场景下你所能用到的所有依赖。

# Testing

Spring Boot Testing 引入的依赖:spring-boot-starter-test 中包含了很多测试相关的类库:

  • JUnit 5: The de-facto standard for unit testing Java applications.
  • Spring Test & Spring Boot Test: Utilities and integration test support for Spring Boot applications.
  • AssertJ: A fluent assertion library.
  • Hamcrest: A library of matcher objects (also known as constraints or predicates). // other assertion library
  • Mockito: A Java mocking framework.
  • JSONassert: An assertion library for JSON.
  • JsonPath: XPath for JSON.
    • JsonPath.read(json,"$.store.book[1].author");

# 测试 Service

# 测试 Controller

# 讨论区

由于评论过多会影响页面最下方的导航,故将评论区做默认折叠处理。

点击查看评论区内容,渴望您的宝贵建议~
Last Updated: 10/18/2022, 2:42:49 PM