Spring Boot 异常处理

参考

一、全局异常处理

代码示例

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
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

/**
* Created with IntelliJ IDEA.
*
* @author Administrator
* Date: 2025/1/14
* Time: 12:37
* Description:全局异常处理类
*/
@RestControllerAdvice
public class GlobalRestExceptionHandler {

/**
* 处理异常
*
* @param ex 捕获异常类型
* @return 返回
*/
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleException(Exception ex) {
return new ResponseEntity<>("网络服务错误", HttpStatus.INTERNAL_SERVER_ERROR);
}

/**
* 处理算数运算异常:如除零错误
*
* @param ex 捕获异常类型
* @return 返回
*/
@ExceptionHandler(ArithmeticException.class)
public ResponseEntity<String> arithmeticException(ArithmeticException ex) {
return new ResponseEntity<>("算数运行异常", HttpStatus.INTERNAL_SERVER_ERROR);
}

}

@RestControllerAdvice 相关设置

优先级设置:存在多个处理器时,使用@Order注解,设置处理器类优先级,例如@Order(Ordered.HIGHEST_PRECEDENCE)

范围设置: 存在多个处理器,例如全局处理器和多个局部处理器时,应当妥善设置范围,避免出现多个处理器同时处理同一个请求。

  • 指定包范围: basePackages: 指定一个或多个包,这些包及其子包下的所有 Controller 都被管理。
    • @RestControllerAdvice(“com.example.controller") // 只管理 com.example.controller 包及其子包下的所有 Controller
    • @RestControllerAdvice(basePackages={"com.example.controller","com.example.controller2"}) // 只管理 com.example.controller 包及其子包下的所有 Controller 和 com.example.controller2 包及其子包下的所有 Controller
    • 指定@RestControllerAdvice(basePackageClasses={Controller1.class,Controller2.class}) 是 basePackages 的一种变形,指定一个或多个 Controller 类,这些类所属的包及其子包下的所有 Controller 都被管理
  • 指定类: assignableTypes 指定一个或多个 Controller 类,这些类被管理
  • 指定注解: annotations 指定一个或多个注解,被这些注解所标记的 Controller 会被管理

处理404错误

即使我们配置了全局异常处理,当出现404 not found等4xx错误时,依然会出现意外情况:

1
2
3
4
5
6
7
8
9
10
11
{
"code": 200,
"msg": "ok",
"data": {
"timestamp": "2023-08-23T17:01:15.102+00:00",
"status": 404,
"error": "Not Found",
"path": "/test/nullapi"
},
"timestamp": 1692810075116
}

解决方法:
application.yml配置文件增加以下配置项:

1
2
3
4
5
6
7
8
9
# 当HTTP状态码为4xx时直接抛出异常
spring:
mvc:
# 高版本这个已经弃用
throw-exception-if-no-handler-found: true
# 关闭默认的静态资源路径映射
web:
resources:
add-mappings: false

现在当我们再次请求一个不存在的接口时,404的话控制台会报NoHandlerFoundException异常,然后被全局异常处理捕获到并统一返回。

Spring Boot 有趣的配置

自定义Banner

  1. 创建一个banner.txt文件,放在resources目录下
  2. 在banner.txt中写上想要显示的Ascii画
  3. 还可以使用 Environment 中的任何key,以及一些占位符如${spring-boot.version}
  4. 重新启动应用,就发现默认的图案已经banner.txt中的内容了

Banner图案生成推荐

Spring Boot 热部署

Spring Boot 统一接口返回结果

参考

代码示例

  1. 定义返回结果枚举类

    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
    public enum ResultEnum {

    SUCCESS(200, "成功"),

    FAIL(500, "失败");

    private final int code;

    private String msg;

    ResultEnum(int code, String msg) {
    this.code = code;
    this.msg = msg;
    }

    public int getCode() {
    return code;
    }

    public String getMsg() {
    return msg;
    }

    public void setMsg(String msg) {
    this.msg = msg;
    }
    }
  2. 定义统一返回结果类

    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
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    public class Result<T> {
    private final Integer code;
    private final String msg;
    private T data;


    public Result(int code, String msg) {
    this.code = code;
    this.msg = msg;
    }


    /**
    * 创建成功返回结果
    *
    * @param data 返回数据
    * @param <T> 返回数据类型
    * @return 返回结果
    */
    public static <T> Result<T> success(T data) {
    ResultEnum resultEnum = ResultEnum.SUCCESS;
    return Result.create(resultEnum, data);
    }

    /**
    * 创建失败返回结果
    *
    * @param data 返回数据
    * @param msg 消息
    * @param <T> 返回数据类型
    * @return 返回结果
    */
    public static <T> Result<T> fail(T data, String msg) {
    ResultEnum resultEnum = ResultEnum.FAIL;
    resultEnum.setMsg(msg);
    return Result.create(resultEnum, data);
    }

    /**
    * 创建失败返回结果
    *
    * @param data 返回数据
    * @param <T> 返回数据类型
    * @return 返回结果
    */
    public static <T> Result<T> fail(T data) {
    ResultEnum resultEnum = ResultEnum.FAIL;
    return Result.create(resultEnum, data);
    }

    /**
    * 创建返回结果
    *
    * @param resultEnum 返回结果枚举
    * @param data 返回数据
    * @param <T> 返回数据类型
    * @return 返回结果
    */
    public static <T> Result<T> create(ResultEnum resultEnum, T data) {
    Result<T> result = new Result<T>(resultEnum.getCode(), resultEnum.getMsg());
    result.setData(data);
    return result;
    }


    public int getCode() {
    return code;
    }

    public String getMsg() {
    return msg;
    }

    public T getData() {
    return data;
    }

    public void setData(T data) {
    this.data = data;
    }
    }
  3. 统一返回结果拦截器

    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
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.core.MethodParameter;
    import org.springframework.core.annotation.Order;
    import org.springframework.http.MediaType;
    import org.springframework.http.converter.HttpMessageConverter;
    import org.springframework.http.server.ServerHttpRequest;
    import org.springframework.http.server.ServerHttpResponse;
    import org.springframework.lang.NonNull;
    import org.springframework.web.bind.annotation.RestControllerAdvice;
    import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

    /**
    * Created with IntelliJ IDEA.
    *
    * @author Administrator
    * Date: 2025/1/15
    * Time: 09:15
    * Description:SpringBoot优雅的统一返回格式
    */
    @RestControllerAdvice
    @Order(AdviceOrdered.GLOBAL_RESPONSE_HANDLER)
    public class RestfulResponseBodyAdvice implements ResponseBodyAdvice<Object> {

    @Autowired
    private ObjectMapper objectMapper;
    /**
    * @param returnType the return type
    * @param converterType the selected converter type
    * @return 判断是否拦截
    */
    @Override
    public boolean supports(@NonNull MethodParameter returnType, @NonNull Class<? extends HttpMessageConverter<?>> converterType) {
    return true;
    }

    /**
    * @param o the body to be written
    * @param returnType the return type of the controller method
    * @param selectedContentType the content type selected through content negotiation
    * @param selectedConverterType the converter type selected to write to the response
    * @param request the current request
    * @param response the current response
    * @return 最终返回对象
    */
    @Override
    public Object beforeBodyWrite(Object o, @NonNull MethodParameter returnType, @NonNull MediaType selectedContentType, @NonNull Class<? extends HttpMessageConverter<?>> selectedConverterType, @NonNull ServerHttpRequest request, @NonNull ServerHttpResponse response) {

    //如果Controller返回String的话,SpringBoot不会帮我们自动封装而直接返回,因此我们需要手动转换成json。
    if (o instanceof String) {
    try {
    return objectMapper.writeValueAsString(Result.success(o));
    } catch (JsonProcessingException e) {
    throw new RuntimeException(e);
    }
    }

    //如果返回的结果是R对象,即已经封装好的,直接返回即可。
    //如果不进行这个判断,后面进行全局异常处理时会出现错误
    if (o instanceof Result<?>) {
    return o;
    }

    //如果返回的是错误,则返回失败
    if (o instanceof Exception) {
    return Result.fail(o);
    }

    return Result.success(o);
    }
    }

即使我们配置了全局接口统一,当出现404 not found等4xx错误时,依然会出现意外情况:查看

SpringBoot 集成日志

参考

快速开始

  1. 添加依赖
  • 添加spring-boot-starter-logging依赖。
  • 如果pom包含了任何spring-boot-starter的依赖,如spring-boot-starter-web则不需要添加任何依赖。因为已经默认集成了spring-boot-starter-logging
  1. 简单使用代码示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @RestController
    class HelloController {

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

    @GetMapping("/")
    public String hello() {
    logger.info("处理'/'请求");
    return "Hello, World!";
    }
    }

配置LogBack

  • 配置文件位置:src/main/resources/logback-spring.xml,不存在则新建。
  • 基本配置示例:下面的配置效果和没有创建时,使用默认配置一样。
    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
    <configuration>

    <springProfile name="dev">
    <!-- appender 定义日志输出的位置和方式
    name="CONSOLE" 自定义唯一标识符,用来被root/logger引用
    class="ch.xxx" 指定了具体实现类,必须提供一个有效的、完全限定的类名。
    -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
    <!-- 定义如何格式化日志事件 -->
    <encoder>
    <!--pattern 用来指定日志输出的具体格式。
    %d{yyyy-MM-dd HH:mm:ss} 表示日志事件的时间戳。这里的 {yyyy-MM-dd HH:mm:ss} 指定了日期格式。
    %-5level:表示日志级别,并且宽度固定为5个字符(左对齐)
    [%thread]:表示生成日志的线程名称,通常对于多线程应用非常有用,可以帮助区分不同线程产生的日志。
    %logger{36}:表示记录器的名字,这里 {36} 表示最长截取36个字符。如果记录器的名字超过这个长度,则会被截断。
    %msg:表示日志的实际消息内容。
    %n:表示换行符,确保每条日志都从新的一行开始。
    -->
    <pattern>%d{yyyy-MM-dd HH:mm:ss} %-5level [%thread] %logger{36} - %msg%n</pattern>
    </encoder>
    </appender>

    <!-- 设置root logger的级别为INFO,并将其与CONSOLE appender关联 -->
    <root level="INFO">
    <appender-ref ref="CONSOLE"/>
    </root>
    </springProfile>

    </configuration>
  • 将日志写入到文件示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <!-- 定义一个名为 FILE 的 appender -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <!-- 日志文件路径 -->
    <file>logs/error.log</file>

    <!-- 滚动策略 -->
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
    <!-- 每天生成一个新的日志文件,并且旧文件以日期为后缀命名 -->
    <fileNamePattern>logs/error-%d{yyyy-MM-dd}.log</fileNamePattern>
    <!-- 保留30天的日志文件 -->
    <maxHistory>30</maxHistory>
    </rollingPolicy>

    <encoder>
    <pattern>%d{yyyy-MM-dd HH:mm:ss} %-5level [%thread] %logger{36} - %msg%n</pattern>
    </encoder>
    </appender>

日志配置基础概念讲解

  • Appender:Appender是一个核心概念,它负责将日志事件输出到不同的目的地。换句话说,Appender定义了日志输出的位置和方式。通过Appender,你可以控制日志是输出到控制台、文件、数据库,还是通过网络发送等。
    • ConsoleAppender:将日志输出到控制台。这是最常用的Appender之一,适合开发环境中的快速调试。
    • FileAppender:将日志写入到文件中。适用于需要持久化存储日志信息的场景。
    • RollingFileAppender:扩展自FileAppender,增加了滚动功能,即当日志文件达到一定大小或符合某个时间条件时,会自动创建一个新的日志文件。这有助于管理日志文件的大小和数量,避免单个日志文件过大。
    • AsyncAppender:异步地将日志事件转发给其他Appender。通过使用独立线程来处理日志事件,它可以减少日志记录对应用程序性能的影响。

标签

  • <configuration>:这是Logback的根标签,用于定义整个配置。
  • <springProfile>: 用于指定一个或多个Spring环境,只有当当前环境与指定的环境匹配时,才应用该配置。
  • <appender-ref>:用于引用一个已定义的Appender,并将其与Logger关联起来。
  • <appender>:用于定义一个Appender,它负责将日志事件输出到不同的目的地。
  • <root>:用于定义根Logger,它负责处理未匹配到其他Logger的日志事件。
  • <logger>:用于定义一个特定的包或类,并指定它的日志级别和Appender。

Git 安装与配置

参考

Windows 安装Git

安装Git

  1. 下载Git安装包,下载地址,Git-2.47.1-64-bit.exe
  2. 运行安装包,选择对应配置,安装即可。建议修改默认安装位置,如改为D:\software\profession\Git

安装Git的可视化工具

  1. 下载TortoiseGit安装包,如TortoiseGit-2.17.0.2-64bit.msi
  2. 运行安装包,选择对应配置,安装即可。建议修改默认安装位置,如改为D:\software\profession\TortoiseGit
  3. 安装TortoiseGit语言包,如TortoiseGit-LanguagePack-2.17.0.0-64bit-zh_CN.msi

配置Git远程仓库身份认证

  1. 创建Putty 密钥
    1. 打开PuttyGen,点击Generate,随意移动鼠标,直到生成密钥。
    2. 记录生成的RSA公钥,例如
      1
      ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDKFkWEBCEBXIBe0xDZS+3RCmgmXKL0/kJC/eqrx169Y7oSp5K/koXlQylS//98ByjBWgnDSBiIJBSStK+Oc9W+um7l/BZziofutMvQJuhOaG3cx6iAXrvrkX7fn+RH95ooSVunOvUph6/YAHzG8Os+WYDBO5PbhLWDLwn5Ut7fNLy1v2aEk/5+O5yt0GMQPCslZ9lom7/rA1R3RfkSvkN/Xm/5ngu+S5s28HbX7ge4sSx9E1XcqwF/R4p406uMPBEfIFIE0m1axgY3Tz2NdEPW6LSvgU360KBifHJ5DEt+IRbiZFjLVOI2NvC0PNFjeKLcmlWg/2WtMD3JM02LLbrd rsa-key-20250108
    3. 点击Save private key保存生成的私钥。
  2. Git仓库配置公钥
    1. 登录远程仓库,点击Settings,找到SSH and GPG keys,点击New SSH key,输入Title,粘贴生成的RSA公钥,点击Add SSH key
    2. 配置Git仓库公钥
    3. 本地TortoiseGit拉取和推送时,选择使用的私钥即可。

IDEA 使用技巧

参考

常用设置

自动生成头部文档注释

  1. 打开 File(文件)->settings(设置)->Editor(编辑器)->File and Code Templates(文件和代码模版)
  2. 选择要修改的文件类型如:Class
  3. 修改默认文件模版,如:
    1
    2
    3
    4
    5
    6
    7
    8
    /**
    * Created with IntelliJ IDEA.
    * @author ${USER}
    * Date: ${DATE}
    * Time: ${TIME}
    * Description:
    *
    */

常用插件

  • Alibaba Java Coding Guidelines:阿里Java开发规范
  • Private Notes:私有笔记-给源码添加注释的插件

Spring Boot 热部署

  1. 打开设置界面
    • 新项目设置: 文件(File)->新建项目设置(New Projects Settings)->新项目的设置(Settings for New Projects)
    • 当前项目设置: 文件(File)->设置(settings)
  2. 启用自动构建项目:构建,执行,部署(Build,Execution,Deployment)->编译器(Compiler)->勾选 自动构建项目(Build project automatically)
  3. 开启热部署策略
    1. pom.xml中添加依赖
      1
      2
      3
      4
      5
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-devtools</artifactId>
      <scope>runtime</scope>
      </dependency>
    2. 设置当前热部署策略
      1. 打开运行/调试配置界面: Run(运行)/Debug(调试)/Edit Configurations(编辑配置)
      2. 点击修改选项
      3. 修改选项界面中,找到执行更新操作时选项,并勾选更新类和资源
  4. 使用热部署
    • 配置热部署完成后
    • 修改某个类后
      • Ctrl+Shift+F9单独编译这个类就可以进行手动更新。
      • Ctrl+Shift+B重新构建项目。

Java Record类

参考

Record类 环境要求

  • Java 16+

Record类 概述

  • Record类是Java 16中引入的新特性,它允许我们创建一个类,该类具有字段和构造函数,并且具有自动生成的getter方法。
  • Record类是final类,不能被继承。
  • Record为处理纯数据类提供了一种更加清晰、简洁的方法,减少了样板代码的数量,使得代码更加易读和维护。

Vim 教程

参考

为什么使用Vim

  • Linux系统中大多都预装了Vim,Windows中Git安装时默认安装的文本编辑器也是Vim。
  • Vim占用资源少,适合低性能设备和远程服务器上使用。

Vim基本介绍

  • Vim工作模式分为:普通模式,输入模式,命令行模式
  • vim [filename]打开文件,默认进入普通模式
  • 普通模式通过i进入输入模式,通过:进入命令行模式,其他模式下都可以通过Esc进入普通模式

Vim模式介绍

普通模式

  • :进入命令行模式,可以执行命令,如:w保存文件,:q退出文件。
  • i进入输入模式,可以输入文本。

输入模式

  • Esc:进入普通模式。
  • 字符按键以及Shift组合,输入字符

命令行模式

  • :w:保存文件。
  • :q:退出 Vim 编辑器。
  • :wq:保存文件并退出 Vim 编辑器。
  • :q!:强制退出Vim编辑器,不保存修改。

Vim常用命令

移动光标

  • gg:移动到文件顶部。
  • G:移动到文件底部。
  • 0:移动到行首。
  • $:移动到行尾。
  • n<Enter>:n 为数字。光标向下移动 n 行。
  • n<space>: n 为数字。光标会向后面移动 n 个字符距离。

搜索替换

  • /[world]:搜索向光标之下寻找字符串。
  • ?[world]:搜索向光标之上寻找字符串。
  • :n1,n2s/word1/word2/g: n1,n2为行号,word1为要替换的字符串,word2为替换的字符串,g为替换所有匹配的字符串。

剪辑粘贴

  • yy:复制当前行。
  • p:粘贴。
  • dd:剪辑当前行。
  • u: 撤销。
  • [Ctrl+r]: 重做。

Docker常用命令

参考

镜像命令

  • docker images 查看所有镜像
  • docker save -o nginx.tar nginx:1.27.3 导出镜像为文件
  • docker load 导入(本地)镜像 -i <文件地址> 例如:docker load -i E:\registry.tar
  • docker rmi 删除镜像
  • docker image prune 删除未使用的镜像
  • docker image inspect 镜像详情

仓库命令

  • docker tag mysql:latest localhost:5000/mysql:latest 给镜像打上私有仓库的标签
  • docker push your-private-registry-ip:5000/your-repo-name:tag 推送镜像到私有仓库

容器命令

  • docker ps 查看容器(运行中的) -a 查看所有容器

docker compose 常用命令

  • docker-compose up 启动容器

查看信息

-