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错误时,依然会出现意外情况:查看

Spring Web MVC 教程

参考

快速开始

  1. 创建一个Spring Boot项目,并添加Spring Web MVC依赖。
    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
  2. 创建一个控制器类,并使用@Controller相关的注解标注。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RestController;

    @RestController
    public class TestController {

    @PostMapping("/hello")
    public String hello() {
    return "hello Spring Boot!";
    }
    }
  3. 启动项目,访问http://localhost:8080/hello,可以看到返回结果。

常用注解解析

  1. 控制器注解

    • 位置:
    • 常用注解
      • @Controller:用于标注控制层组件,即处理器(Handler)。
      • @RestController:用于标注控制层组件,即处理器(Handler),并且返回的数据直接写入 HTTP 响应体中,一般用于 Restful 服务。
  2. 路由注解

    • 位置:方法
    • 作用:指定请求的地址和请求方法
    • 常用注解
      • @RequestMapping:用于映射 HTTP 请求,支持 GET、POST、PUT、DELETE 等请求方法。
      • @GetMapping:用于映射 HTTP GET 请求。
      • @PostMapping:用于映射 HTTP POST 请求。
  3. 参数注解

    • 位置:参数
    • 作用:用于获取请求参数
    • 常用注解
      • @PathVariable:用于获取 URL 中的变量,如 /user/{id} 中的 {id}。
      • @RequestParam:用于获取请求参数,如 ?name=John 中的 name。
      • @RequestBody:用于获取请求体中的数据,如 POST 请求的 JSON 数据。

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。

Gradle 安装配置教程

参考

安装Java环境

  1. 下载需要版本的JDK安装包,推荐LTS版本(8,11,17,21)。下载地址:OpenJDK
  2. 解压安装包,并配置环境变量。

安装

  1. 前往官网下载链接下载合适版本的Grdle。
  2. 下载后解压到任意位置,如D:\software\profession\Gradle\gradle-8.0.2
  3. 配置环境变量
    • 新建GRADLE_HOME变量,值为解压路径。如:D:\software\profession\Gradle\gradle-8.0.2
      %GRADLE_HOME%\bin添加到 Path 环境变量中,然后点击确定
    • 新建GRADLE_USER_HOME变量,值为本地仓库地址,
      D:\software\profession\Gradle\repository
  4. 验证gradle是否安装成功,打开cmd命令行输入gradle -v

配置国内镜像源

全局配置镜像源

  1. 找到/创建gradle.properties
    • Windows: %GRADLE_USER_HOME%\gradle.properties。如果未配置GRADLE_USER_HOME,则默认地址为C:\Users\<YourUsername>\.gradle
  2. 在gradle.properties中添加镜像源,如:
    1
    2
    3
    4
    5
    6
    # Alibaba Maven 
    repositories.grails.default = https://maven.aliyun.com/repository/public
    # Tsinghua
    repositories.grails.default.1 = https://mirrors.tuna.tsinghua.edu.cn/maven/repos/public
    # Central
    repositories.grails.default.2 = https://repo.maven.apache.org/maven2

Gradle 项目配置

  • 配置项目distributionUrl为本地地址:有时候总是从官网下载最新版,而本地已经下载了,所以需要配置本地地址。
    在项目根目录下的gradle/wrapper/gradle-wrapper.properties中配置distributionUrl为本地现在的gradle压缩包,例如file:///D:/software/profession/Gradle/distributions/gradle-8.9-bin.zip

Gradle 资源列表

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重新构建项目。