Spring Boot

全网最新Spring Boot 快速练手学习项目——简单超易懂系列

/**
 * @author muyi
 * @Description demo
 * @create 2023-08-21 22:29
 */
@RestController
public class test {
    @GetMapping("/hello/{url}")
    public String helloWorld( @PathVariable String url){
        return "Hello World"+url;
    }
}

前置内容

1、请求发起&&统一响应

1.1、请求响应

在这里插入图片描述
@ResponseBody :
在这里插入图片描述

1.1、统一返回响应
1.1.1、实现版本一

一个标准的返回格式至少包含3部分:

code: 状态码
message: 接口调用的提示信息
data: 返回数据

定义返回对象 Result.java

public class Result<T> {
    private int code;
    private String message;
    private T data;public Result() {}
    
    public Result(int code, String message) {
        this.code = code;
        this.message = message;
    }
    
    /**
     * 成功
     */
    public static <T> Result<T> success(T data) {
        Result<T> result = new Result<T>();
        result.setCode(ResultMsgEnum.SUCCESS.getCode());
        result.setMessage(ResultMsgEnum.SUCCESS.getMessage());
        result.setData(data);
        return result;
    }/**
     * 失败
     */
    public static <T> Result<T> error(int code, String message) {
        return new Result(code, message);
    }
}


定义状态码 HttpCode.java

public enum ResultMsgEnum {
    SUCCESS(0, "成功"),
    FAIL(-1, "失败"),
    AUTH_ERROR(502, "授权失败!"),
    SERVER_BUSY(503, "服务器正忙,请稍后再试!"),
    DATABASE_OPERATION_FAILED(504, "数据库操作失败");
    private int code;
    private String message;ResultMsgEnum(int code, String message) {
        this.code = code;
        this.message = message;
    }
    
    public int getCode() {
        return this.code;
    }
    
    public String getMessage() {
        return this.message;
    }
}


返回结果如下,可以看到返回结果与在Result中定义的参数类型相同。

{
    "code": 0,
    "message": "成功",
    "data": "Hello"
}

通过在Controller层调用 ResultData.success()对返回结果进行包装后返回给前端,虽然能够满足日常需求,但是当有大量的接口时,每一个接口中都使用Result.success()来包装返回信息就会增加很多重复代码。

1.1.2、实现初阶版本

可以借助SpringBoot提供的ResponseBodyAdvice对以上内容进行二级封装

ResponseBodyAdvice类介绍

该接口是SpringMVC 4.1提供的,它允许在 执行@ResponseBody后自定义返回数据,用来封装统一数据格式返回;拦截Controller方法的返回值,统一处理返回值/响应体,一般用来统一返回格式,加解密,签名等


public interface ResponseBodyAdvice<T> {
		/**
		* 是否支持advice功能
		* true 支持,false 不支持
		*/
    boolean supports(MethodParameter var1, Class<? extends HttpMessageConverter<?>> var2);

	  /**
		* 对返回的数据进行处理
		*/
    @Nullable
    T beforeBodyWrite(@Nullable T var1, MethodParameter var2, MediaType var3, Class<? extends HttpMessageConverter<?>> var4, ServerHttpRequest var5, ServerHttpResponse var6);
}

@RestControllerAdvice: 该注解是Controller的增强版,可以全局捕获抛出的异常,全局数据绑定,全局数据预处理。

本版本的基本内容实现:
新建ResponseAdvice类;

该类用于统一封装controller中接口的返回结果。
实现ResponseBodyAdvice接口,实现supports、beforeBodyWrite方法。

public class ResponseAdvice implements ResponseBodyAdvice<Object> {
    @Autowired
    private ObjectMapper objectMapper;
​
    /**
     * 是否开启功能 true:是 
     */
    @Override
    public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
        return true;
    }
​
    /**
     * 处理返回结果
     */
    @Override
    public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
        //处理字符串类型数据,如果Controller返回String的话,SpringBoot是直接返回.
        if(o instanceof String){
            try {
                return objectMapper.writeValueAsString(Result.success(o));
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
        }
        return Result.success(o);
    }
}

测试后发现和直接使用Result返回的结果是一致的,这样就再也不需要在Controller层通过 ResultData.success()来进行转换了,直接返回原始数据格式,SpringBoot会自动帮我们进行封装。

但是这里还存在一个问题,在ResponseAdvice我们全部使用了Result.success(o)来处理结果,对于error类型的结果未做处理。那么当发生异常情况时会发生什么情况?测试结果如下:

{
    "code": 0,
    "message": "成功",
    "data": {
        "timestamp": "2021-08-09T09:33:26.805+00:00",
        "status": 405,
        "error": "Method Not Allowed",
        "path": "/sysUser/getUserName"
    }
}

虽然格式上没有毛病,但是在code、data字段的具体数据上是不正确的,接口都报错了还返回操作成功的响应码。
全局异常处理器的实现:

  • 遇到异常时,第一时间想到的应该是try…catch,不过这种方式会导致大量代码重复,维护困难等问题,这里不用手写try…catch,由全局异常处理器统一捕获、
  • 对于自定义异常,只能通过全局异常处理器来处理,使用全局异常处理器最大的便利就是程序员在写代码时不再需要手写 try…catch了。
  • 当我们引入Validator参数校验器的时候,参数校验不通过会抛出异常,此时是无法用 try…catch捕获的,只能使用全局异常处理器。

首先新增一个类,增加@RestControllerAdvice注解,该注解的作用上面已经介绍过。

@RestControllerAdvice
public class CustomerExceptionHandler {
    
}

如果我们有想要拦截的异常类型,就新增一个方法,使用@ExceptionHandler注解修饰,注解参数为目标异常类型。

例如:controller中接口发生Exception异常时,就会进入到Execption方法中进行捕获,将杂乱的异常信息,转换成指定格式后交给ResponseAdvice方法进行统一格式封装并返回给前端。

@RestControllerAdvice
@Slf4j
public class CustomerExceptionHandler {@ExceptionHandler(AuthException.class)
    public String ErrorHandler(AuthorizationException e) {
        log.error("没有通过权限验证!", e);
        return "没有通过权限验证!";
    }@ExceptionHandler(Exception.class)
    public Result Execption(Exception e) {
        log.error("未知异常!", e);
        return Result.error(ResultMsgEnum.SERVER_BUSY.getCode(),ResultMsgEnum.SERVER_BUSY.getMessage());
    }
}

再次调用接口getUserName查看返回结果,发现还是有一些问题,因为我们在CustomerExceptionHandler中已经将接口返回结果封装成Result类型,而代码执行到统一结果返回类ResponseAdvice时,又会结果再次封装,就导致出现了如下问题。


{
    "code": 0,
    "message": "成功",
    "data": {
        "code": 503,
        "message": "服务器正忙,请稍后再试!",
        "data": null
    }
}

统一返回结果处理类版本实现:
解决上述问题非常简单,只要在beforeBodyWrite中增加一条判断即可。

@RestControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice<Object> {
    @Autowired
    private ObjectMapper objectMapper;
​
    /**
     * 是否开启功能 true:开启
     */
    @Override
    public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
        return true;
    }
​
    /**
     * 处理返回结果
     */
    @Override
    public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
        //处理字符串类型数据
        if(o instanceof String){
            try {
                return objectMapper.writeValueAsString(Result.success(o));
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
        }
        //返回类型是否已经封装
        if(o instanceof Result){
            return o;
        }
        return Result.success(o);
    }
}

至此,SpringBoot统一接口返回和全局异常处理就封装好了。

Spring Boot3

1、Spring Boot3 核心特性

1.0、快速入门

SpringBoot 3 依赖 JDK 17,采用全新的 Spring 6,Maven 的支持也提高到了 3.5,Gradle 提高到了 7.3,版本管理器默认也换成了 Gradle。值得一提的是,IDEA 2022.x 版本以上都可以支持 JDK 17。

SpringBoot 可以简单、快速地创建一个独立的、生产级别的 Spring 应用(SpringBoot 的底层实现是 Spring)。大多数 SpringBoot 应用只需要编写少量配置,即可快速整合 Spring 平台以及第三方技术。SpringBoot 的特性如下:

  • 快速创建独立 Spring 应用
  • 直接嵌入 Tomcat、Jetty、Undertow,无需部署 war 包
  • 提供可选的 starter,简化应用的整合
  • 按需自动配置 Spring 以及第三方库
  • 提供生产级特性:如监控指标、健康检查、外部化配置等
  • 无代码生成、无 XML 配置文件
1.0.1、开发环境要求

在这里插入图片描述

SpringBoot 3 依赖 JDK 17,采用全新的 Spring 6,Maven 的支持也提高到了 3.5,Gradle 提高到了 7.3,版本管理器默认也换成了 Gradle。值得一提的是,IDEA 2022.x 版本以上都可以支持 JDK 17。

1.0.2、创建 Maven 项目

创建 Maven 项目,SpringBoot 项目一般都需要继承自 spring-boot-starter-parent

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.0.5</version>
</parent>

或者引入 spring-boot-dependencies,这适用于项目已经拥有 Parent 依赖包的场景

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>3.0.5</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

继承 spring-boot-starter-parent 的时候,其实也算是继承自 spring-boot-dependencies,这是因为 parent 其实也是继承 dependencies,同时 parent 里面增加了一些插件,并指定了 Maven 的编译版本。

1.0.3、导入启动器

导入 Web 启动器,目的是引入 Spring MVC、Tomcat 等。

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
1.0.4、主启动程序

添加 @SpringBootApplication 注解,声明这是一个 SpringBoot 应用程序。

@SpringBootApplication
public class MainApplication {

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

1.0.5、业务控制类
@RestController
public class HelloController {

    @GetMapping("/hello")
    public String hello(){

        return "Hello,Spring Boot 3!";
    }

}

1.0.6、项目打包

在 Maven 的配置文件中添加打包插件后,执行 mvn clean package 命令可以将项目打成可执行的 Jar 包,并可以使用 java -jar xxx.jar 命令直接启动 SpringBoot 项目。

<build>
    <plugins>
        <!-- SpringBoot应用打包插件 -->
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

1.0.7、代码测试

浏览器访问 http://127.0.0.1:8080/hello,若正常返回 Hello,Spring Boot 3!,则说明第一个 SpringBoot 3 应用启动成功。

1.0.8、SpringBoot 特性总结

简化整合
导入相关的场景启动器,即可拥有相关的功能,默认支持的所有场景启动器可参照 官方文档。

  1. 官方提供的场景启动器:命名为 spring-boot-starter-*
  2. 第三方提供的场景启动器:命名为 *-spring-boot-starter

简化配置

  1. 配置基本都有默认值
  2. 能写的所有配置信息都可以在 官方文档 获取到
  3. 集中式管理配置,只需要修改 application.properties 这个文件就可以

简化部署
可以将项目打包为可执行的 Jar 包,并通过 java -jar xxx.jar 命令直接运行项目。

简化运维
支持外部化配置(往 Jar 包所在目录下放一个 application.properties 文件)、监控、健康检查。

1.0.9、SpringBoot 常用注解

SpringBoot 摒弃 XML 的配置方式,改为全注解驱动开发。

1.0.9.1、组件注册

注解介绍
常用的组件注册注解:

  1. @Configuration、@SpringBootConfiguration
  2. @Bean、@Scope
  3. @Controller、 @Service、@Repository、@Component
  4. @Import
  5. @ComponentScan

1、使用 @Configuration 注解编写一个配置类
2、在配置类中,自定义方法往容器中注册组件,并配合使用 @Bean 注解
3、或使用 @Import 注解导入第三方的组件

