对于个人站长的后端服务(通常运行在VPS或公有云虚拟机上),提供稳定且友好的API接口至关重要。当服务发生错误时,如果不进行统一处理,客户端可能会收到各种不规范的错误页面或堆栈信息,这既影响用户体验,也存在安全隐患。
Spring Framework提供了强大的@ControllerAdvice注解,允许我们集中处理跨越所有@Controller或@RestController的逻辑,最主要的应用就是全局异常处理和模型数据注入。
1. 什么是@ControllerAdvice?
@ControllerAdvice(或@RestControllerAdvice,后者默认包含了@ResponseBody,常用于RESTful API)是一个特殊的Spring组件。它允许你在应用层面上拦截请求,对所有Controller进行统一的配置和处理。
2. 实现全局异常处理
我们将定义一个标准的错误响应体,并创建一个全局异常处理器来捕获并格式化所有抛出的异常。
步骤 2.1:定义统一响应结构
首先,我们创建一个通用的返回对象Result,用于封装成功和失败的响应。
public class Result<T> {
private int code; // 业务状态码
private String message; // 错误或成功信息
private T data;
public Result(int code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}
public static <T> Result<T> success(T data) {
return new Result<>(200, "操作成功", data);
}
public static Result<?> failure(int code, String message) {
return new Result<>(code, message, null);
}
// Getters and Setters omitted for brevity
}
步骤 2.2:创建全局异常处理器
使用@RestControllerAdvice注解标记该类,并使用@ExceptionHandler来指定该方法需要处理的异常类型。
package com.example.vpsdemo.advice;
import com.example.vpsdemo.dto.Result;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* 全局统一异常处理器
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
// 1. 处理自定义业务异常(例如:用户不存在、参数错误)
@ExceptionHandler(IllegalArgumentException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST) // 设置HTTP状态码为400
public Result<?> handleIllegalArgumentException(IllegalArgumentException e) {
System.err.println("捕获到参数非法异常: " + e.getMessage());
return Result.failure(40001, e.getMessage());
}
// 2. 处理Spring框架的参数校验异常(@Valid或@Validated失败时抛出)
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Result<?> handleValidationException(MethodArgumentNotValidException e) {
String defaultMessage = e.getBindingResult().getFieldError().getDefaultMessage();
System.err.println("捕获到参数校验异常: " + defaultMessage);
return Result.failure(40002, "参数校验失败: " + defaultMessage);
}
// 3. 处理所有未捕获的通用异常(兜底处理)
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) // 设置HTTP状态码为500
public Result<?> handleGeneralException(Exception e) {
e.printStackTrace(); // 记录详细堆栈信息
return Result.failure(50000, "服务器内部错误,请联系管理员。错误信息: " + e.getMessage());
}
}
步骤 2.3:测试异常处理
创建一个简单的Controller来触发异常:
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestController {
@GetMapping("/test/error")
public Result<String> triggerError(@RequestParam String id) {
if (id.length() < 5) {
// 抛出将被全局捕获的异常
throw new IllegalArgumentException("ID长度不足5位");
}
return Result.success("验证成功");
}
}
访问 /test/error?id=123 后,全局异常处理器会捕获 IllegalArgumentException,并返回统一的JSON格式响应。
3. 利用@ControllerAdvice注入共有属性(Model属性)
除了异常处理,@ControllerAdvice还可以配合@ModelAttribute在所有Controller的方法执行前,向Model中注入公共属性。这在需要为所有视图模板(如Thymeleaf或JSP)提供相同的全局配置或用户信息时非常有用。
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ModelAttribute;
@ControllerAdvice
public class CommonAttributeAdvice {
/**
* 在所有Controller方法执行前,将属性注入到Model中
*/
@ModelAttribute("globalConfig")
public String addGlobalConfig() {
// 假设这里获取了站点的版本号或ICP备案信息
return "Version 1.0.0 | Powered by VPS Tech Blog";
}
}
这样,在任何视图模板中,你都可以直接通过 globalConfig 访问到这个值。
汤不热吧