  • 第一种组件注册方式
@Configuration
public class UserConfiguration {

    /**
     * 注册组件,组件在容器中的名称默认是方法名,且默认是单实例
     */
    @Bean
    public User user() {
        User user = new User();
        user.setId(1L);
        user.setName("Peter");
        return user;
    }

}

  • 第二种组件注册方式
/**
 * 注册组件,组件在容器中的名称默认是全类名,且默认是单实例
 */
@Import(User.class)
@Configuration
public class UserConfiguration {

}

1.0.9.2、条件判断

注解介绍
如果注解指定的判断条件成立,则触发指定的行为。值得一提的是,条件注解的命名规则一般为 @ConditionalOnXxx。
在这里插入图片描述
除了上述介绍的四种条件注解,SpringBoot 还提供了以下的条件注解。
在这里插入图片描述
例如

@ConditionalOnBean(value = 组件类型,name = 组件名字),判断容器中是否有这个类型的组件,并且名称是指定的值
如果存在 FastsqlException 这个类,往容器中放一个 Cat 组件,名为 cat01,否则就往容器中放一个 Dog 组件,名为 dog01
如果系统中有 Dog 这个组件,就往容器中放一个 User 组件,名为 jim,否则就放一个 User,名叫 tom

@Configuration
public class SystemConfiguration {

    @ConditionalOnClass(name = "com.alibaba.druid.FastsqlException")
    @Bean(name = "cat01")
    public Cat cat() {
        return new Cat();
    }

    @ConditionalOnMissingClass(value = "com.alibaba.druid.FastsqlException")
    @Bean(name = "dog01")
    public Dog dog() {@Import(User.class)
    }

    @ConditionalOnBean(value = Dog.class)
    @Bean(name = "jim")
    public User userJim() {
        return new User();
    }

    @ConditionalOnMissingBean(value = Dog.class)
    @Bean(name = "tom")
    public User userTom() {
        return new User();
    }

}

1.0.9.3、属性绑定

注解介绍
在这里插入图片描述
@EnableConfigurationProperties 注解的使用场景

SpringBoot 只会扫描主程序类所在的包及其下面的子包。如果导入了第三方包,即使组件上标注了 @Component、@ConfigurationProperties 注解,组件也不会生效。因为这些组件都扫描不进来,此时使用 @EnableConfigurationProperties 注解就可以快速进行属性绑定,并把组件注册进容器。

例如

  • 往容器中注册组件(@Configuration、@Component、@Bean)
  • 使用 @ConfigurationProperties 声明组件和配置文件的哪些配置项进行绑定

第一种写法

  • 添加配置内容,在项目的 application.properties 配置文件中添加配置内容
pig.id=1
pig.age=3
pig.name=peter

  • 声明 Bean 类,通过 @Configuration 注解声明 Bean 类,并使用 @ConfigurationProperties 注解将 Bean 的属性值和配置文件的配置项的值进行绑定
@Configuration
@ConfigurationProperties(prefix = "pig")
public class PigProperties {

    private Long id;

    private String name;

    private Integer age;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "PigProperties{" +
            "id=" + id +
            ", name='" + name + '\'' +
            ", age=" + age +
            '}';
    }

}

第二种写法
使用 @Bean + @ConfigurationProperties 注解,将 Bean 的属性值和配置文件的配置项的值进行绑定。

@ConfigurationProperties(prefix = "pig")
public class PigProperties {

    private Long id;

    private String name;

    private Integer age;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "PigProperties{" +
            "id=" + id +
            ", name='" + name + '\'' +
            ", age=" + age +
            '}';
    }

}

@Configuration
public class SystemConfiguration {

    @Bean
    public PigProperties pigProperties() {
        return new PigProperties();
    }

}

@SpringBootApplication
public class MainApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(MainApplication.class, args);
        PigProperties properties = context.getBean(PigProperties.class);
        System.out.println(properties);
    }

}

第三种写法
使用 @ConfigurationProperties + @EnableConfigurationProperties 注解,将 Bean 的属性值和配置文件的配置项的值进行绑定,不再使用 @Configuration、@Component、@Bean 等注解。值得一提的是,@EnableConfigurationProperties 注解可以快速进行 Bean 属性的绑定,并把 Bean 注册进容器。

@ConfigurationProperties(prefix = "pig")
public class PigProperties {

    private Long id;

    private String name;

    private Integer age;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "PigProperties{" +
            "id=" + id +
            ", name='" + name + '\'' +
            ", age=" + age +
            '}';
    }

}

@SpringBootApplication
@EnableConfigurationProperties(PigProperties.class)
public class MainApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(MainApplication.class, args);
        PigProperties pigProperties = context.getBean(PigProperties.class);
        System.out.println(pigProperties);

        SheepProperties sheepProperties = context.getBean(SheepProperties.class);
        System.out.println(sheepProperties);
    }

}

但是目前我们学的都是给自定义的bean使用这种形原因就在于当前@ConfigurationProperties注解是写在类定义的上方,而第三方开发的bean源代码不是你自己书写的,你也不可能到源代码中去添@ConfigurationProperties注解,这种问题该怎么解决呢?下面就来说说这个问题。

第四种写法
同样使用 @Bean + @ConfigurationProperties 注解,将 Bean 的属性值和配置文件的配置项的值进行绑定,但 @ConfigurationProperties 注解不再声明在 Bean 类上。

public class PigProperties {

    private Long id;

    private String name;

    private Integer age;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "PigProperties{" +
            "id=" + id +
            ", name='" + name + '\'' +
            ", age=" + age +
            '}';
    }

}

@Configuration
public class SystemConfiguration {

    @Bean
    @ConfigurationProperties(prefix = "pig")
    public PigProperties pigProperties() {
        return new PigProperties();
    }

}

@SpringBootApplication
public class MainApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(MainApplication.class, args);
        PigProperties properties = context.getBean(PigProperties.class);
        System.out.println(properties);
    }

}

1.0.9.4、自动配置原理
在这里插入图片描述

1.0.10、常规配置
1.0.10.1、YAML 配置

YAML 是 “YAML Ain’t a Markup Language”(YAML 不是一种标记语言)。在开发这种语言时,YAML 的意思其实是:”Yet Another Markup Language”(是另一种标记语言)。

YAML 配置方式的优势

  • 设计目标,就是方便人类读写
    层次分明,更适合做配置文件
    使用 .yml 或 .yaml 作为文件后缀,如 application.yml

Properties 配置方式的弊端

  • 痛点:SpringBoot 集中化管理配置(application.properties)
    问题:配置内容多了以后,难以阅读和修改,层级结构辨识度不高

YMAL基本语法
在这里插入图片描述
例如:在Properties和YAML中分别表示下面Person对象
Java 配置属性类

@Data
@Component
@ConfigurationProperties(prefix = "person")
public class Person {

    private String name;
    private Integer age;
    private Date birthDay;
    private Boolean like;
    private Child child; // 嵌套对象
    private List<Dog> dogs; // 数组(里面是对象)
    private Map<String, Cat> cats; // 表示 Map

}

@Data
public class Child {
    private String name;
    private Integer age;
    private Date birthDay;
    private List<String> text; // 数组
}

@Data
public class Dog {
    private String name;
    private Integer age;
}

@Data
public class Cat {
    private String name;
    private Integer age;
}

Properties 表示法

person.name=张三
person.age=18
person.birthDay=2010/10/12 12:12:12
person.like=true
person.child.name=李四
person.child.age=12
person.child.birthDay=2018/10/12
person.child.text[0]=abc
person.child.text[1]=def
person.dogs[0].name=小黑
person.dogs[0].age=3
person.dogs[1].name=小白
person.dogs[1].age=2
person.cats.c1.name=小蓝
person.cats.c1.age=3
person.cats.c2.name=小灰
person.cats.c2.age=2

YAML表示法

person:
  name: 张三
  age: 18
  birthDay: 2010/10/10 12:12:12
  like: true
  child:
    name: 李四
    age: 20
    birthDay: 2018/10/10
    text: ["abc","def"]
  dogs:
    - name: 小黑
      age: 3
    - name: 小白
      age: 2
  cats:
    c1:
      name: 小蓝
      age: 3
    c2: {name: 小绿,age: 2} # 对象也可以用{}表示

值得注意的是:

在这里插入图片描述

1.0.10.2、日志配置
1.0.10.2.1、日志简介
  • Spring 使用 commons-logging 作为内部日志,但底层日志实现是开放的,可对接其他日志框架。
  • Spring 5 及以后的版本,commons-logging 被 Spring 自己直接重写了,包名是 spring-jcl。
  • SpringBoot 支持 JUL,Log4j,Logback 等日志框架,默认使用的日志框架是 Logback。
  • SpringBoot 提供了默认的控制台输出配置,也可以配置输出为文件。
  • 虽然日志框架很多,但是开发人员不用担心,因为使用 SpringBoot 默认的日志配置就能工作的很好。

SpringBoot 是怎么把日志默认配置好的?

1、每个场景启动器 spring-boot-starter-xxx,都会导入 spring-boot-starter 核心场景启动器
2、核心场景启动器导入了 spring-boot-starter-logging,包含了日志的所用功能
3、SpringBoot 默认使用了 SLF4J + Logback 组合作为默认的底层日志实现
4、日志是系统一启动就要用的,而 xxxAutoConfiguration 自动配置类只用于在系统启动完成后导入组件,属于后来用的,因此不适用于日志配置
5、SpringBoot 的日志配置是利用监听器机制实现,底层是通过实现 ApplicationContextInitializer 接口来配置好日志的
6、SpringBoot 的所有日志配置都可以通过修改配置文件实现变更,以 logging 为前缀开始项的所有配置内容

1.0.10.2.2、日志框架

在这里插入图片描述

1.0.10.2.3、日志格式

2023-06-11T22:34:17.511+08:00 INFO 4944 — [main] o.apache.catalina.core.StandardService : Starting service [Tomcat]

2023-06-11T22:34:17.511+08:00 INFO 4944 — [main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.7]

在这里插入图片描述

特别注意: Logback 没有 FATAL 级别,对应的是 ERROR 级别。

在这里插入图片描述
更改日志的输出格式

logging:
  pattern:
    console: '%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{15} ===> %msg%n'
1.0.10.2.4、日志记录

第一种写法,使用 SLF4J 的 API

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MainApplication {

    private static final Logger logger = LoggerFactory.getLogger(MainApplication.class);

    public static void main(String[] args) {
        SpringApplication.run(MainApplication.class, args);
        logger.info("Hello World!");
    }

}

第二种写法,使用 Lombok 的 @Slf4j 注解

<dependencies>
    <!-- 引入 Lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <scope>compile</scope>
    </dependency>
</dependencies>

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@Slf4j
@SpringBootApplication
public class MainApplication {

    public static void main(String[] args) {
        SpringApplication.run(MainApplication.class, args);
        log.info("Hello World!");
    }

}

1.0.10.2.5、日志级别

在这里插入图片描述

SpringBoot 默认的日志级别是 INFO。
SpringBoot 只会打印指定日志级别及以上级别的日志。
不指定日志级别的所有类,默认都使用 root 指定的日志级别。

日志级别的配置

  • 在 application.properties 配置文件中,可以通过 logging.level.= 指定日志级别。
  • level 的可取值范围:TRACE,DEBUG,INFO,WARN,ERROR,FATAL,OFF,它们都定义在 LogLevel 枚举类中。
  • root 的 logger-name 叫 root,所以可以配置 logging.level.root=warn,代表所有未指定日志级别的类都使用 root 的 warn 日志级别。
1.0.10.2.6、日志分组

精确调整某个包下类的日志级别,一般的写法如下:

logging:
  level:
    com.clay.shop: info

建议将相关的 logger 分组写在一起,SpringBoot 也支持这样统一配置,比如 Tomcat 相关的日志统一设置:

logging:
  group:
    tomcat: org.apache.catalina,org.apache.coyote,org.apache.tomcat
  level:
    tomcat: info

SpringBoot 预定义了两个日志分组:
在这里插入图片描述

1.0.10.2.7、日志文件输出

SpringBoot 默认只会将日志输出到控制台,如果想额外记录到文件,可以在 application.properties 中添加 logging.file.name 或者 logging.file.path 配置项。
在这里插入图片描述

logging:
  level:
    com.clay.shop: info
  file:
    name: /tmp/logs/shop/app.log

1.0.10.2.8、日志归档

文件归档与滚动切割

归档:每天的日志单独存到一个文档中。
切割:每个文件 10MB,超过大小就切割成另外一个文件。

  • 每天的日志应该独立分割出来存档。如果使用 Logback(SpringBoot 默认整合),可以通过 application.properties 文件指定日志滚动规则。
  • 如果是其他日志系统,需要自行配置(例如使用 Log4j2,则添加 log4j2.xml 或 log4j2-spring.xml 配置文件)
  • SpringBoot 默认支持的滚动规则配置如下:
    在这里插入图片描述
1.0.10.2.9、自定义配置

一般情况下,SpringBoot 项目修改 application.properties 配置文件,就可以调整日志的所有行为。如果不够,还可以自定义日志系统的配置文件。比如:
在这里插入图片描述
值得注意的是:
如果可能,建议在日志配置文件的名称中使用 -spring 变量,比如 logback-spring.xml 而不是 logback.xml。这是因为如果使用标准的文件名称,Spring 无法完全控制日志的初始化过程。

1.0.10.2.10、切换日志框架

切换 Log4j2 日志框架

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>

Log4j2 支持 XML、YAML 、JSON 格式的配置文件
在这里插入图片描述
值得注意的是:
在这里插入图片描述

1.1、Web开发

1.1.1、自动配置原理

Web 场景的自动配置原理

  • 导入 spring-boot-starter-web,会导入 spring-boot-starter,也就会导入 spring-boot-autoconfigure 包。
  • spring-boot-autoconfigure 包里面有一个文件 METAINF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,里面指定了应用启动时所有要加载的自动配置类。
  • @EnableAutoConfiguration 会自动地将上面文件里写的所有自动配置类都导入进来,同时 xxxAutoConfiguration 是有声明条件注解的,目的是按需加载。
  • xxxAutoConfiguration 往容器中导入一堆组件,这些组件都是从 xxxProperties 中获取属性值。
  • xxxProperties 又是和配置文件进行了绑定。

Web 场景的所有自动配置类

org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration
org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration
org.springframework.boot.autoconfigure.web.reactive.ReactiveMultipartAutoConfiguration
org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration
org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration
org.springframework.boot.autoconfigure.web.reactive.WebSessionIdResolverAutoConfiguration
org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration
org.springframework.boot.autoconfigure.web.reactive.function.client.ClientHttpConnectorAutoConfiguration
org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration

Web 场景下配置文件的前缀配置项说明

  • 服务器的配置 server
  • Web 场景通用配置 spring.web
  • Spring MVC 的所有配置 spring.mvc
  • 文件上传配置 spring.servlet.multipart

自动配置的默认效果
SpringBoot Web 场景自动配置的效果如下:

  • 支持静态 index.html
  • 支持默认的静态资源处理机制,静态资源放在 static 文件夹下即可直接访问
  • 包含了 ContentNegotiatingViewResolver 和 BeanNameViewResolver 组件,用于视图解析
  • 自动注册了 Converter,GenericConverter,Formatter 组件,适配了常见的数据类型转换和格式化需求
  • 支持 HttpMessageConverters,可以方便返回 JSON 等数据类型
  • 注册 MessageCodesResolver,方便国际化及错误消息处理
  • 自动使用 ConfigurableWebBindingInitializer 实现消息处理、数据绑定、类型转化、数据校验等功能

值得注意的是:
在这里插入图片描述

如果想保持 SpringBoot MVC 的默认配置,并且自定义更多的 MVC 配置(如 interceptors,formatters,view controllers 等),可以使用 @Configuration 标注一个配置类,让配置类实现 WebMvcConfigurer 接口,并不要使用 @EnableWebMvc 注解。

如果想保持 SpringBoot MVC 的默认配置,但要自定义核心组件的实例(如 RequestMappingHandlerMapping,RequestMappingHandlerAdapter,ExceptionHandlerExceptionResolver),往容器中放一个 WebMvcRegistrations 组件,并不要使用 @EnableWebMvc 注解。

如果想全面接管 SpringBoot MVC 的配置,可以使用 @Configuration 标注一个配置类,让配置类实现 WebMvcConfigurer 接口,并加上 @EnableWebMvc 注解即可。

1.1.2、静态资源
1.1.2.1、默认静态资源规则
1.1.2.1.1、静态资源映射

静态资源映射规则都在 WebMvcAutoConfiguration 自动配置类中进行了定义。

  • 访问 /webjars/** 路径就会去 classpath:/META-INF/resources/webjars/ 下找资源,一般用于访问通过 Maven 引入的第三方前端组件(如 Vue、Bootstrap)
  • 访问 /** 路径就会去静态资源默认的四个位置找资源,包括 classpath:/META-INF/resources/、classpath:/resources/、classpath:/static/、classpath:/public/
1.1.2.1.2、静态资源缓存

所有静态资源都定义了缓存规则,浏览器访问过一次,就会缓存一段时间,但此功能的参数无默认值。

  • cachePeriod:缓存周期,多久不用找服务器要新的,默认值 0s,以秒为单位
  • cacheControl: HTTP 缓存控制,参照 Mozilla 文档
  • useLastModified:是否使用 Last-Modified 头,默认值 false,配合 HTTP Cache 规则使用
  • 所有缓存参数的配置,都可以通过配置文件里的 spring.web 前缀配置项指定(如下)
# 缓存周期(秒)
spring.web.resources.cache.period=3600

# HTTP 缓存控制(秒),浏览器第一次请求服务器时,服务器会告诉浏览器此资源缓存7200秒,即7200秒以内访问此资源的所有请求都不会发送给服务器
spring.web.resources.cache.cachecontrol.max-age=7200

# 是否使用 Last-Modified 头,对比服务器和浏览器的资源是否有变化,如果资源没有变化,服务器会返回304码,此时浏览器会使用本地缓存中的资源
spring.web.resources.cache.use-last-modified=true

1.1.2.1.3、Favicon 图标

在默认的四个静态资源路径下查找 favicon.ico

  • 首先会在默认的四个静态资源路径下查找 index.html
  • 如果静态资源路径下找不到,就会在 templates 目录下找 index 模板页面
1.1.2.2、自定义静态资源规则
1.1.2.2.1、配置方式
  • spring.mvc:配置静态资源访问路径的前缀
  • spring.web:配合静态资源目录、静态资源策略 (开启映射、处理链、缓存规则)、国际化的区域信息
# 自定义静态资源访问路径的前缀
spring.mvc.static-path-pattern=/static/**

# 自定义webjars访问路径的前缀
spring.mvc.webjars-path-pattern=/webjars/**

# 自定义静态资源文件夹的位置
spring.web.resources.static-locations=classpath:/static/,classpath:/public/,classpath:/asset/

当不使用 spring.mvc.static-path-pattern 自定义静态资源访问路径的前缀时,静态资源默认的访问路径示例是 http://127.0.0.1:8080/backgrond.png

当使用 spring.mvc.static-path-pattern=/static/** 自定义静态资源访问路径的前缀时,静态资源的访问路径示例是 http://127.0.0.1:8080/static/backgrond.png

1.2.2.2.2、代码方式

如果希望完全禁用 SpringBoot MVC 的自动配置,可以在配置类上添加 @EnableWebMvc 注解,此时相当于采用全手动的方式配置 MVC。

第一种写法:实现 WebMvcConfigurer 接口,同样可以自定义静态资源规则

import org.springframework.context.annotation.Configuration;
import org.springframework.http.CacheControl;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.concurrent.TimeUnit;

@Configuration
public class WebConfiguration implements WebMvcConfigurer {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        // 保留默认的配置规则,并添加新的配置规则
        registry
            // 自定义静态资源访问路径的前缀
            .addResourceHandler("/static/**")
            // 自定义静态资源文件夹的位置
            .addResourceLocations("classpath:/static/", "classpath:/public/", "classpath:/asset/")
            // 自定义HTTP缓存控制
            .setCacheControl(CacheControl.maxAge(7200, TimeUnit.SECONDS));
    }

}

第二种写法:使用 @Bean 注解,定义 WebMvcConfigurer 组件,同样可以自定义静态资源规则

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.CacheControl;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.concurrent.TimeUnit;

@Configuration
public class WebConfiguration {

    @Bean
    public WebMvcConfigurer webMvcConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addResourceHandlers(ResourceHandlerRegistry registry) {
                // 保留默认的配置规则,并添加新的配置规则
                registry
                    // 自定义静态资源访问路径的前缀
                    .addResourceHandler("/static/**")
                    // 自定义静态资源文件夹的位置
                    .addResourceLocations("classpath:/static/", "classpath:/public/", "classpath:/asset/")
                    // 自定义HTTP缓存控制
                    .setCacheControl(CacheControl.maxAge(7200, TimeUnit.SECONDS));
            }
        };
    }

}

1.1.3、错误处理
1.1.3.1、默认机制

SpringBoot 错误处理的自动配置都在 ErrorMvcAutoConfiguration 中,两大核心机制:

  • SpringBoot 会自适应处理错误,响应页面或 JSON 数据给客户端
  • SpringMVC 的错误处理机制依然保留,SpringMVC 处理不了的,才会交给 SpringBoot 进行处理

在这里插入图片描述

  • 发生错误以后,请求转发给 /error 路径,SpringBoot 在底层写好一个 BasicErrorController 组件,专门处理这个请求
/**
 * 返回HTML
 */
@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
    HttpStatus status = getStatus(request);
    Map<String, Object> model = Collections
        .unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
    response.setStatus(status.value());
    ModelAndView modelAndView = resolveErrorView(request, response, status, model);
    return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
}

/**
 * 返回 ResponseEntity,JSON
 */
@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
    HttpStatus status = getStatus(request);
    if (status == HttpStatus.NO_CONTENT) {
        return new ResponseEntity<>(status);
    }
    Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));
    return new ResponseEntity<>(body, status);
}

  • 错误页面是这么解析到的
// 解析错误的自定义视图地址
ModelAndView modelAndView = resolveErrorView(request, response, status, model);
// 如果解析不到错误页面的地址,默认的错误页就是 error
return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);

  • 容器中专门有一个错误视图解析器
@Bean
@ConditionalOnBean(DispatcherServlet.class)
@ConditionalOnMissingBean(ErrorViewResolver.class)
DefaultErrorViewResolver conventionErrorViewResolver() {
    return new DefaultErrorViewResolver(this.applicationContext, this.resources);
}

  • SpringBoot 解析自定义错误页的默认规则
@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
    ModelAndView modelAndView = resolve(String.valueOf(status.value()), model);
    if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
        modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
    }
    return modelAndView;
}

private ModelAndView resolve(String viewName, Map<String, Object> model) {
    String errorViewName = "error/" + viewName;
    TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext);
    if (provider != null) {
        return new ModelAndView(errorViewName, model);
    }
    return resolveResource(errorViewName, model);
}

private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
    for (String location : this.resources.getStaticLocations()) {
        try {
            Resource resource = this.applicationContext.getResource(location);
            resource = resource.createRelative(viewName + ".html");
            if (resource.exists()) {
                return new ModelAndView(new HtmlResourceView(resource), model);
            }
        }
        catch (Exception ex) {
        }
    }
    return null;
}

  • 容器中有一个默认的名为 error 的 View,提供了默认错误页面的功能
@Bean(name = "error")
@ConditionalOnMissingBean(name = "error")
public View defaultErrorView() {
    return this.defaultErrorView;
}

  • 封装了 JSON 格式的错误信息
@Bean
@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
public DefaultErrorAttributes errorAttributes() {
    return new DefaultErrorAttributes();
}

在这里插入图片描述

1.1.3.2、自定义错误响应
  • 自定义 JSON 响应,使用 @ControllerAdvice + @ExceptionHandler 进行统一异常处理
@ControllerAdvice
public class GlobalExceptionHandler {

    @ResponseBody
    @ExceptionHandler(Exception.class)
    public R handleException(Exception exception) {
        return R.error(exception.getMessage());
    }

}

  • 自定义页面响应,根据 SpringBoot 的错误页面解析规则,自定义错误页面。
1.1.3.3、错误处理的最佳实践

在这里插入图片描述

@AutoConfiguration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
		ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
		ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
		ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {
    
}

@Override
protected void onRefresh() {
    super.onRefresh();
    try {
        createWebServer();
    }
    catch (Throwable ex) {
        throw new ApplicationContextException("Unable to start web server", ex);
    }
}

1.1.4、嵌入式容器

Servlet 容器指的是管理、运行 Servlet 组件(Servlet、Filter、Listener)的环境,一般指 Web 服务器。

1.1.4.1、自动配置原理

在这里插入图片描述
值得注意的是

Web 场景的 Spring 容器启动,在调用 onRefresh() 的时候,会调用创建 Web 服务器的方法。
Web 服务器的创建是通过 WebServerFactory 实现的,容器中又会根据条件注解,启动相关的服务器配置,默认 EmbeddedTomcat 会往容器中放一个 TomcatServletWebServerFactory 组件,导致项目启动后,自动创建出 Tomcat 服务器。

1.1.4.2、自定义嵌入式容器

嵌入式三大容器有 Tomcat、Jetty、Undertow,SpringBoot 默认使用 Tomcat 作为容器。若希望切换到其他容器,只需要更改 Maven 的配置即可,如下所示:

<properties>
    <!-- 定义Servlet版本 -->
    <servlet-api.version>3.1.0</servlet-api.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <exclusions>
            <!-- 排除Tomcat依赖 -->
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-tomcat</artifactId>
            </exclusion>
        </exclusions>
    </dependency>

    <!-- 使用Undertow容器 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-undertow</artifactId>
    </dependency>
</dependencies>

值得注意的是

可以通过在YAML修改以 server 为开始前缀的相关配置,这样就可以修改服务器参数
通过往容器中放一个 ServletWebServerFactory 组件,来禁用掉 SpringBoot 默认引入的 Web 服务器工厂,这样就可以实现自定义任意的嵌入服务器。

1.1.5、底层源码浅析
1.1.5.1、Web MVC 自动配置原理
1.1.5.1.1、自动配置生效的条件
/**
 * WebMvcAutoConfiguration 的底层源码
 */
@AutoConfiguration(after = { DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class }) // 在这些自动配置之后
@ConditionalOnWebApplication(type = Type.SERVLET) // 如果是 Web 应用就生效,类型: SERVLET、REACTIVE
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class) // 容器中没有这个 Bean 才生效,默认就是没有
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10) // 优先级
@ImportRuntimeHints(WebResourcesRuntimeHints.class)
public class WebMvcAutoConfiguration {

}

1.1.5.1.2、自动配置实现的效果
  • 效果一:往容器中放了两个 Filter
    • HiddenHttpMethodFilter:页面表单提交 REST 请求(支持 GET、POST、PUT、DELETE 方法)
    • FormContentFilter: 表单内容 Filter,GET(数据放 URL 后面)、POST(数据放请求体)请求可以携带数据,PUT、DELETE 的请求体数据会被忽略
  • 效果二:往容器中放了 WebMvcConfigurer 组件,给 Spring MVC 添加各种定制功能,所有功能最终都会和配置文件的内容进行绑定
    • WebMvcProperties: 绑定了以 spring.mvc 为开始前缀的配置项
    • WebProperties: 绑定了以 spring.web 为开始前缀的配置项
1.1.5.1.3、 WebMvcConfigurer 接口
@Configuration(proxyBeanMethods = false)
@Import(EnableWebMvcConfiguration.class) // 额外导入了其他配置
@EnableConfigurationProperties({ WebMvcProperties.class, WebProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware{
    
}

在这里插入图片描述

1.1.5.1.4、静态资源映射规则的源码
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware {

    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        if (!this.resourceProperties.isAddMappings()) {
            logger.debug("Default resource handling disabled");
        } else {
            this.addResourceHandler(registry, this.mvcProperties.getWebjarsPathPattern(), "classpath:/META-INF/resources/webjars/");
            this.addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> {
                registration.addResourceLocations(this.resourceProperties.getStaticLocations());
                if (this.servletContext != null) {
                    ServletContextResource resource = new ServletContextResource(this.servletContext, "/");
                    registration.addResourceLocations(new Resource[]{resource});
                }

            });
        }
    }

}

在这里插入图片描述

registration.setCachePeriod(getSeconds(this.resourceProperties.getCache().getPeriod()));
registration.setUseLastModified(this.resourceProperties.getCache().isUseLastModified());
registration.setCacheControl(this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl());

当浏览器访问了一个静态资源文件 index.js,如果在服务器中这个资源文件没有发生变化,那么在用户下次访问的时候,就可以直接让浏览器用本地缓存中的资源文件,而不用给服务器发送请求。

1.1.5.1.5、EnableWebMvcConfiguration 的源码

EnableWebMvcConfiguration 继承了 DelegatingWebMvcConfiguration,而 DelegatingWebMvcConfiguration 则继承了 WebMvcConfigurationSupport。

@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {

}

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(WebProperties.class)
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {
    
}

SpringBoot 默认会往容器中放入 WebMvcConfigurationSupport 组件,如果开发者注册了 WebMvcConfigurationSupport 组件,那么 SpringBoot 的 WebMvcAutoConfiguration 组件就会失效。这相当于禁用 SpringBoot 的自动配置,采用全手动的方式配置 MVC。

@AutoConfiguration(after = { DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class })
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class) // 容器中没有这个 Bean 才生效,默认就是没有
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@ImportRuntimeHints(WebResourcesRuntimeHints.class)
public class WebMvcAutoConfiguration {

}

在这里插入图片描述

1.1.5.1.6、为什么定义 WebMvcConfigurer 就能配置底层行为

在这里插入图片描述

/**
 * DelegatingWebMvcConfiguration 的底层源码
 */
@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {

	private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();


	@Autowired(required = false)
	public void setConfigurers(List<WebMvcConfigurer> configurers) {
		if (!CollectionUtils.isEmpty(configurers)) {
			this.configurers.addWebMvcConfigurers(configurers);
		}
	}

	@Override
	protected void configurePathMatch(PathMatchConfigurer configurer) {
		this.configurers.configurePathMatch(configurer);
	}

	@Override
	protected void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
		this.configurers.configureContentNegotiation(configurer);
	}

	...

}

1.1.5.2、全面接管 Spring MVC

SpringBoot 默认配置好了 Spring MVC 的所有常用特性,如果需要全面接管 Spring MVC 的所有配置并禁用默认配置,仅需要编写一个 WebMvcConfigurer 配置类,并标注 @EnableWebMvc 即可。

1.1.5.2.1、 WebMvcAutoConfiguration 自动配置了哪些规则

Spring MVC 自动配置场景给我们配置了如下所有默认行为:
在这里插入图片描述

1.1.5.2.2、 @EnableWebMvc 禁用默认行为

在这里插入图片描述

1.1.5.2.1、 WebMvcConfigurer 的功能

WebMvcConfigurer 定义并扩展了 Spring MVC 的底层功能,其中的功能列表如下:

<table><thead><tr><th>提供方法</th><th>核心参数</th><th>功能</th><th>默认</th></tr></thead><tbody><tr><td> addFormatters</td><td>FormatterRegistry</td><td> 格式化器:支持属性上 <code>@NumberFormat</code><code>@DatetimeFormat</code> 的数据类型转换</td><td> GenericConversionService</td></tr><tr><td>getValidator</td><td></td><td>数据校验:校验 Controller 上使用 <code>@Valid</code> 标注的参数合法性。需要导入 <code>spring-boot-starter-validator</code></td><td></td></tr><tr><td> addInterceptors</td><td>InterceptorRegistry</td><td> 拦截器:拦截收到的所有请求</td><td></td></tr><tr><td> configureContentNegotiation</td><td>ContentNegotiationConfigurer</td><td> 内容协商:支持多种数据格式返回。需要配合支持这种类型的 <code>HttpMessageConverter</code></td><td>支持 JSON</td></tr><tr><td>configureMessageConverters</td><td>List&lt;HttpMessageConverter&lt;?&gt;&gt;</td><td> 消息转换器:标注 <code>@ResponseBody</code> 的返回值会利用 <code>MessageConverter</code> 直接写出去</td><td>支持 8 种数据类型,包括 <code>byte</code><code>string</code><code>multipart</code><code>resource</code><code>json</code></td></tr><tr><td>addViewControllers</td><td>ViewControllerRegistry</td><td> 视图映射:直接将请求路径与物理视图映射。用于无 Java 业务逻辑的直接视图页渲染</td><td></td></tr><tr><td> configureViewResolvers</td><td>ViewResolverRegistry</td><td> 视图解析器:逻辑视图转为物理视图</td><td> ViewResolverComposite</td></tr><tr><td>addResourceHandlers</td><td>ResourceHandlerRegistry</td><td> 静态资源处理:静态资源路径映射、缓存控制</td><td> ResourceHandlerRegistry</td></tr><tr><td>configureDefaultServletHandling</td><td>DefaultServletHandlerConfigurer</td><td> 默认 <code>Servlet</code>:可以覆盖 Tomcat 的 <code>DefaultServlet</code>。让 <code>DispatcherServlet</code> 拦截 <code>/</code></td><td></td></tr><tr><td> configurePathMatch</td><td>PathMatchConfigurer</td><td> 路径匹配:自定义 URL 路径匹配。可以自动为所有路径加上指定前缀,比如 <code>/api</code></td><td></td></tr><tr><td> configureAsyncSupport</td><td>AsyncSupportConfigurer</td><td> 异步支持</td><td> TaskExecutionAutoConfiguration</td></tr><tr><td>addCorsMappings</td><td>CorsRegistry</td><td> 跨域支持</td><td></td></tr><tr><td> addArgumentResolvers</td><td>List<handlermethodargumentresolver></handlermethodargumentresolver></td><td> 参数解析器</td><td> MVC 默认提供</td></tr><tr><td> addReturnValueHandlers</td><td>List<handlermethodreturnvaluehandler></handlermethodreturnvaluehandler></td><td> 返回值解析器</td><td> MVC 默认提供</td></tr><tr><td> configureHandlerExceptionResolvers</td><td>List<handlerexceptionresolver></handlerexceptionresolver></td><td> 异常处理器</td><td> 3 个核心类:<code>ExceptionHandlerExceptionResolver</code><code>ResponseStatusExceptionResolver</code><code>DefaultHandlerExceptionResolver</code></td></tr><tr><td>getMessageCodesResolver</td><td></td><td>消息码解析器:国际化使用</td><td></td></tr></tbody></table>

值得注意的是
在这里插入图片描述

1.1.6、国际化

国际化的实现步骤如下:

在这里插入图片描述

常用配置
国际化的自动配置可参考 MessageSourceAutoConfiguration 自动配置类。

# 字符集编码
spring.messages.encoding=UTF-8
# 资源文件名的前缀
spring.messages.basename=messages

例如:

@Slf4j
@Controller
public class LoginController {

    /**
     * 获取国际化消息的组件
     */
    @Autowired
    public MessageSource messageSource;

    @GetMapping("/login")
    public String login(HttpServletRequest request) {
        Locale local = request.getLocale();
        // 通过代码的方式获取国际化配置文件中指定的配置项的值
        String login = messageSource.getMessage("login", null, local);
        log.info("login: {}", login);
        return "login";
    }

}

1.1.7、路径匹配

Spring 5.3 之后加入了更多的请求路径匹配的实现策略,以前只支持 AntPathMatcher 策略,现在额外提供了 PathPatternParser 策略(默认),并且支持指定使用哪种策略。
Ant 风格
在这里插入图片描述
值得注意的是

Ant 风格的路径模式语法中的特殊字符需要转义,如: 要匹配文件路径中的星号,则需要转义为 \* 要匹配文件路径中的问号,则需要转义为
\?

模式切换
在这里插入图片描述

@GetMapping("/a*/b?/{p1:[a-f]+}/**")
public String hello(HttpServletRequest request, @PathVariable("p1") String path) {
    log.info("路径变量: {}", path);
    return request.getRequestURI();
}

  • 换路径匹配策略,ant_path_matcher 是旧版策略,path_pattern_parser 是新版策略
# 切换路径匹配策略
spring.mvc.pathmatch.matching-strategy=ant_path_matcher

需要注意的是

SpringBoot 默认的路径匹配策略是由 PathPatternParser 提供的
如果路径中间需要有 **,则需要切换为 Ant 风格的路径匹配策略 AntPathMatcher

1.1.8、内容协商

在这里插入图片描述

1.1.8.1、 多端内容适配

在这里插入图片描述
引入支持输出 XML 内容的依赖

<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
</dependency>

标注 XML 注解

@JacksonXmlRootElement  // 支持输出 XML 格式的数据
@Data
public class Person {
    private Long id;
    private String userName;
    private String email;
    private Integer age;
}

编写控制器

import com.clay.boot.web.domain.Person;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping("/person")
public class PersonController {

    /**
     * 添加 @ResponseBody 注解
     */
    @ResponseBody
    @GetMapping("/get")
    public Person get() {
        Person person = new Person();
        person.setId(1L);
        person.setAge(18);
        person.setUserName("张三");
        person.setEmail("aaa@qq.com");
        return person;
    }

}

开启基于请求参数的内容协商

# 开启基于请求参数的内容协商功能,此功能默认不开启,默认的参数名为 format
spring.mvc.contentnegotiation.favor-parameter=true

# 指定内容协商时使用的参数名,默认的参数名为 format
spring.mvc.contentnegotiation.parameter-name=type

需要注意的是
SpringBoot 默认支持接口返回 JSON 数据,因为 Web 场景启动器默认引入了 Jackson 处理 JSON 的包。

1.1.8.2、配置协商规则与支持类型

更改内容协商方式

# 开启基于请求参数的内容协商功能,此功能默认不开启,默认的参数名为 format
spring.mvc.contentnegotiation.favor-parameter=true

# 指定内容协商时使用的参数名,默认的参数名为 format
spring.mvc.contentnegotiation.parameter-name=type

大多数 MediaType 都是开箱即用的,也可以自定义内容类型,如:

spring.mvc.contentnegotiation.media-types.yaml=text/yaml

自定义内容返回
创建使用YAML 内容协商策略

  • YAML 内容协商
<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-yaml</artifactId>
</dependency>

  • 新增一种媒体类型(MediaType),并开启基于请求参数的内容协商
# 新增一种媒体类型
spring.mvc.contentnegotiation.media-types.yaml=text/yaml

# 开启基于请求参数的内容协商功能,此功能默认不开启,默认的参数名为 format
spring.mvc.contentnegotiation.favor-parameter=true

# 指定内容协商时使用的参数名,默认的参数名为 format
spring.mvc.contentnegotiation.parameter-name=type

  • 编写可以支持 YAML 格式数据的 HttpMessageConverter
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;

import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;

public class YamlHttpMessageConverter extends AbstractHttpMessageConverter<Object> {

    private final ObjectMapper objectMapper;

    public YamlHttpMessageConverter() {
        // 指定支持的媒体类型
        super(new MediaType("text", "yaml", StandardCharsets.UTF_8));
        // 初始化YAML工具
        YAMLFactory yamlFactory = new YAMLFactory().disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER);
        this.objectMapper = new ObjectMapper(yamlFactory);
    }

    /**
     * 支持的类
     */
    @Override
    protected boolean supports(Class<?> clazz) {
        // TODO 只处理对象类型,不处理基本类型(如 int)
        return true;
    }

    /**
     * 处理方法参数(@RequestBody)
     */
    @Override
    protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        return null;
    }

    /**
     * 处理方法返回值(@ResponseBody)
     */
    @Override
    protected void writeInternal(Object returnValue, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
        try (OutputStream outputStream = outputMessage.getBody()) {
            this.objectMapper.writeValue(outputStream, returnValue);
        }
    }

}

  • 添加 HttpMessageConverter 组件,专门负责把返回值对象输出为 YAML 格式的数据
@Configuration
public class WebConfiguration implements WebMvcConfigurer {

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        // 添加一个支持将返回值对象转为 YAML 格式的 MessageConverter
        converters.add(new YamlHttpMessageConverter());
    }

}

如何自定义内容返回
在这里插入图片描述

1.1.8.3、内容协商原理

@ResponseBody 的底层由 HttpMessageConverter 处理数据,即标注了 @ResponseBody 的返回值,将会由支持它的 HttpMessageConverter 将数据返回给浏览器。

在这里插入图片描述

WebMvcAutoConfiguration 提供了 6 种 默认的 HttpMessageConverters
在这里插入图片描述

需要注意的是
SpringBoot 提供默认的 MessageConverter 功能有限,仅用于 JSON 或者普通的返回数据。如果需要增加新的内容协商功能,必须添加新的 HttpMessageConverter。

1.1.9、模板引擎

由于 SpringBoot 使用了嵌入式 Servlet 容器,所以 JSP 默认是不能使用的。如果需要服务端页面渲染,优先考虑使用模板引擎技术。
在这里插入图片描述
SpringBoot 默认包含了以下模板引擎的自动配置,模板引擎的页面默认放在 src/main/resources/templates 目录下。

  • FreeMarker
  • Thymeleaf
  • Mustache
  • Groovy
1.1.9.1、Thymeleaf 整合
  • 引入Maven
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

  • 添加配置内容(可选)
spring:
  thymeleaf:
    enabled: true
    cache: true
    mode: HTML
    suffix: .html
    encoding: UTF-8
    prefix: classpath:/templates/
    check-template-location: true

  • 编写 Controller 类,往模板文件中存放值
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.thymeleaf.util.StringUtils;

@Controller
public class WelcomeController {

    @GetMapping("/")
    public String welcome(@RequestParam(name = "name", required = false) String name, Model model) {
        if (StringUtils.isEmpty(name)) {
            name = "Thymeleaf";
        }
        model.addAttribute("name", name);
        return "welcome";
    }

}

  • 编写 HTML 模板页面,显示在 Controller 类中设置的值
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Welcome</title>
</head>
<body>
    <h1>Hello <span th:text="${name}"></span></h1>
</body>
</html>

  • 浏览器访问 http://127.0.0.1:8080/?name=Peter,显示的页面内容如下:
    在这里插入图片描述
    自动配置
    在这里插入图片描述
1.1.9.2、Thymeleaf 基础语法
1.1.9.2.1、核心用法

在这里插入图片描述

在这里插入图片描述

1.1.9.2.2、语法示例

在这里插入图片描述
以上所有语法都可以嵌套组合

1.1.9.2.3、集合遍历

在这里插入图片描述

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Welcome</title>
</head>
<body>
<table border="1" cellspacing="0">
    <thead>
        <tr>
            <th>ID</th>
            <th>姓名</th>
            <th>年龄</th>
            <th>邮箱</th>
        </tr>
    </thead>
    <tbody>
        <tr th:each="person : ${persons}">
            <td th:text="${person.id}"></td>
            <td th:text="${person.userName}"></td>
            <td th:text="${person.age}"></td>
            <td th:text="${person.email}"></td>
        </tr>
    </tbody>
</table>

<table border="1" cellspacing="0">
    <thead>
    <tr>
        <th>ID</th>
        <th>姓名</th>
        <th>年龄</th>
        <th>邮箱</th>
    </tr>
    </thead>
    <tbody>
        <tr th:each="person, iterStat : ${persons}" th:class="${iterStat.odd} ? 'odd'" th:index="${iterStat.index}">
            <td th:text="${person.id}"></td>
            <td th:text="${person.userName}"></td>
            <td th:text="${person.age}"></td>
            <td th:text="${person.email}"></td>
        </tr>
    </tbody>
</table>
</body>
</html>

1.1.9.2.4、条件判断

表达式判断

<tr th:each="person : ${persons}">
    <td th:text="${person.age >= 18 ? '成年人' : '未成年人'}"></td>
</tr>

th:if 判断

<a href="@{/comments.html}" th:if="${not #lists.isEmpty(prod.comments)}">view</a>

th:switch 判断

<div th:switch="${person.role}">
    <span th:case="pm">项目经理</span>
    <span th:case="admin">管理员</span>
    <span th:case="hr">HR</span>
    <span th:case="*">其他</span>
</div>

1.1.9.2.5、属性优先级

在这里插入图片描述

1.1.9.2.6、行内写法

语法:[[…]] or [(…)]

<p>[[${session.user.name}]]</p>

等同于

<p th:text="${session.user.name}"></p>

1.1.9.2.7、变量选择
<div th:object="${session.user}">
  <p>Name: <span th:text="*{firstName}">Sebastian</span>.</p>
  <p>Surname: <span th:text="*{lastName}">Pepper</span>.</p>
  <p>Nationality: <span th:text="*{nationality}">Saturn</span>.</p>
</div>

等同于

<div>
  <p>Name: <span th:text="${session.user.firstName}">Sebastian</span>.</p>
  <p>Surname: <span th:text="${session.user.lastName}">Pepper</span>.</p>
  <p>Nationality: <span th:text="${session.user.nationality}">Saturn</span>.</p>
</div>

1.1.9.2.8、模板布局

模板布局一般用于实现代码片段的重复使用。
在这里插入图片描述

  • 定义模板布局(如 footer.html)
<span th:fragment="copy">&copy; 2011 The Good Thymes Virtual Grocery</span>

  • 引用模板布局(如在 index.html 中引用)
<body>
  <div th:insert="~{footer :: copy}"></div>
  <div th:replace="~{footer :: copy}"></div>
</body>

  • 实现的效果(如 index.html 最终的渲染结果)
<body>
    <div>
        <span>&copy; 2011 The Good Thymes Virtual Grocery</span>
    </div>
    <span>&copy; 2011 The Good Thymes Virtual Grocery</span>
</body>

1.1.9.2.9、Devtools

SpringBoot 提供了 Devtools 工具用于代码的热加载,首先引入依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <optional>true</optional>
</dependency>

更改 Thymeleaf 的模板页面后,使用快捷键 Ctrl + F9 就可以让页面的更改立即生效。值得一提的是,对于 Java 代码的修改,如果 Devtools 热启动了,可能会引起一些 Bug,且难以排查。

1.1.10、新特性
1.1.10.1、Problemdetails

Problemdetails 实现了 RFC 7807 规范,用于返回新格式的错误信息。

  • ProblemDetailsExceptionHandler 是一个 @ControllerAdvice,用于集中处理系统异常
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(prefix = "spring.mvc.problemdetails", name = "enabled", havingValue = "true")
static class ProblemDetailsErrorHandlingConfiguration {

    @Bean
    @ConditionalOnMissingBean(ResponseEntityExceptionHandler.class)
    ProblemDetailsExceptionHandler problemDetailsExceptionHandler() {
        return new ProblemDetailsExceptionHandler();
    }

}

@ControllerAdvice
final class ProblemDetailsExceptionHandler extends ResponseEntityExceptionHandler {

}

  • ProblemDetailsExceptionHandler 默认可以处理以下异常,如果系统出现以下异常,会被 SpringBoot 以 RFC 7807 规范的方式返回错误数据
@ExceptionHandler({
    HttpRequestMethodNotSupportedException.class,
    HttpMediaTypeNotSupportedException.class,
    HttpMediaTypeNotAcceptableException.class,
    MissingPathVariableException.class,
    MissingServletRequestParameterException.class,
    MissingServletRequestPartException.class,
    ServletRequestBindingException.class,
    MethodArgumentNotValidException.class,
    NoHandlerFoundException.class,
    AsyncRequestTimeoutException.class,
    ErrorResponseException.class,
    ConversionNotSupportedException.class,
    TypeMismatchException.class,
    HttpMessageNotReadableException.class,
    HttpMessageNotWritableException.class,
    BindException.class
})

值得注意的是
ProblemDetails 功能默认是关闭的,需要手动开启

spring.mvc.problemdetails.enabled=true

ProblemDetails 启用后,当 SpringBoot 捕获到异常后,默认会响应 JSON 数据和返回 HTTP 状态码 405,且响应的 Header 是 Content-Type: application/problem+json

{
    "type": "about:blank",
    "title": "Method Not Allowed",
    "status": 405,
    "detail": "Method 'POST' is not supported.",
    "instance": "/list"
}

1.1.10.2、函数式 Web编程

Spring MVC 5.2 以后,允许使用函数式的方式定义 Web 的请求处理流程。

  • Web 请求处理的两种方式
    • @Controller + @RequestMapping:耦合式(路由和业务耦合)
    • @Controller + @RequestMapping:耦合式(路由和业务耦合)

使用场景
在这里插入图片描述
核心类

在这里插入图片描述
函数式编程例如
实体类

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class User {

    private Long id;
    private String userName;
    private String email;
    private Integer age;
    private String role;

}

定义路由信息

  • 此处将controller层变更为Router层,
  • 同时以Handlers代替Sevices层的业务管理(换了个名字,内部调用逻辑不变)
import com.clay.boot.web.biz.UserBizHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.servlet.function.RequestPredicate;
import org.springframework.web.servlet.function.RequestPredicates;
import org.springframework.web.servlet.function.RouterFunction;
import org.springframework.web.servlet.function.RouterFunctions;
import org.springframework.web.servlet.function.ServerResponse;

@Configuration
public class RoutingConfiguration {

    private static final RequestPredicate ACCEPT_ALL = RequestPredicates.accept(MediaType.ALL);
    private static final RequestPredicate ACCEPT_JSON = RequestPredicates.accept(MediaType.APPLICATION_JSON);

    @Bean
    public RouterFunction<ServerResponse> userRoute(UserBizHandler userBizHandler) {
        return RouterFunctions.route()
            .GET("/user/{id}", ACCEPT_ALL, userBizHandler::getUser)
            .GET("/users", ACCEPT_ALL, userBizHandler::listUser)
            .POST("/user", ACCEPT_JSON, userBizHandler::addUser)
            .PUT("/user/{id}", ACCEPT_JSON, userBizHandler::updateUser)
            .DELETE("/user/{id}", ACCEPT_ALL, userBizHandler::deleteUser)
            .build();
    }

}

业务处理 (此处做了全局返回信息处理,类似于R类的设计)

import com.clay.boot.web.domain.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.function.ServerRequest;
import org.springframework.web.servlet.function.ServerResponse;

import java.util.ArrayList;
import java.util.List;

@Slf4j
@Component
public class UserBizHandler {

    public ServerResponse getUser(ServerRequest serverRequest) throws Exception {
        Long id = Long.parseLong(serverRequest.pathVariable("id"));
        User user = new User(id, "Peter1", "peter@gmail.com", 18, "pm");
        return ServerResponse.ok().body(user);
    }

    public ServerResponse listUser(ServerRequest serverRequest) throws Exception {
        List<User> users = new ArrayList<>();
        users.add(new User(1L, "Peter1", "peter@gmail.com", 18, "pm"));
        users.add(new User(2L, "Peter2", "peter@gmail.com", 16, "admin"));
        users.add(new User(3L, "Peter3", "peter@gmail.com", 18, "pm"));
        return ServerResponse.ok().body(users);
    }

    public ServerResponse addUser(ServerRequest serverRequest) throws Exception {
        User user = serverRequest.body(User.class);
        log.info("user save success, {}", user.toString());
        return ServerResponse.ok().build();
    }

    public ServerResponse deleteUser(ServerRequest serverRequest) throws Exception {
        Long id = Long.parseLong(serverRequest.pathVariable("id"));
        log.info("user {} delete success", id);
        return ServerResponse.ok().build();
    }

    public ServerResponse updateUser(ServerRequest serverRequest) throws Exception {
        User user = serverRequest.body(User.class);
        Long id = Long.parseLong(serverRequest.pathVariable("id"));
        log.info("user {} update success, {}", id, user.toString());
        return ServerResponse.ok().build();
    }

}

1.3、数据访问

1.3.1、引入Mybatis整合依赖

在这里插入图片描述

1.3.2、配置数据源

在这里插入图片描述
第一行注释,编辑数据源
第二行注释,告诉Mybatis XML文件在哪里
第三行注释,告诉Mybatis mapper 接口文件在哪里(在主程序上添加@MapperScan(basePakage),或者在每一个Mapper接口文件上单独添加@Mapper注解)
第四行注释,将数据库中的user_name形式的命名转变为userName的驼峰命名

1.3.3、编写Mapper接口

此处可以安装MybatisX插件,
鼠标悬浮接口上右击生成对应的XML文件
鼠标悬浮方法上右击在对应的XML文件中生成SQL语句
在这里插入图片描述

1.3.4、小结

在这里插入图片描述

1.4、基础特性

1.4.1、SpringApplication
1.4.1.1、自定义 banner

推荐使用 Spring Boot Banner 在线生成工具,制作并下载英文 banner.txt 文件,然后将它放到项目的 /src/main/resources 目录下,这样就可以实现应用的个性化启动。

1.4.1.2、自定义 SpringApplication
import org.springframework.boot.Banner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MyApplication {

    public static void main(String[] args) {
        SpringApplication application = new SpringApplication(MyApplication.class);
        // 关闭Banner(此方式的配置优先级较低,低于配置文件)
        application.setBannerMode(Banner.Mode.OFF);
        application.run(args);
    }

}

上面的代码等同于以下配置内容,值得一提的是,配置文件的优先级更高

在这里插入代码片
# 关闭Banner
spring.main.banner-mode=off

1.4.1.3、FluentBuilder API

SpringBoot 支持以 Builder 方式构建 SpringApplication,通过 FluentBuilder API 设置应用属性。

import org.springframework.boot.Banner;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;

@SpringBootApplication
public class MainApplication {

    public static void main(String[] args) {
        new SpringApplicationBuilder()
            .main(MainApplication.class)
            .sources(MainApplication.class)
            .bannerMode(Banner.Mode.OFF) // 关闭Banner(此方式的配置优先级较低,低于配置文件)
            .run(args);
    }

}

1.4.2、Profiles 环境隔离
1.4.2.1、简单介绍

Profiles 提供环境隔离能力,支持快速切换开发、测试、生产环境,使用步骤如下:

1、指定环境:指定哪些组件、配置在哪个环境生效
2、激活环境:这个环境对应的所有组件和配置就应该生效

1.4.2.2、基础使用
1.4.2.2.1、指定环境

Spring Profiles 提供一种隔离配置的方式,使其仅在特定环境生效
任何 @Component、@Configuration 或 @ConfigurationProperties 都可以使用 @Profile 注解,来指定自身何时被加载。值得一提的是,Spring 容器中的组件都可以被 @Profile 注解标记。

@Profile({"dev"})
@Configuration
public class WebConfiguration {

}

@Profile({"prod", "test"})
@Configuration
public class SecurityConfiguration {

}

1.4.2.2.2、环境激活

在 application.properties 配置文件中,指定需要激活的环境

spring.profiles.active=dev

或者同时激活多个环境

spring.profiles.active=dev,test

值得注意的是

也可以使用命令行激活环境,如 java -jar xxxx.jar --spring.profiles.active=dev,test
还可以配置默认环境,即不标注 @Profile 注解的组件永远都会生效
以前默认环境叫 default
自定义默认环境 spring.profiles.default=test,
不推荐使用自定义默认环境的方式,而是推荐使用激活方式激活指定环境

1.4.2.2.3、环境包含

包含指定的环境,即不管激活哪个环境,包含指定的环境都会生效

spring.profiles.include=dev

即为各个环境所共有的值处理

1.4.2.2.4、小结
  • 生效的环境 = 激活的环境 / 默认环境 + 包含的环境
  • 企业项目里面的使用规则
    • 基础的配置内容,如 MyBatis、Log 写到包含环境
    • 需要动态切换变化的配置内容,如 DataBase、Redis 写到激活的环境中
1.4.2.3、Profiles 分组

创建 prod 分组,指定包含的 db 和 mq 配置。当使用命令行激活 java -jar xxx.jar --spring.profiles.active=prod ,就会激活 prod 分组,包括激活 db,mq 的配置文件。

spring.profiles.group.prod=db,mq

或者使用数组的写法

spring.profiles.group.prod[0]=db
spring.profiles.group.prod[1]=mq

1.4.2.4、Profiles 配置文件
  • application-{profile}.properties 可以作为指定环境的配置文件
  • 激活这个环境,对应的配置文件就会生效,最终生效的所有配置如下
    • application.properties 主配置文件,任意时候都会生效
    • application-{profile}.properties 指定环境配置文件,激活指定环境时会生效
    • 如果发生了配置冲突,默认以激活的环境配置文件为准,即 application-{profile}.properties 的优先级高于 application.properties

值得注意的是

spring.profiles.default、spring.profiles.active、spring.profiles.include 的配置信息只能写在 application.properties 中,如果写在 application-{profile}.properties 是无效的。

1.4.3、外部化配置

线上应用如何快速修改配置,并应用最新配置?

SpringBoot 使用 配置优先级 + 外部配置,可以简化配置更新、简化运维。 只需要往 Jar 应用所在的文件夹放一个
application.properties 最新配置文件,重启项目后就能自动应用最新的配置信息,无需重新编译打包代码。

1.4.3.1、配置优先级

SpringBoot 允许将配置外部化,以便可以在不同的环境中使用相同的应用程序代码。支持使用各种外部配置源,包括 Properties 文件、YAML 文件、环境变量和命令行参数。使用 @Value 注解可以获取到配置参数的值,也可以用 @ConfigurationProperties 注解将所有属性绑定到 POJO 中。
在这里插入图片描述
配置信息可以写在很多位置,常见的优先级顺序: 命令行 > 配置文件 > SpringApplication 配置。
在这里插入图片描述

优先级顺序:包外 > 包内; 同级情况:Profiles 配置 > Application 配置
建议使用同一种格式的配置文件。如果 xxx.properties 和 xxx.yml 同时存在,则 xxx.properties 的优先级更高
所有参数均可由命令行传入,使用 --参数项=参数值 格式,参数将会被添加到环境变量中,且优先级大于配置文件。比如 java -jar app.jar --name=“Spring”,可以使用 @Value(“${name}”) 获取参数值。

在这里插入图片描述

1.4.3.2、外部配置说明

SpringBoot 应用在启动的时候,会自动寻找 application.properties 和 application.yaml 配置文件,然后进行加载。配置文件的加载顺序如下(越往后优先级越高):
在这里插入图片描述

1.4.3.3、外部配置总结

在这里插入图片描述
在这里插入图片描述

1.4.3.4、导入配置使用

使用 spring.config.import 可以导入额外的配置。

spring.config.import=classpath:/my.properties
my.property=value

无论以上写法的先后顺序是怎样,my.properties 的值总是优先于直接在文件中编写的 my.property。

1.4.3.5、属性占位符使用

配置文件中可以使用 ${name:default} 形式取出之前配置过的值。

app.name=MyApp
app.description=${app.name} is a Spring Boot application written by ${username:Unknown}

1.4.4、JUnit 5 单元测试
1.4.4.1、整合单元测试

SpringBoot 提供一系列测试工具集及注解方便开发者进行测试。spring-boot-test 提供核心测试能力,spring-boot-test-autoconfigure 提供测试的一些自动配置。值得一提的是,一般只需要导入 spring-boot-starter-test 即可整合单元测试。

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

spring-boot-starter-test 默认提供了以下库供开发者进行单元测试使用:

  • JUnit 5
  • Spring Test
  • AssertJ
  • Hamcrest
  • Mockito
  • JSONassert
  • JsonPath
1.4.4.2、使用单元测试
1.4.4.2.1、组件测试

直接通过 @Autowired 注入容器中的组件即可进行测试。

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import static org.junit.jupiter.api.Assumptions.assumeTrue;

@SpringBootTest
public class StandardTests {

    @Autowired
    public User userService;

    @Test
    public void getUserById() {
        User user = userService.getById(1L);
        assumeTrue(user != null);
    }

}

单元测试类所在包的名称,必须与主启动类所在的包或者其子包的名称相同;否则单元测试类需要通过 @SpringBootTest 注解的 class 属性指定主启动类。

1.4.4.2.2、常用注解

JUnit5 的注解与 JUnit4 的注解有所区别,详细说明请参考 官方文档
在这里插入图片描述

import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.Assumptions.assumeTrue;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

@SpringBootTest
public class StandardTests {

    @BeforeAll
    static void initAll() {
    }

    @BeforeEach
    void init() {
    }

    @DisplayName("😱")
    @Test
    void succeedingTest() {
    }

    @Test
    void failingTest() {
        fail("a failing test");
    }

    @Test
    @Disabled("for demonstration purposes")
    void skippedTest() {
    }

    @Test
    void abortedTest() {
        assumeTrue("abc".contains("Z"));
        fail("test should have been aborted");
    }

    @AfterEach
    void tearDown() {
    }

    @AfterAll
    static void tearDownAll() {
    }

}

1.4.4.2.3、常用断言

根据详情选用
在这里插入图片描述

1.4.4.2.4、嵌套测试

JUnit 5 可以通过 Java 中的内部类和 @Nested 注解实现嵌套测试,从而可以更好的把相关的测试方法组织在一起。在内部类中可以使用 @BeforeEach 和 @AfterEach 注解,而且嵌套的层次没有限制。

package com.clay.boot;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import java.util.EmptyStackException;
import java.util.Stack;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

@SpringBootTest
@DisplayName("A stack")
public class TestingAStack {

    Stack<Object> stack;

    @Test
    @DisplayName("is instantiated with new Stack()")
    void isInstantiatedWithNew() {
        new Stack<>();
    }

    @Nested
    @DisplayName("when new")
    class WhenNew {

        @BeforeEach
        void createNewStack() {
            stack = new Stack<>();
        }

        @Test
        @DisplayName("is empty")
        void isEmpty() {
            assertTrue(stack.isEmpty());
        }

        @Test
        @DisplayName("throws EmptyStackException when popped")
        void throwsExceptionWhenPopped() {
            assertThrows(EmptyStackException.class, stack::pop);
        }

        @Test
        @DisplayName("throws EmptyStackException when peeked")
        void throwsExceptionWhenPeeked() {
            assertThrows(EmptyStackException.class, stack::peek);
        }

        @Nested
        @DisplayName("after pushing an element")
        class AfterPushing {

            String anElement = "an element";

            @BeforeEach
            void pushAnElement() {
                stack.push(anElement);
            }

            @Test
            @DisplayName("it is no longer empty")
            void isNotEmpty() {
                assertFalse(stack.isEmpty());
            }

            @Test
            @DisplayName("returns the element when popped and is empty")
            void returnElementWhenPopped() {
                assertEquals(anElement, stack.pop());
                assertTrue(stack.isEmpty());
            }

            @Test
            @DisplayName("returns the element when peeked but remains not empty")
            void returnElementWhenPeeked() {
                assertEquals(anElement, stack.peek());
                assertFalse(stack.isEmpty());
            }
        }
    }

}

1.4.4.2.5、参数化测试

参数化测试是 JUnit5 很重要的一个新特性,它使得用不同的参数多次运行测试成为了可能,也为单元测试带来许多便利。利用 @ValueSource 等注解,指定传入的参数,将可以使用不同的参数进行多次单元测试,而不需要每新增一个参数就新增一个单元测试,省去了很多冗余代码。

  • @ValueSource: 为参数化测试指定入参来源,支持八大基础类以及 String 类型,Class 类型
  • @NullSource: 表示为参数化测试提供一个 null 的入参
  • @EnumSource: 表示为参数化测试提供一个枚举入参
  • @CsvFileSource:表示读取指定 CSV 文件内容作为参数化测试入参
  • @MethodSource:表示读取指定方法的返回值作为参数化测试入参(注意方法的返回值需要是一个流)
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.junit.platform.commons.util.StringUtils;

import java.util.stream.Stream;

@SpringBootTest
@DisplayName("Input Param")
public class TestingInputParam {

    @ParameterizedTest
    @ValueSource(strings = {"one", "two", "three"})
    @DisplayName("参数化测试")
    public void parameterizedTest1(String string) {
        System.out.println(string);
        Assertions.assertTrue(StringUtils.isNotBlank(string));
    }

    @ParameterizedTest
    @MethodSource("method")
    @DisplayName("方法来源参数测试")
    public void testWithExplicitLocalMethodSource(String name) {
        System.out.println(name);
        Assertions.assertNotNull(name);
    }

    static Stream<String> method() {
        return Stream.of("apple", "banana");
    }

}

1.4.5、SpringBoot 事件驱动开发

SpringBoot 提供了基于事件驱动的编程模型,允许开发者将代码以应用程序与 IOC 容器之间的事件进行的方式来组织,实现松散耦合和高内聚。在 SpringBoot 事件驱动的过程中,当事件被触发时,IOC 容器会通过 ApplicationEventPublisher 发布事件。事件监听器通过实现 ApplicationListener 接口,并指定其感兴趣的事件类型来响应此事件。SpringBoot 事件驱动支持异步监听,可以在异步环境中执行事件响应函数,达到事件和响应函数的解耦。例如可以基于 SimpleAsyncTaskExecutor 执行器实现简单的异步处理。

1.4.5.1、事件驱动使用介绍

SpringBoot 事件驱动开发,依赖应用启动过程生命周期事件感知(9 大事件)、应用运行中事件感知(无数种)

  • 事件发布:实现 ApplicationEventPublisherAware 接口,或者注入 ApplicationEventMulticaster
  • 事件监听:实现 ApplicationListener 接口,或者使用 @EventListener 注解
    在这里插入图片描述
1.4.5.2、事件驱动使用场景
  • SpringBoot 事件驱动的常见使用场景

    • 系统级别:例如在应用启动或关闭时执行一些任务。
    • 业务处理:例如用户完成注册时,在后台发送电子邮件或短信等。
    • 更改数据或状态:例如在用户完成订单时,将订单数据写入数据库,并发送通知邮件。
  • SpringBoot 事件监听的常见使用场景

    • 对事件进行处理:如发送邮件,写日志,执行业务逻辑等。
    • 事件发布 / 订阅:多个组件可以监听同一事件,每个组件可以以自己独特的方式响应事件,如使用事件获取数据等。
    • 事件模型:自定义 SpringBoot 事件可以成为一个轻量级的消息传递系统。
1.4.5.1、事件驱动使用案例

这里将模拟用户成功登录后,通过 SpringBoot 的事件驱动模型,分别执行增加用户积分、发送优惠券、记录系统日志等操作。
实体类

@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class User {

    private String username;

    private String password;

}

控制器类

@Controller
@RequestMapping("/user")
public class LoginController {

    @Autowired
    private EventPublisher eventPublisher;

    @ResponseBody
    @PostMapping("/login")
    public String login(@RequestBody User user) {

        // 处理用户登录业务

        // 发送用户登录成功的事件
        LoginSuccessEvent event = new LoginSuccessEvent(user);
        eventPublisher.sendEvent(event);

        return "Login Success";
    }

}

自定义事件,继承 ApplicationEvent 类

public class LoginSuccessEvent extends ApplicationEvent {

    public LoginSuccessEvent(User user) {
        super(user);
    }

}

定义事件发送器,实现 ApplicationEventPublisherAware 接口,获取 IOC 容器中的 ApplicationEventPublisher

@Component
public class EventPublisher implements ApplicationEventPublisherAware {

    private ApplicationEventPublisher eventPublisher;

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.eventPublisher = applicationEventPublisher;
    }

    /**
     * 发送事件
     * @param event
     */
    public void sendEvent(ApplicationEvent event) {
        this.eventPublisher.publishEvent(event);
    }

}

接收事件,实现 ApplicationListener 接口(第一种方式)

@Slf4j
@Service
public class AccountService implements ApplicationListener<LoginSuccessEvent> {

    /**
     * 接收事件
     *
     * @param event
     */
    @Override
    public void onApplicationEvent(LoginSuccessEvent event) {
        User user = (User) event.getSource();
        addAccountScore(user);
    }

    /**
     * 增加用户积分
     *
     * @param user
     */
    public void addAccountScore(User user) {
        log.info("{} 加了 1 积分", user.getUsername());
    }

}

接收事件,使用 @EventListener 注解(第二种方式),可以通过 @Order 注解指定接收事件的顺序

@Slf4j
@Service
public class CouponService {

    /**
     * 接收事件
     *
     * @param event
     */
    @Order(1)
    @EventListener
    public void onEvent(LoginSuccessEvent event) {
        User user = (User) event.getSource();
        sendCoupon(user);
    }

    /**
     * 发送优惠券
     *
     * @param user
     */
    public void sendCoupon(User user) {
        log.info("{} 得到 1 张优惠券", user.getUsername());
    }

}

@Slf4j
@Service
public class SysLogService {

    /**
     * 接收事件
     *
     * @param event
     */
    @Order(2)
    @EventListener
    public void onEvent(LoginSuccessEvent event) {
        User user = (User) event.getSource();
        sendCoupon(user);
    }

    /**
     * 记录系统日志
     *
     * @param user
     */
    public void sendCoupon(User user) {
        log.info("{} 成功登录系统", user.getUsername());
    }

}

1.5、核心原理

依赖管理机制
在这里插入图片描述

为什么导入 spring-boot-starter-web 后,所有相关的依赖都导入进来了?

  • 开发什么场景,导入什么场景启动器
  • Maven 依赖的传递原则。A -> B -> C,那么 A 就拥有 B 和 C
  • 导入场景启动器后,会自动把这个场景的所有核心依赖全部导入进来

为什么版本号都不用写?

  • 每个 SpringBoot 项目都有一个父项目 spring-boot-starter-parent
  • parent 的父项目是 spring-boot-dependencies
  • 父项目是版本仲裁中心,会将所有常见 Jar 包的依赖版本都声明好了,比如 mysql-connector-j

如何自定义依赖的版本号?

  • 第一种方式:直接在当前 Maven 配置文件的 标签中声明父项目用的版本属性的 Key
  • 第二种方式:直接在导入依赖的时候声明版本
  • 上述两种方式都是利用 Maven 的就近原则特性

如何导入第三方的 Jar 包

  • 对于 spring-boot-starter-parent 没有管理的 Jar 包依赖,直接在 Maven 的配置文件中自行声明就可以
1.5.1、自动配置原理

SpringBoot 应用关注的三大核心:场景、配置、组件。

1.5.1、自动配置流程
1.5.1.1、初步理解
  • 自动配置 SpringMVC、Tomcat 等
    • 导入场景,容器中就会自动配置好这个场景的核心组件
    • 以前:DispatcherServlet、ViewResolver、CharacterEncodingFilter ….
    • 现在:自动配置好的这些组件
    • 验证:容器中有了什么组件,就具有什么功能
@SpringBootApplication
public class MainApplication {
    public static void main(String[] args) {
        // JDK 10 的新特性,局部变量类型的自动推断
        var ioc = SpringApplication.run(MainApplication.class, args);
        // 获取容器中所有组件的名称
        String[] names = ioc.getBeanDefinitionNames();
        // 遍历容器中的所有组件,SpringBoot 把以前需要手动配置的核心组件现在都给自动配置好
        for (String name : names) {
            System.out.println(name);
        }
    }
}

  • 默认的包扫描规则
    • @SpringBootApplication 注解标注的类就是主程序类
    • SpringBoot 只会扫描主程序类所在的包及其下面的子包,即自动的 component-scan 功能
    • 自定义包的扫描路径
      • 第一种方式:@SpringBootApplication(scanBasePackages = “com.clay”)
      • 第二种方式:@ComponentScan(“com.clay”) 直接指定扫描的包路径
  • 配置默认值
    • 配置文件的所有配置项是和某个类的对象值进行一一绑定的
    • 绑定了配置文件中每一项值的类,称为配置属性类
      比如:
      • ServerProperties 绑定了所有 Tomcat 服务器有关的配置
      • MultipartProperties 绑定了所有文件上传相关的配置
      • …… 参照 官方文档 或者参照绑定的配置属性类
  • 按需加载自动配置
    • 导入场景启动器 spring-boot-starter-web
    • 场景启动器除了会导入相关功能的依赖,还会导入 spring-boot-starter,它是所有 starter 的 starter,是基础核心 starter
    • spring-boot-starter 导入了一个包 spring-boot-autoconfigure,里面都是各种场景的 AutoConfiguration 自动配置类
    • 虽然全场景的自动配置都在 spring-boot-autoconfigure 这个包中,但并不是全部都默认开启的,导入哪个场景启动器才会开启哪个场景的自动配置

小结
导入场景启动器会触发 spring-boot-autoconfigure 这个包的自动配置生效,Spring 的 IOC 容器中就会具有相关场景的功能。

1.5.1.2、进阶理解

1、SpringBoot 是怎么实现导一个 starter,写一些简单配置,开发者无需关心整合,应用就能跑起来的?
2、为什么 Tomcat 的端口号可以配置在 application.properties 中,并且 Tomcat 能启动成功?
3、导入场景启动器后,哪些自动配置能生效?

在这里插入图片描述
在这里插入图片描述
小结

1、导入 spring-boot-starter-xxxx,会导入 spring-boot-starter,也就会导入 spring-boot-autoconfigure 包。
2、spring-boot-autoconfigure 包里面有一个文件 METAINF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,里面指定了应用启动时所有要加载的自动配置类。
3、@EnableAutoConfiguration 会自动地将上面文件里写的所有自动配置类都导入进来,同时 xxxAutoConfiguration 是有声明条件注解的,目的是按需加载。
4、xxxAutoConfiguration 往容器中导入一堆组件,这些组件都是从 xxxProperties 中获取属性值。
5、xxxProperties 又是和配置文件进行了绑定。

1.5.1.3、深入理解

在这里插入图片描述
SpringBoot 完整的生命周期启动加载流程如下:
在这里插入图片描述

1.5.1.2、SPI 机制介绍
  • Java 中的 SPI(Service Provider Interface)是一种软件设计模式,用于在应用程序中动态地发现和加载组件。SPI 的思想是,定义一个接口或抽象类,然后通过在 classpath 中定义实现该接口的类来实现对组件的动态发现和加载。
  • SPI 的主要目的是解决在应用程序中使用可插拔组件的问题。例如,一个应用程序可能需要使用不同的日志框架或数据库连接池,但是这些组件的选择可能取决于运行时的条件。通过使用 SPI,应用程序可以在运行时发现并加载适当的组件,而无需在代码中硬编码这些组件的实现类。
  • 在 Java 中,SPI 的实现方式是通过在 META-INF/services 目录下创建一个以服务接口全限定名为名字的文件,文件中包含实现该服务接口的类的全限定名。当应用程序启动时,Java 的 SPI 机制会自动扫描 classpath 中的这些文件,并根据文件中指定的类名来加载实现类。
  • 通过使用 SPI,应用程序可以实现更灵活、可扩展的架构,同时也可以避免硬编码依赖关系和增加代码的可维护性。
1.5.1.3、功能开关介绍
  • 自动配置:自动批量导入,全部都配置好,什么都不用管
    • 项目一启动,SPI 文件中指定的所有组件都会被加载
    • @EnableXxxx:手动导入,手动控制哪些功能的开启
  • 开启 xxx 功能
    • 都是利用 @Import 注解把此功能要用的组件导入进去
1.5.2、嵌入式容器

Servlet 容器指的是管理、运行 Servlet 组件(Servlet、Filter、Listener)的环境,一般指 Web 服务器。
自动配置原理
在这里插入图片描述

@AutoConfiguration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
		ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
		ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
		ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {
    
}

@Override
protected void onRefresh() {
    super.onRefresh();
    try {
        createWebServer();
    }
    catch (Throwable ex) {
        throw new ApplicationContextException("Unable to start web server", ex);
    }
}

值得注意的是

Web 场景的 Spring 容器启动,在调用 onRefresh() 的时候,会调用创建 Web 服务器的方法。
Web 服务器的创建是通过 WebServerFactory 实现的,容器中又会根据条件注解,启动相关的服务器配置,默认 EmbeddedTomcat 会往容器中放一个 TomcatServletWebServerFactory 组件,导致项目启动后,自动创建出 Tomcat 服务器。

内容协商基本原理

  • @ResponseBody 的底层由 HttpMessageConverter 处理数据,即标注了 @ResponseBody 的返回值,将会由支持它的 HttpMessageConverter 将数据返回给浏览器。
    在这里插入图片描述
  • WebMvcAutoConfiguration 提供了 6 种 默认的 HttpMessageConverters
    在这里插入图片描述

值得注意的是
SpringBoot 提供默认的 MessageConverter 功能有限,仅用于 JSON 或者普通的返回数据。如果需要增加新的内容协商功能,必须添加新的 HttpMessageConverter。

1.5.3、事件和监听器
1.5.3.1、生命周期监听
1.5.3.1.1、自定义监听器

SpringApplicationRunListener 负责监听应用的生命周期。

自定义 SpringApplicationRunListener 来监听应用生命周期的步骤

  • 编写 SpringApplicationRunListener 接口的实现类
  • 在项目的 /META-INF/spring.factories 中配置 org.springframework.boot.SpringApplicationRunListener=自定义的Listener,还可以指定一个有参构造方法,接受两个参数(SpringApplication application,String[] args)
  • 值得一提的是,SpringBoot 在 spring-boot.jar 中配置了默认的 Listener,即 org.springframework.boot.SpringApplicationRunListener=org.springframework.boot.context.event.EventPublishingRunListener
1.5.3.1.2、生命周期流程

在这里插入图片描述

SpringBoot 应用的生命周期流程

  • 首先从 /META-INF/spring.factories 读取到 Listener
  • 引导:利用 BootstrapContext 引导整个项目启动
    starting:应用开始,SpringApplication 的 run() 方法一调用,只要有了 BootstrapContext 就执行
    environmentPrepared:环境准备好(把启动参数等绑定到环境变量中),但是 IOC 容器还没有创建,此步骤只会执行一次
  • 启动
    contextPrepared:IOC 容器创建并准备好,但是 Sources(主配置类)还没加载,并关闭引导上下文,组件都没创建,此步骤只会执行一次
    contextLoaded:IOC 容器加载。主配置类加载进去了,但是 IOC 容器还没刷新(所有 Bean 还没创建),此步骤只会执行一次
    started:IOC 容器刷新了(所有 Bean 创建好了),但是 runner 没调用
    ready:IOC 容器刷新了(所有 Bean 创建好了),所有 runner 调用完了
  • 运行以上步骤都正确执行,代表容器成功运行
1.5.3.2、事件触发时机
1.5.3.2.1、各种事件监听器

在这里插入图片描述
在这里插入图片描述

如果想要在项目启动前做事:BootstrapRegistryInitializer 和 ApplicationContextInitializer
如果想要在项目启动完成后做事:ApplicationRunner 和 CommandLineRunner
如果要干涉整个生命周期做事:SpringApplicationRunListener
如果想要利用事件机制做事:ApplicationListener

1.5.3.2.2、完整事件触发流程

在这里插入图片描述
在这里插入图片描述

感知应用是否存活了:应用可能处于植物状态,虽然活着,但是不能处理请求。
应用是否就绪了:应用可以处理外部请求,说明确实活的比较好。
探针的使用场景:若应用将来部署到 Kubernetes 等平台,则会大量被使用到。

1.5.3.2.3、SpringBoot 事件驱动开发

SpringBoot 事件驱动开发,依赖应用启动过程生命周期事件感知(9 大事件)、应用运行中事件感知(无数种)。

  • 事件发布:实现 ApplicationEventPublisherAware 接口,或者注入 ApplicationEventMulticaster
  • 事件监听:实现 ApplicationListener 接口,或者使用 @EventListener 注解
    在这里插入图片描述

2、Spring Boot3 进阶(重点)

2.1、快速介绍

2.2、NoSQL

2.3、接口文档

2.4、远程调用

2.5、消息服务

2.6、Web安全

2.7、可观测性

2.8、AOT

2.9、SpringBoot3改变&新特性快速总结

3、Spring Boot 响应式编程全套(按需)-独立发布

3.1、Reactor核心

3.2、Spring WebFlux(响应式Web)

3.3、Spring Data R2DBC(响应式数据库)

3.4、Spring Data Reactive Redis(响应式NoSQL)

3.5、Spring Sercurity Reactive(响应式安全)

  • 5
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值