文章目录

    • 1. 异常处理基础概念
      • 1.1 什么是异常处理
      • 1.2 为什么需要统一异常处理
      • 1.3 Spring异常处理机制
    • 2. SpringBoot默认异常处理
      • 2.1 默认错误页面
      • 2.2 自定义错误页面
    • 3. 全局异常处理器
      • 3.1 基础全局异常处理器
      • 3.2 统一响应格式
      • 3.3 使用统一响应格式的异常处理器
    • 4. 自定义异常
      • 4.1 创建基础自定义异常
      • 4.2 具体的业务异常类
      • 4.3 错误码枚举
      • 4.4 使用错误码的异常类
    • 5. 完整的异常处理体系
      • 5.1 完整的全局异常处理器
      • 5.2 改进的Result类
    • 6. 实际应用示例
      • 6.1 用户服务示例
      • 6.2 用户服务类
      • 6.3 用户控制器
      • 6.4 登录请求DTO
    • 7. 日志记录和监控
      • 7.1 异常日志记录
      • 7.2 增强的异常处理器
      • 7.3 异常统计和监控
    • 8. 配置和优化
      • 8.1 异常处理配置
      • 8.2 配置属性类
      • 8.3 异常处理配置类
    • 9. 测试异常处理
      • 9.1 异常处理单元测试
      • 9.2 集成测试
    • 10. 最佳实践
      • 10.1 异常处理设计原则
      • 10.2 异常分类策略
      • 10.3 异常处理最佳实践
      • 10.4 异常处理建议
    • 11. 总结
      • 11.1 核心知识点回顾
      • 11.2 应用场景
      • 11.3 学习建议

1. 异常处理基础概念

1.1 什么是异常处理

异常处理是程序在运行过程中遇到错误时的处理机制。在Web应用中,异常处理负责捕获和处理各种异常情况,并向客户端返回友好的错误信息。

形象比喻:
想象异常处理就像医院的急诊科:

  • 分诊台(@ExceptionHandler):根据病情类型分配到不同科室
  • 专科医生(具体异常处理器):针对特定问题进行专业处理
  • 病历记录(日志记录):记录处理过程和结果
  • 出院小结(统一响应格式):给病人明确的诊断和建议

1.2 为什么需要统一异常处理

  1. 用户体验:避免直接暴露技术错误信息给用户
  2. 安全性:防止敏感信息泄露(如数据库结构、系统路径等)
  3. 维护性:集中处理异常逻辑,便于维护和修改
  4. 一致性:确保所有错误响应格式统一
  5. 监控性:便于日志记录和错误追踪

1.3 Spring异常处理机制

Spring提供了多种异常处理方式:

  1. @ExceptionHandler:方法级异常处理
  2. @ControllerAdvice:全局异常处理
  3. @RestControllerAdvice:REST接口全局异常处理
  4. HandlerExceptionResolver:底层异常解析器

2. SpringBoot默认异常处理

2.1 默认错误页面

SpringBoot默认提供了基本的异常处理机制:

package com.example.controller;import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;/*** 测试控制器 - 演示默认异常处理*/
@RestController
@RequestMapping("/api/test")
public class TestController {/*** 模拟空指针异常*/@GetMapping("/null-pointer")public String nullPointerException() {String str = null;return str.length() + ""; // 这里会抛出NullPointerException}/*** 模拟数组越界异常*/@GetMapping("/array-index")public String arrayIndexException() {int[] array = {1, 2, 3};return array[5] + ""; // 这里会抛出ArrayIndexOutOfBoundsException}/*** 模拟除零异常*/@GetMapping("/divide-zero")public String divideByZeroException() {int result = 10 / 0; // 这里会抛出ArithmeticExceptionreturn result + "";}/*** 模拟参数类型错误*/@GetMapping("/users/{id}")public String getUserById(@PathVariable Long id) {return "User ID: " + id;}
}

当访问这些端点时,SpringBoot会返回默认的错误信息,但这些信息对用户不够友好。

2.2 自定义错误页面

可以通过配置文件定制默认错误页面:

# application.yml
server:error:include-message: alwaysinclude-binding-errors: alwaysinclude-stacktrace: on_paraminclude-exception: falsepath: /error

3. 全局异常处理器

3.1 基础全局异常处理器

package com.example.exception;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;/*** 全局异常处理器* @RestControllerAdvice 是 @ControllerAdvice + @ResponseBody 的组合* 用于处理所有Controller抛出的异常*/
@RestControllerAdvice
public class GlobalExceptionHandler {private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);/*** 处理空指针异常*/@ExceptionHandler(NullPointerException.class)@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)public Map<String, Object> handleNullPointerException(NullPointerException e) {logger.error("空指针异常: ", e);Map<String, Object> result = new HashMap<>();result.put("code", 500);result.put("message", "系统内部错误,请稍后重试");result.put("timestamp", LocalDateTime.now());result.put("success", false);return result;}/*** 处理数组越界异常*/@ExceptionHandler(ArrayIndexOutOfBoundsException.class)@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)public Map<String, Object> handleArrayIndexOutOfBoundsException(ArrayIndexOutOfBoundsException e) {logger.error("数组越界异常: ", e);Map<String, Object> result = new HashMap<>();result.put("code", 500);result.put("message", "数据访问越界");result.put("timestamp", LocalDateTime.now());result.put("success", false);return result;}/*** 处理算术异常(如除零异常)*/@ExceptionHandler(ArithmeticException.class)@ResponseStatus(HttpStatus.BAD_REQUEST)public Map<String, Object> handleArithmeticException(ArithmeticException e) {logger.error("算术异常: ", e);Map<String, Object> result = new HashMap<>();result.put("code", 400);result.put("message", "计算错误:" + e.getMessage());result.put("timestamp", LocalDateTime.now());result.put("success", false);return result;}/*** 处理通用Exception* 这个方法会捕获所有没有被具体处理的异常*/@ExceptionHandler(Exception.class)@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)public Map<String, Object> handleGeneralException(Exception e) {logger.error("未处理的异常: ", e);Map<String, Object> result = new HashMap<>();result.put("code", 500);result.put("message", "系统异常,请联系管理员");result.put("timestamp", LocalDateTime.now());result.put("success", false);return result;}
}

3.2 统一响应格式

为了保持响应格式的一致性,建议创建统一的响应类:

package com.example.common;import com.fasterxml.jackson.annotation.JsonFormat;import java.time.LocalDateTime;/*** 统一响应结果类* @param <T> 数据类型*/
public class Result<T> {/*** 响应码*/private Integer code;/*** 响应消息*/private String message;/*** 响应数据*/private T data;/*** 是否成功*/private Boolean success;/*** 时间戳*/@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")private LocalDateTime timestamp;public Result() {this.timestamp = LocalDateTime.now();}public Result(Integer code, String message, T data, Boolean success) {this();this.code = code;this.message = message;this.data = data;this.success = success;}/*** 成功响应*/public static <T> Result<T> success(T data) {return new Result<>(200, "操作成功", data, true);}public static <T> Result<T> success(String message, T data) {return new Result<>(200, message, data, true);}public static <T> Result<T> success() {return new Result<>(200, "操作成功", null, true);}/*** 失败响应*/public static <T> Result<T> error(Integer code, String message) {return new Result<>(code, message, null, false);}public static <T> Result<T> error(String message) {return new Result<>(500, message, null, false);}public static <T> Result<T> error() {return new Result<>(500, "操作失败", null, false);}// getter和setter方法public Integer getCode() {return code;}public void setCode(Integer code) {this.code = code;}public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}public T getData() {return data;}public void setData(T data) {this.data = data;}public Boolean getSuccess() {return success;}public void setSuccess(Boolean success) {this.success = success;}public LocalDateTime getTimestamp() {return timestamp;}public void setTimestamp(LocalDateTime timestamp) {this.timestamp = timestamp;}
}

3.3 使用统一响应格式的异常处理器

package com.example.exception;import com.example.common.Result;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;/*** 改进的全局异常处理器* 使用统一响应格式*/
@RestControllerAdvice
public class ImprovedGlobalExceptionHandler {private static final Logger logger = LoggerFactory.getLogger(ImprovedGlobalExceptionHandler.class);/*** 处理空指针异常*/@ExceptionHandler(NullPointerException.class)@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)public Result<Void> handleNullPointerException(NullPointerException e) {logger.error("空指针异常: ", e);return Result.error(500, "系统内部错误,请稍后重试");}/*** 处理数组越界异常*/@ExceptionHandler(ArrayIndexOutOfBoundsException.class)@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)public Result<Void> handleArrayIndexOutOfBoundsException(ArrayIndexOutOfBoundsException e) {logger.error("数组越界异常: ", e);return Result.error(500, "数据访问越界");}/*** 处理算术异常*/@ExceptionHandler(ArithmeticException.class)@ResponseStatus(HttpStatus.BAD_REQUEST)public Result<Void> handleArithmeticException(ArithmeticException e) {logger.error("算术异常: ", e);return Result.error(400, "计算错误:" + e.getMessage());}/*** 处理通用异常*/@ExceptionHandler(Exception.class)@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)public Result<Void> handleGeneralException(Exception e) {logger.error("未处理的异常: ", e);return Result.error(500, "系统异常,请联系管理员");}
}

4. 自定义异常

4.1 创建基础自定义异常

package com.example.exception;/*** 基础业务异常类* 所有自定义业务异常都应该继承这个类*/
public class BaseException extends RuntimeException {/*** 错误码*/private Integer code;/*** 错误消息*/private String message;public BaseException(Integer code, String message) {super(message);this.code = code;this.message = message;}public BaseException(Integer code, String message, Throwable cause) {super(message, cause);this.code = code;this.message = message;}public BaseException(String message) {this(500, message);}public BaseException(String message, Throwable cause) {this(500, message, cause);}public Integer getCode() {return code;}public void setCode(Integer code) {this.code = code;}@Overridepublic String getMessage() {return message;}public void setMessage(String message) {this.message = message;}
}

4.2 具体的业务异常类

package com.example.exception;/*** 业务异常类* 用于处理业务逻辑中的异常情况*/
public class BusinessException extends BaseException {public BusinessException(String message) {super(400, message);}public BusinessException(Integer code, String message) {super(code, message);}public BusinessException(String message, Throwable cause) {super(400, message, cause);}
}/*** 用户不存在异常*/
public class UserNotFoundException extends BaseException {public UserNotFoundException(Long userId) {super(404, "用户不存在,ID:" + userId);}public UserNotFoundException(String username) {super(404, "用户不存在,用户名:" + username);}
}/*** 参数校验异常*/
public class ValidationException extends BaseException {public ValidationException(String message) {super(400, message);}public ValidationException(String field, String message) {super(400, String.format("参数校验失败:%s %s", field, message));}
}/*** 权限不足异常*/
public class PermissionDeniedException extends BaseException {public PermissionDeniedException() {super(403, "权限不足,无法访问该资源");}public PermissionDeniedException(String message) {super(403, message);}public PermissionDeniedException(String resource, String action) {super(403, String.format("权限不足,无法对资源 %s 执行 %s 操作", resource, action));}
}/*** 数据重复异常*/
public class DuplicateDataException extends BaseException {public DuplicateDataException(String message) {super(409, message);}public DuplicateDataException(String field, String value) {super(409, String.format("数据已存在:%s = %s", field, value));}
}/*** 资源未找到异常*/
public class ResourceNotFoundException extends BaseException {public ResourceNotFoundException(String resource) {super(404, resource + " 不存在");}public ResourceNotFoundException(String resource, String id) {super(404, String.format("%s 不存在,ID:%s", resource, id));}
}

4.3 错误码枚举

为了更好地管理错误码,建议使用枚举:

package com.example.enums;/*** 错误码枚举* 统一管理系统中的所有错误码*/
public enum ErrorCode {// 成功SUCCESS(200, "操作成功"),// 客户端错误 4xxBAD_REQUEST(400, "请求参数错误"),UNAUTHORIZED(401, "未授权"),FORBIDDEN(403, "权限不足"),NOT_FOUND(404, "资源不存在"),METHOD_NOT_ALLOWED(405, "请求方法不支持"),CONFLICT(409, "数据冲突"),VALIDATION_FAILED(422, "参数校验失败"),// 服务器错误 5xxINTERNAL_SERVER_ERROR(500, "系统内部错误"),SERVICE_UNAVAILABLE(503, "服务不可用"),// 业务错误 6xxxUSER_NOT_FOUND(6001, "用户不存在"),USER_ALREADY_EXISTS(6002, "用户已存在"),INVALID_PASSWORD(6003, "密码错误"),ACCOUNT_LOCKED(6004, "账户已锁定"),ACCOUNT_EXPIRED(6005, "账户已过期"),// 数据错误 7xxxDATA_NOT_FOUND(7001, "数据不存在"),DATA_ALREADY_EXISTS(7002, "数据已存在"),DATA_INTEGRITY_VIOLATION(7003, "数据完整性约束冲突"),// 第三方服务错误 8xxxEXTERNAL_SERVICE_ERROR(8001, "外部服务调用失败"),PAYMENT_SERVICE_ERROR(8002, "支付服务异常"),SMS_SERVICE_ERROR(8003, "短信服务异常");private final Integer code;private final String message;ErrorCode(Integer code, String message) {this.code = code;this.message = message;}public Integer getCode() {return code;}public String getMessage() {return message;}
}

4.4 使用错误码的异常类

package com.example.exception;import com.example.enums.ErrorCode;/*** 使用错误码的业务异常*/
public class BizException extends BaseException {private ErrorCode errorCode;public BizException(ErrorCode errorCode) {super(errorCode.getCode(), errorCode.getMessage());this.errorCode = errorCode;}public BizException(ErrorCode errorCode, String customMessage) {super(errorCode.getCode(), customMessage);this.errorCode = errorCode;}public BizException(ErrorCode errorCode, Throwable cause) {super(errorCode.getCode(), errorCode.getMessage(), cause);this.errorCode = errorCode;}public ErrorCode getErrorCode() {return errorCode;}
}

5. 完整的异常处理体系

5.1 完整的全局异常处理器

package com.example.exception;import com.example.common.Result;
import com.example.enums.ErrorCode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.NoHandlerFoundException;import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;/*** 完整的全局异常处理器* 处理系统中可能出现的各种异常*/
@RestControllerAdvice
public class CompleteGlobalExceptionHandler {private static final Logger logger = LoggerFactory.getLogger(CompleteGlobalExceptionHandler.class);/*** 处理自定义业务异常*/@ExceptionHandler(BizException.class)@ResponseStatus(HttpStatus.OK) // 业务异常通常返回200,通过业务码区分public Result<Void> handleBizException(BizException e, HttpServletRequest request) {logger.warn("业务异常 - URL: {}, 异常: {}", request.getRequestURL(), e.getMessage());return Result.error(e.getCode(), e.getMessage());}/*** 处理基础自定义异常*/@ExceptionHandler(BaseException.class)@ResponseStatus(HttpStatus.OK)public Result<Void> handleBaseException(BaseException e, HttpServletRequest request) {logger.warn("自定义异常 - URL: {}, 异常: {}", request.getRequestURL(), e.getMessage());return Result.error(e.getCode(), e.getMessage());}/*** 处理参数校验异常(@Valid)*/@ExceptionHandler(MethodArgumentNotValidException.class)@ResponseStatus(HttpStatus.BAD_REQUEST)public Result<Map<String, String>> handleMethodArgumentNotValidException(MethodArgumentNotValidException e, HttpServletRequest request) {logger.warn("参数校验异常 - URL: {}", request.getRequestURL());Map<String, String> errors = new HashMap<>();for (FieldError error : e.getBindingResult().getFieldErrors()) {errors.put(error.getField(), error.getDefaultMessage());}return Result.error(ErrorCode.VALIDATION_FAILED.getCode(), "参数校验失败").setData(errors);}/*** 处理Bean校验异常*/@ExceptionHandler(BindException.class)@ResponseStatus(HttpStatus.BAD_REQUEST)public Result<Map<String, String>> handleBindException(BindException e, HttpServletRequest request) {logger.warn("Bean校验异常 - URL: {}", request.getRequestURL());Map<String, String> errors = new HashMap<>();for (FieldError error : e.getFieldErrors()) {errors.put(error.getField(), error.getDefaultMessage());}return Result.error(ErrorCode.VALIDATION_FAILED.getCode(), "参数校验失败").setData(errors);}/*** 处理单个参数校验异常(@Validated)*/@ExceptionHandler(ConstraintViolationException.class)@ResponseStatus(HttpStatus.BAD_REQUEST)public Result<Map<String, String>> handleConstraintViolationException(ConstraintViolationException e, HttpServletRequest request) {logger.warn("参数约束异常 - URL: {}", request.getRequestURL());Map<String, String> errors = new HashMap<>();Set<ConstraintViolation<?>> violations = e.getConstraintViolations();for (ConstraintViolation<?> violation : violations) {String propertyPath = violation.getPropertyPath().toString();String message = violation.getMessage();errors.put(propertyPath, message);}return Result.error(ErrorCode.VALIDATION_FAILED.getCode(), "参数校验失败").setData(errors);}/*** 处理请求参数缺失异常*/@ExceptionHandler(MissingServletRequestParameterException.class)@ResponseStatus(HttpStatus.BAD_REQUEST)public Result<Void> handleMissingServletRequestParameterException(MissingServletRequestParameterException e, HttpServletRequest request) {logger.warn("缺少请求参数 - URL: {}, 参数: {}", request.getRequestURL(), e.getParameterName());String message = String.format("缺少必要的请求参数:%s", e.getParameterName());return Result.error(ErrorCode.BAD_REQUEST.getCode(), message);}/*** 处理参数类型不匹配异常*/@ExceptionHandler(MethodArgumentTypeMismatchException.class)@ResponseStatus(HttpStatus.BAD_REQUEST)public Result<Void> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e, HttpServletRequest request) {logger.warn("参数类型不匹配 - URL: {}, 参数: {}", request.getRequestURL(), e.getName());String message = String.format("参数类型不匹配:%s", e.getName());return Result.error(ErrorCode.BAD_REQUEST.getCode(), message);}/*** 处理HTTP请求方法不支持异常*/@ExceptionHandler(HttpRequestMethodNotSupportedException.class)@ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)public Result<Void> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e, HttpServletRequest request) {logger.warn("请求方法不支持 - URL: {}, 方法: {}", request.getRequestURL(), e.getMethod());String message = String.format("请求方法 %s 不支持", e.getMethod());return Result.error(ErrorCode.METHOD_NOT_ALLOWED.getCode(), message);}/*** 处理HTTP消息不可读异常(通常是JSON格式错误)*/@ExceptionHandler(HttpMessageNotReadableException.class)@ResponseStatus(HttpStatus.BAD_REQUEST)public Result<Void> handleHttpMessageNotReadableException(HttpMessageNotReadableException e, HttpServletRequest request) {logger.warn("HTTP消息不可读 - URL: {}", request.getRequestURL());return Result.error(ErrorCode.BAD_REQUEST.getCode(), "请求体格式错误");}/*** 处理404异常*/@ExceptionHandler(NoHandlerFoundException.class)@ResponseStatus(HttpStatus.NOT_FOUND)public Result<Void> handleNoHandlerFoundException(NoHandlerFoundException e, HttpServletRequest request) {logger.warn("404异常 - URL: {}", request.getRequestURL());return Result.error(ErrorCode.NOT_FOUND.getCode(), "请求的资源不存在");}/*** 处理空指针异常*/@ExceptionHandler(NullPointerException.class)@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)public Result<Void> handleNullPointerException(NullPointerException e, HttpServletRequest request) {logger.error("空指针异常 - URL: {}", request.getRequestURL(), e);return Result.error(ErrorCode.INTERNAL_SERVER_ERROR.getCode(), "系统内部错误");}/*** 处理运行时异常*/@ExceptionHandler(RuntimeException.class)@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)public Result<Void> handleRuntimeException(RuntimeException e, HttpServletRequest request) {logger.error("运行时异常 - URL: {}", request.getRequestURL(), e);return Result.error(ErrorCode.INTERNAL_SERVER_ERROR.getCode(), "系统内部错误");}/*** 处理所有未捕获的异常*/@ExceptionHandler(Exception.class)@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)public Result<Void> handleException(Exception e, HttpServletRequest request) {logger.error("未知异常 - URL: {}", request.getRequestURL(), e);return Result.error(ErrorCode.INTERNAL_SERVER_ERROR.getCode(), "系统异常,请联系管理员");}
}

5.2 改进的Result类

package com.example.common;import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonInclude;import java.time.LocalDateTime;/*** 改进的统一响应结果类*/
@JsonInclude(JsonInclude.Include.NON_NULL) // 空值不序列化
public class Result<T> {private Integer code;private String message;private T data;private Boolean success;@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")private LocalDateTime timestamp;public Result() {this.timestamp = LocalDateTime.now();}public Result(Integer code, String message, T data, Boolean success) {this();this.code = code;this.message = message;this.data = data;this.success = success;}// 成功响应public static <T> Result<T> success() {return new Result<>(200, "操作成功", null, true);}public static <T> Result<T> success(T data) {return new Result<>(200, "操作成功", data, true);}public static <T> Result<T> success(String message) {return new Result<>(200, message, null, true);}public static <T> Result<T> success(String message, T data) {return new Result<>(200, message, data, true);}// 失败响应public static <T> Result<T> error() {return new Result<>(500, "操作失败", null, false);}public static <T> Result<T> error(String message) {return new Result<>(500, message, null, false);}public static <T> Result<T> error(Integer code, String message) {return new Result<>(code, message, null, false);}public static <T> Result<T> error(Integer code, String message, T data) {return new Result<>(code, message, data, false);}// 支持链式调用public Result<T> setData(T data) {this.data = data;return this;}public Result<T> setMessage(String message) {this.message = message;return this;}// getter和setter方法public Integer getCode() {return code;}public void setCode(Integer code) {this.code = code;}public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}public T getData() {return data;}public void setData(T data) {this.data = data;}public Boolean getSuccess() {return success;}public void setSuccess(Boolean success) {this.success = success;}public LocalDateTime getTimestamp() {return timestamp;}public void setTimestamp(LocalDateTime timestamp) {this.timestamp = timestamp;}
}

6. 实际应用示例

6.1 用户服务示例

package com.example.entity;import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;/*** 用户实体类*/
public class User {private Long id;@NotBlank(message = "用户名不能为空")@Size(min = 3, max = 20, message = "用户名长度必须在3-20个字符之间")private String username;@NotBlank(message = "邮箱不能为空")@Email(message = "邮箱格式不正确")private String email;@NotBlank(message = "密码不能为空")@Size(min = 6, max = 20, message = "密码长度必须在6-20位之间")private String password;// 构造方法public User() {}public User(Long id, String username, String email) {this.id = id;this.username = username;this.email = email;}// getter和setter方法public Long getId() {return id;}public void setId(Long id) {this.id = id;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getEmail() {return email;}public void setEmail(String email) {this.email = email;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}
}

6.2 用户服务类

package com.example.service;import com.example.entity.User;
import com.example.enums.ErrorCode;
import com.example.exception.BizException;
import org.springframework.stereotype.Service;import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;/*** 用户服务类* 演示异常处理的使用*/
@Service
public class UserService {// 模拟数据库private final ConcurrentHashMap<Long, User> userDatabase = new ConcurrentHashMap<>();private final AtomicLong idGenerator = new AtomicLong(1);/*** 根据ID查询用户*/public User getUserById(Long id) {if (id == null || id <= 0) {throw new BizException(ErrorCode.BAD_REQUEST, "用户ID不能为空或小于等于0");}User user = userDatabase.get(id);if (user == null) {throw new BizException(ErrorCode.USER_NOT_FOUND);}return user;}/*** 根据用户名查询用户*/public User getUserByUsername(String username) {if (username == null || username.trim().isEmpty()) {throw new BizException(ErrorCode.BAD_REQUEST, "用户名不能为空");}User user = userDatabase.values().stream().filter(u -> username.equals(u.getUsername())).findFirst().orElse(null);if (user == null) {throw new BizException(ErrorCode.USER_NOT_FOUND, "用户不存在:" + username);}return user;}/*** 创建用户*/public User createUser(User user) {// 检查用户名是否已存在boolean usernameExists = userDatabase.values().stream().anyMatch(u -> user.getUsername().equals(u.getUsername()));if (usernameExists) {throw new BizException(ErrorCode.USER_ALREADY_EXISTS, "用户名已存在:" + user.getUsername());}// 检查邮箱是否已存在boolean emailExists = userDatabase.values().stream().anyMatch(u -> user.getEmail().equals(u.getEmail()));if (emailExists) {throw new BizException(ErrorCode.DATA_ALREADY_EXISTS, "邮箱已存在:" + user.getEmail());}// 设置ID并保存user.setId(idGenerator.getAndIncrement());userDatabase.put(user.getId(), user);return user;}/*** 更新用户*/public User updateUser(Long id, User user) {User existingUser = getUserById(id); // 这里会抛出用户不存在异常// 检查用户名是否被其他用户占用boolean usernameConflict = userDatabase.values().stream().anyMatch(u -> !u.getId().equals(id) && user.getUsername().equals(u.getUsername()));if (usernameConflict) {throw new BizException(ErrorCode.CONFLICT, "用户名已被其他用户占用:" + user.getUsername());}// 更新用户信息existingUser.setUsername(user.getUsername());existingUser.setEmail(user.getEmail());return existingUser;}/*** 删除用户*/public void deleteUser(Long id) {User user = getUserById(id); // 这里会抛出用户不存在异常userDatabase.remove(id);}/*** 获取所有用户*/public List<User> getAllUsers() {return new ArrayList<>(userDatabase.values());}/*** 用户登录*/public User login(String username, String password) {if (username == null || username.trim().isEmpty()) {throw new BizException(ErrorCode.BAD_REQUEST, "用户名不能为空");}if (password == null || password.trim().isEmpty()) {throw new BizException(ErrorCode.BAD_REQUEST, "密码不能为空");}User user = getUserByUsername(username);if (!password.equals(user.getPassword())) {throw new BizException(ErrorCode.INVALID_PASSWORD);}return user;}/*** 模拟系统异常*/public void simulateSystemError() {throw new RuntimeException("模拟系统异常");}/*** 模拟空指针异常*/public String simulateNullPointer() {String str = null;return str.length() + ""; // 这里会抛出NullPointerException}
}

6.3 用户控制器

package com.example.controller;import com.example.common.Result;
import com.example.entity.User;
import com.example.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;import javax.validation.Valid;
import javax.validation.constraints.Min;
import java.util.List;/*** 用户控制器* 演示统一异常处理的效果*/
@RestController
@RequestMapping("/api/users")
@Validated
public class UserController {@Autowiredprivate UserService userService;/*** 根据ID查询用户*/@GetMapping("/{id}")public Result<User> getUserById(@PathVariable @Min(value = 1, message = "用户ID必须大于0") Long id) {User user = userService.getUserById(id);return Result.success("查询成功", user);}/*** 根据用户名查询用户*/@GetMapping("/username/{username}")public Result<User> getUserByUsername(@PathVariable String username) {User user = userService.getUserByUsername(username);return Result.success("查询成功", user);}/*** 创建用户*/@PostMappingpublic Result<User> createUser(@Valid @RequestBody User user) {User createdUser = userService.createUser(user);return Result.success("用户创建成功", createdUser);}/*** 更新用户*/@PutMapping("/{id}")public Result<User> updateUser(@PathVariable @Min(value = 1, message = "用户ID必须大于0") Long id,@Valid @RequestBody User user) {User updatedUser = userService.updateUser(id, user);return Result.success("用户更新成功", updatedUser);}/*** 删除用户*/@DeleteMapping("/{id}")public Result<Void> deleteUser(@PathVariable @Min(value = 1, message = "用户ID必须大于0") Long id) {userService.deleteUser(id);return Result.success("用户删除成功");}/*** 获取所有用户*/@GetMappingpublic Result<List<User>> getAllUsers() {List<User> users = userService.getAllUsers();return Result.success("查询成功", users);}/*** 用户登录*/@PostMapping("/login")public Result<User> login(@RequestParam String username, @RequestParam String password) {User user = userService.login(username, password);return Result.success("登录成功", user);}/*** 模拟系统异常*/@GetMapping("/error/system")public Result<Void> simulateSystemError() {userService.simulateSystemError();return Result.success();}/*** 模拟空指针异常*/@GetMapping("/error/null-pointer")public Result<String> simulateNullPointer() {String result = userService.simulateNullPointer();return Result.success("操作成功", result);}
}

6.4 登录请求DTO

package com.example.dto;import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;/*** 登录请求DTO*/
public class LoginRequest {@NotBlank(message = "用户名不能为空")private String username;@NotBlank(message = "密码不能为空")@Size(min = 6, message = "密码长度不能少于6位")private String password;public LoginRequest() {}public LoginRequest(String username, String password) {this.username = username;this.password = password;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}
}

改进登录接口:

/*** 用户登录(使用DTO)*/
@PostMapping("/login-dto")
public Result<User> loginWithDto(@Valid @RequestBody LoginRequest request) {User user = userService.login(request.getUsername(), request.getPassword());return Result.success("登录成功", user);
}

7. 日志记录和监控

7.1 异常日志记录

package com.example.util;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;/*** 日志工具类*/
public class LogUtil {private static final Logger logger = LoggerFactory.getLogger(LogUtil.class);/*** 记录异常详细信息*/public static void logException(Exception e, HttpServletRequest request) {StringBuilder logMessage = new StringBuilder();logMessage.append("\n=== 异常详细信息 ===\n");logMessage.append("请求URL: ").append(request.getRequestURL()).append("\n");logMessage.append("请求方法: ").append(request.getMethod()).append("\n");logMessage.append("客户端IP: ").append(getClientIP(request)).append("\n");logMessage.append("User-Agent: ").append(request.getHeader("User-Agent")).append("\n");// 记录请求参数logMessage.append("请求参数: \n");Enumeration<String> paramNames = request.getParameterNames();while (paramNames.hasMoreElements()) {String paramName = paramNames.nextElement();String paramValue = request.getParameter(paramName);logMessage.append("  ").append(paramName).append(" = ").append(paramValue).append("\n");}// 记录请求头logMessage.append("请求头: \n");Enumeration<String> headerNames = request.getHeaderNames();while (headerNames.hasMoreElements()) {String headerName = headerNames.nextElement();String headerValue = request.getHeader(headerName);logMessage.append("  ").append(headerName).append(" = ").append(headerValue).append("\n");}logMessage.append("异常信息: ").append(e.getMessage()).append("\n");logMessage.append("=== 异常详细信息结束 ===");logger.error(logMessage.toString(), e);}/*** 获取客户端真实IP*/private static String getClientIP(HttpServletRequest request) {String ip = request.getHeader("X-Forwarded-For");if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("Proxy-Client-IP");}if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("WL-Proxy-Client-IP");}if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("HTTP_CLIENT_IP");}if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("HTTP_X_FORWARDED_FOR");}if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {ip = request.getRemoteAddr();}return ip;}
}

7.2 增强的异常处理器

package com.example.exception;import com.example.common.Result;
import com.example.enums.ErrorCode;
import com.example.util.LogUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
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;import javax.servlet.http.HttpServletRequest;/*** 增强的全局异常处理器* 包含详细的日志记录和监控*/
@RestControllerAdvice
public class EnhancedGlobalExceptionHandler {private static final Logger logger = LoggerFactory.getLogger(EnhancedGlobalExceptionHandler.class);@Value("${app.debug:false}")private boolean debug;/*** 处理业务异常*/@ExceptionHandler(BizException.class)@ResponseStatus(HttpStatus.OK)public Result<Void> handleBizException(BizException e, HttpServletRequest request) {// 业务异常通常是可预期的,使用warn级别logger.warn("业务异常 - URL: {}, 错误码: {}, 消息: {}", request.getRequestURL(), e.getCode(), e.getMessage());return Result.error(e.getCode(), e.getMessage());}/*** 处理参数校验异常*/@ExceptionHandler(MethodArgumentNotValidException.class)@ResponseStatus(HttpStatus.BAD_REQUEST)public Result<Object> handleMethodArgumentNotValidException(MethodArgumentNotValidException e, HttpServletRequest request) {logger.warn("参数校验失败 - URL: {}, 错误数量: {}", request.getRequestURL(), e.getBindingResult().getErrorCount());// 详细记录校验错误StringBuilder errorDetails = new StringBuilder();e.getBindingResult().getFieldErrors().forEach(error -> {errorDetails.append(String.format("[%s: %s] ", error.getField(), error.getDefaultMessage()));});logger.debug("校验错误详情: {}", errorDetails.toString());if (debug) {// 开发环境返回详细错误信息Map<String, String> errors = new HashMap<>();for (FieldError error : e.getBindingResult().getFieldErrors()) {errors.put(error.getField(), error.getDefaultMessage());}return Result.error(ErrorCode.VALIDATION_FAILED.getCode(), "参数校验失败", errors);} else {// 生产环境返回简化错误信息return Result.error(ErrorCode.VALIDATION_FAILED.getCode(), "参数校验失败");}}/*** 处理系统异常*/@ExceptionHandler(Exception.class)@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)public Result<Void> handleException(Exception e, HttpServletRequest request) {// 记录详细的异常信息LogUtil.logException(e, request);// 发送告警(生产环境)if (!debug) {sendAlert(e, request);}// 返回用户友好的错误信息String message = debug ? e.getMessage() : "系统异常,请联系管理员";return Result.error(ErrorCode.INTERNAL_SERVER_ERROR.getCode(), message);}/*** 发送告警通知*/private void sendAlert(Exception e, HttpServletRequest request) {// 这里可以集成钉钉、企业微信、邮件等告警方式logger.error("系统异常告警 - URL: {}, 异常类型: {}", request.getRequestURL(), e.getClass().getSimpleName());// 示例:发送到监控系统// monitoringService.sendAlert("系统异常", e.getMessage(), request.getRequestURL().toString());}
}

7.3 异常统计和监控

package com.example.service;import org.springframework.stereotype.Service;import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;/*** 异常统计服务*/
@Service
public class ExceptionStatisticsService {// 异常计数器private final ConcurrentHashMap<String, AtomicLong> exceptionCounts = new ConcurrentHashMap<>();// 异常发生时间记录private final ConcurrentHashMap<String, Long> lastOccurrenceTime = new ConcurrentHashMap<>();/*** 记录异常*/public void recordException(String exceptionType, String url) {// 增加计数exceptionCounts.computeIfAbsent(exceptionType, k -> new AtomicLong(0)).incrementAndGet();// 记录最后发生时间lastOccurrenceTime.put(exceptionType, System.currentTimeMillis());// 记录URL相关的异常String urlKey = "URL:" + url;exceptionCounts.computeIfAbsent(urlKey, k -> new AtomicLong(0)).incrementAndGet();}/*** 获取异常统计信息*/public Map<String, Object> getStatistics() {Map<String, Object> statistics = new HashMap<>();// 异常计数Map<String, Long> counts = new HashMap<>();exceptionCounts.forEach((key, value) -> counts.put(key, value.get()));statistics.put("exceptionCounts", counts);// 最后发生时间statistics.put("lastOccurrenceTime", new HashMap<>(lastOccurrenceTime));// 总异常数long totalExceptions = exceptionCounts.values().stream().mapToLong(AtomicLong::get).sum();statistics.put("totalExceptions", totalExceptions);return statistics;}/*** 清理统计数据*/public void clearStatistics() {exceptionCounts.clear();lastOccurrenceTime.clear();}
}

8. 配置和优化

8.1 异常处理配置

# application.yml
app:# 是否开启调试模式debug: false# 异常处理配置exception:# 是否记录详细堆栈信息log-stack-trace: true# 是否发送告警send-alert: true# 告警阈值(每分钟异常数量)alert-threshold: 10# 是否返回详细错误信息给客户端return-details: false# 日志配置
logging:level:com.example.exception: DEBUGcom.example.util: DEBUGpattern:file: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"file:name: logs/application.logmax-size: 10MBmax-history: 30# 服务器错误配置
server:error:# 是否包含异常信息include-exception: false# 是否包含堆栈跟踪include-stacktrace: never# 是否包含消息include-message: never# 错误页面路径path: /error

8.2 配置属性类

package com.example.config;import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;/*** 异常处理配置属性*/
@Component
@ConfigurationProperties(prefix = "app.exception")
public class ExceptionProperties {/*** 是否记录详细堆栈信息*/private boolean logStackTrace = true;/*** 是否发送告警*/private boolean sendAlert = true;/*** 告警阈值(每分钟异常数量)*/private int alertThreshold = 10;/*** 是否返回详细错误信息给客户端*/private boolean returnDetails = false;// getter和setter方法public boolean isLogStackTrace() {return logStackTrace;}public void setLogStackTrace(boolean logStackTrace) {this.logStackTrace = logStackTrace;}public boolean isSendAlert() {return sendAlert;}public void setSendAlert(boolean sendAlert) {this.sendAlert = sendAlert;}public int getAlertThreshold() {return alertThreshold;}public void setAlertThreshold(int alertThreshold) {this.alertThreshold = alertThreshold;}public boolean isReturnDetails() {return returnDetails;}public void setReturnDetails(boolean returnDetails) {this.returnDetails = returnDetails;}
}

8.3 异常处理配置类

package com.example.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;import java.util.List;/*** 异常处理配置类*/
@Configuration
public class ExceptionConfig {/*** 配置异常解析器的优先级*/@Beanpublic void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {// 确保ExceptionHandlerExceptionResolver优先级最高resolvers.stream().filter(resolver -> resolver instanceof ExceptionHandlerExceptionResolver).findFirst().ifPresent(resolver -> {resolvers.remove(resolver);resolvers.add(0, resolver);});}
}

9. 测试异常处理

9.1 异常处理单元测试

package com.example.exception;import com.example.enums.ErrorCode;
import com.example.exception.BizException;
import com.example.exception.CompleteGlobalExceptionHandler;
import com.example.common.Result;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockHttpServletRequest;import static org.junit.jupiter.api.Assertions.*;/*** 异常处理器单元测试*/
class GlobalExceptionHandlerTest {private CompleteGlobalExceptionHandler exceptionHandler;private MockHttpServletRequest request;@BeforeEachvoid setUp() {exceptionHandler = new CompleteGlobalExceptionHandler();request = new MockHttpServletRequest();request.setRequestURI("/api/test");request.setMethod("GET");}@Testvoid testHandleBizException() {// 准备测试数据BizException exception = new BizException(ErrorCode.USER_NOT_FOUND);// 执行测试Result<Void> result = exceptionHandler.handleBizException(exception, request);// 验证结果assertNotNull(result);assertEquals(ErrorCode.USER_NOT_FOUND.getCode(), result.getCode());assertEquals(ErrorCode.USER_NOT_FOUND.getMessage(), result.getMessage());assertFalse(result.getSuccess());}@Testvoid testHandleNullPointerException() {// 准备测试数据NullPointerException exception = new NullPointerException("测试空指针异常");// 执行测试Result<Void> result = exceptionHandler.handleNullPointerException(exception, request);// 验证结果assertNotNull(result);assertEquals(ErrorCode.INTERNAL_SERVER_ERROR.getCode(), result.getCode());assertFalse(result.getSuccess());}@Testvoid testHandleGeneralException() {// 准备测试数据RuntimeException exception = new RuntimeException("测试运行时异常");// 执行测试Result<Void> result = exceptionHandler.handleException(exception, request);// 验证结果assertNotNull(result);assertEquals(ErrorCode.INTERNAL_SERVER_ERROR.getCode(), result.getCode());assertFalse(result.getSuccess());}
}

9.2 集成测试

package com.example.controller;import com.example.enums.ErrorCode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;/*** 异常处理集成测试*/
@WebMvcTest(UserController.class)
class ExceptionHandlingIntegrationTest {@Autowiredprivate MockMvc mockMvc;@Autowiredprivate ObjectMapper objectMapper;@MockBeanprivate UserService userService;@Testvoid testUserNotFoundExceptionHandling() throws Exception {// 测试用户不存在异常mockMvc.perform(get("/api/users/999")).andExpect(status().isOk()).andExpect(jsonPath("$.success").value(false)).andExpect(jsonPath("$.code").value(ErrorCode.USER_NOT_FOUND.getCode())).andExpect(jsonPath("$.message").exists());}@Testvoid testValidationExceptionHandling() throws Exception {// 测试参数校验异常String invalidUserJson = """{"username": "","email": "invalid-email","password": "123"}""";mockMvc.perform(post("/api/users").contentType(MediaType.APPLICATION_JSON).content(invalidUserJson)).andExpect(status().isBadRequest()).andExpect(jsonPath("$.success").value(false)).andExpect(jsonPath("$.code").value(ErrorCode.VALIDATION_FAILED.getCode())).andExpect(jsonPath("$.data").exists());}@Testvoid testMethodArgumentTypeMismatchException() throws Exception {// 测试参数类型不匹配异常mockMvc.perform(get("/api/users/invalid-id")).andExpect(status().isBadRequest()).andExpect(jsonPath("$.success").value(false)).andExpect(jsonPath("$.code").value(ErrorCode.BAD_REQUEST.getCode()));}
}

10. 最佳实践

10.1 异常处理设计原则

  1. 统一性:所有异常都通过全局异常处理器处理
  2. 安全性:不暴露敏感的系统信息
  3. 用户友好:提供清晰、有意义的错误消息
  4. 可监控:记录详细的异常信息用于问题排查
  5. 可配置:支持不同环境的不同配置

10.2 异常分类策略

/*** 异常分类处理策略*/
public class ExceptionClassificationStrategy {/*** 业务异常(4xx)* - 用户输入错误* - 业务规则违反* - 资源不存在*/public static boolean isBusinessException(Exception e) {return e instanceof BizException ||e instanceof ValidationException ||e instanceof UserNotFoundException;}/*** 系统异常(5xx)* - 数据库连接异常* - 网络异常* - 系统资源异常*/public static boolean isSystemException(Exception e) {return e instanceof SQLException ||e instanceof IOException ||e instanceof OutOfMemoryError;}/*** 第三方服务异常* - 外部API调用失败* - 消息队列异常* - 缓存服务异常*/public static boolean isExternalServiceException(Exception e) {return e instanceof HttpClientErrorException ||e instanceof RedisConnectionException ||e instanceof MessagingException;}
}

10.3 异常处理最佳实践

package com.example.exception;import com.example.common.Result;
import com.example.config.ExceptionProperties;
import com.example.service.ExceptionStatisticsService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;import javax.servlet.http.HttpServletRequest;/*** 最佳实践异常处理器*/
@RestControllerAdvice
@Order(1) // 设置优先级
public class BestPracticeExceptionHandler {private static final Logger logger = LoggerFactory.getLogger(BestPracticeExceptionHandler.class);@Autowiredprivate ExceptionProperties exceptionProperties;@Autowiredprivate ExceptionStatisticsService statisticsService;@ExceptionHandler(Exception.class)public Result<Void> handleException(Exception e, HttpServletRequest request) {// 1. 记录异常统计statisticsService.recordException(e.getClass().getSimpleName(), request.getRequestURI());// 2. 根据配置决定日志级别if (exceptionProperties.isLogStackTrace()) {logger.error("异常详情", e);} else {logger.error("异常: {} - {}", e.getClass().getSimpleName(), e.getMessage());}// 3. 根据异常类型选择不同的处理策略if (ExceptionClassificationStrategy.isBusinessException(e)) {return handleBusinessException(e, request);} else if (ExceptionClassificationStrategy.isSystemException(e)) {return handleSystemException(e, request);} else if (ExceptionClassificationStrategy.isExternalServiceException(e)) {return handleExternalServiceException(e, request);} else {return handleUnknownException(e, request);}}private Result<Void> handleBusinessException(Exception e, HttpServletRequest request) {logger.warn("业务异常 - URL: {}, 异常: {}", request.getRequestURI(), e.getMessage());String message = exceptionProperties.isReturnDetails() ? e.getMessage() : "业务处理失败";return Result.error(400, message);}private Result<Void> handleSystemException(Exception e, HttpServletRequest request) {logger.error("系统异常 - URL: {}", request.getRequestURI(), e);// 发送告警if (exceptionProperties.isSendAlert()) {sendSystemAlert(e, request);}return Result.error(500, "系统暂时不可用,请稍后重试");}private Result<Void> handleExternalServiceException(Exception e, HttpServletRequest request) {logger.error("外部服务异常 - URL: {}", request.getRequestURI(), e);return Result.error(503, "服务暂时不可用,请稍后重试");}private Result<Void> handleUnknownException(Exception e, HttpServletRequest request) {logger.error("未知异常 - URL: {}", request.getRequestURI(), e);// 未知异常需要特别关注if (exceptionProperties.isSendAlert()) {sendCriticalAlert(e, request);}return Result.error(500, "系统异常,请联系管理员");}private void sendSystemAlert(Exception e, HttpServletRequest request) {// 实现系统告警逻辑logger.warn("发送系统告警: {}", e.getMessage());}private void sendCriticalAlert(Exception e, HttpServletRequest request) {// 实现紧急告警逻辑logger.error("发送紧急告警: {}", e.getMessage());}
}

10.4 异常处理建议

  1. 错误消息国际化
@Component
public class MessageService {@Autowiredprivate MessageSource messageSource;public String getMessage(String key, Object... args) {return messageSource.getMessage(key, args, LocaleContextHolder.getLocale());}
}// 在异常处理器中使用
@Autowired
private MessageService messageService;@ExceptionHandler(BizException.class)
public Result<Void> handleBizException(BizException e) {String message = messageService.getMessage("error.business", e.getMessage());return Result.error(e.getCode(), message);
}
  1. 异常重试机制
@Service
public class RetryableService {@Retryable(value = {Exception.class}, maxAttempts = 3, backoff = @Backoff(delay = 1000))public String processWithRetry() throws Exception {// 可能失败的操作if (Math.random() > 0.7) {throw new RuntimeException("随机失败");}return "处理成功";}@Recoverpublic String recover(Exception e) {logger.error("重试后仍然失败", e);throw new BizException(ErrorCode.EXTERNAL_SERVICE_ERROR, "服务暂时不可用");}
}
  1. 异常降级处理
@Service
public class FallbackService {public String getDataWithFallback(String key) {try {return externalService.getData(key);} catch (Exception e) {logger.warn("外部服务调用失败,使用降级方案", e);return getCachedData(key);}}private String getCachedData(String key) {// 从缓存获取数据return "缓存数据";}
}

11. 总结

11.1 核心知识点回顾

  1. 异常处理机制:@ExceptionHandler、@ControllerAdvice、@RestControllerAdvice
  2. 异常分类:业务异常、系统异常、第三方服务异常
  3. 统一响应:使用统一的Result类格式化响应
  4. 日志记录:详细记录异常信息用于问题排查
  5. 监控告警:异常统计和实时告警机制

11.2 应用场景

  • API接口异常处理:统一处理REST接口的各种异常
  • 参数校验异常:处理@Valid校验失败的情况
  • 业务逻辑异常:处理业务规则验证失败
  • 系统异常处理:处理数据库连接、网络异常等
  • 第三方服务异常:处理外部API调用失败

11.3 学习建议

  1. 理解原理:掌握Spring异常处理的底层机制
  2. 分类处理:根据异常类型采用不同的处理策略
  3. 用户友好:始终考虑用户体验,提供有意义的错误信息
  4. 安全考虑:不暴露敏感的系统信息
  5. 监控完善:建立完善的异常监控和告警机制

SpringBoot的统一异常处理是构建健壮Web应用的重要基础。通过合理的异常处理机制,可以提升用户体验、保证系统安全、便于问题排查和系统维护。记住异常处理不仅仅是技术实现,更是用户体验和系统稳定性的重要保障!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/90353.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/90353.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/90353.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

No Hack No CTF 2025Web部分个人WP

No Hack No CTF 2025 Next Song is 春日影 hint&#xff1a;NextJS Vulnerability at /adminCVE-2025-29927Next.js 中间件权限绕过漏洞 访问admin路由发现跳转利用CVE&#xff1a; curl -i \-H "x-middleware-subrequest: middleware:middleware:middleware:middleware:m…

STM32第十八天 ESP8266-01S和电脑实现串口通信

一&#xff1a; ESP和电脑实现串口通信1. 配置 WiFi 模式 ATCWMODE3 // softAPstation mode 响应 : OK 2. 连接路路由器器 ATCWJAP"SSID","password" // SSID and password of router 响应 : OK 3. 查询 ESP8266 设备的 IP 地址 ATCIFSR 响应 : CIFSR:APIP…

STM32第十七天ESP8266-01Swifi模块

ESP8266-01S wifi模块1&#xff1a;ESP8266是实现wifi通讯的一个模块种类&#xff0c;有很多分类包含esp8266-12、esp8266-12E、ESP8266-01S、esp32等等。esp8266-01S由一颗esp8266作为主控再由一块flash作为存储芯片组成&#xff0c;带有板载芯片供电采用3.3V电压使用串口进行…

ProCCD复古相机:捕捉复古瞬间

在数字摄影盛行的今天&#xff0c;复古胶片相机的独特质感和怀旧风格依然吸引着众多摄影爱好者。ProCCD复古相机APP正是这样一款能够满足用户对复古摄影需求的应用程序。它通过模拟复古CCD数码相机的效果&#xff0c;让用户在手机上也能轻松拍出具有千禧年风格的照片和视频。无…

Spring Boot 应用启动时,端口 8080 已被其他进程占用,怎么办

1、修改application.yml配置文件&#xff0c;将端口号更改为未被占用的端口&#xff08;例如9090&#xff09;2、以管理员身份运行命令提示符在命令提示符窗口中输入命令netstat -ano | findstr :8080”输出结果可能如下&#xff1a;“TCP 0.0.0.0:8080 0.0.0.0:0 LISTENING xx…

使用Jenkins完成springboot项目快速更新

✨重磅&#xff01;盹猫的个人小站正式上线啦&#xff5e;诚邀各位技术大佬前来探秘&#xff01;✨ 这里有&#xff1a; 硬核技术干货&#xff1a;编程技巧、开发经验、踩坑指南&#xff0c;带你解锁技术新姿势&#xff01;趣味开发日常&#xff1a;代码背后的脑洞故事、工具…

HDLBits刷题笔记和一些拓展知识(九)

文章目录HDLBits刷题笔记CircuitsFsm1Fsm1sFsm2Fsm3onehotExams/ece241 2013 q4Lemmings1Lemmings2Lemmings3Lemmings4Fsm onehotFsm ps2Fsm ps2dataFsm serialFsm serialdataFsm serialdpFsm hdlc未完待续HDLBits刷题笔记 以下是在做HDLBits时的一些刷题笔记&#xff0c;截取一…

CD46.【C++ Dev】list的模拟实现(1)

目录 1.STL库的list 2.模拟实现 节点结构体 list类 无参构造函数 尾插函数 迭代器★ begin() operator 前置 后置 operator-- 前置-- 后置-- operator! operator end() operator* const修饰的迭代器的设计 1.STL库的list 模拟实现list之前,先看看STL库里的…

数据结构——二叉树的基本介绍

————————————本文旨在讨论与学习计算机知识&#xff0c;欢迎交流————————————上一章&#xff0c;我们讲解了树结构的综述导论&#xff0c;那么&#xff0c;现在我们来深入了解一下树结构中最常用研究的结构——二叉树结构&#xff08;上一章的扩展——…

英伟达发布 Llama Nemotron Nano 4B:专为边缘 AI 和科研任务优化的高效开源推理模型

英伟达推出了 Llama Nem)otron Nano 4B&#xff0c;这是一款专为在科学任务、编程、符号运算、函数调用和指令执行方面提供强大性能与效率而设计的开源推理模型&#xff0c;其紧凑程度足以支持边缘部署。该模型仅包含 40 亿参数&#xff0c;却在内部基准测试中实现了比其他多达…

论文阅读笔记——Autoregressive Image Generation without Vector Quantization

MAR 论文 基于 VQ&#xff08;向量量化&#xff09;的图像生成方法具有显著优势&#xff0c;它通过离散化压缩将原始图像映射到有限的 codebook 空间&#xff0c;从而缩小学习范围、降低建模难度&#xff0c;同时这种离散表示更易于与自回归&#xff08;AG&#xff09;生成方式…

【科普】关于C 语言日志系统实战:如何同时输出到终端和文件?

1.概述 c语言没有现成的日志库&#xff0c;如果要记录日志&#xff0c;需要自己封装一个日志库。如果要实现日志级别和参数打印&#xff0c;还是比较麻烦的&#xff0c;正好在github找到了一个c语言开源日志库&#xff0c;可以实现日志级别打印&#xff0c;参数打印&#xff0…

2025,数字人借直播场景迈过“真假线”丨数智化观察

作者 | 曾响铃文 | 响铃说一夜带货超5500万GMV、观看人次1300万&#xff0c;罗永浩数字人在百度电商的直播首秀正在掀起新的行业浪潮——2025&#xff0c;数字人直播带货成功出圈&#xff0c;加速进入大众视野&#xff0c;被更多的消费者所认可。成就这场热潮的关键点之一&…

HTML表格导出为Excel文件的实现方案

1、前端javascript可通过mime类型、blob对象或专业库&#xff08;如sheetjs&#xff09;实现html表格导出excel&#xff0c;适用于中小型数据量&#xff1b;2、服务器端方案利用后端语言&#xff08;如python的openpyxl、java的apache poi&#xff09;处理复杂报表和大数据&…

企业微信iPad协议端强制拉群漏洞深度分析

正常一次最多邀请40人进群 超过40人的拉群&#xff0c;会变成邀请&#xff0c;需要对方同意 新版本修复了漏洞&#xff0c;但还是可以用老版本进行强制拉群 虽然官方也做了版本过低的限制&#xff0c;但还是有办法绕过 要么修改版本号或者登录几天新版本&#xff0c;之后就可以…

Python编译器(Pycharm Jupyter)

Pycharm下载不过多赘述pycharm导入anaconda创建的python环境选择想要的环境 Jupyter Jupyter 是一个开源的交互式计算环境&#xff0c;能够让用户将代码、文本&#xff08;包括 Markdown&#xff09;、可视化结果等内容整合在一个文档中&#xff0c;非常适合进行数据分析、科学…

漏洞修复与Fiddler抓包工具的使用

漏洞描述 1. 短信轰炸漏洞 Type:存在三个不同的值。Login是登录处,register是注册账号处的短信验证码获取值,还有一个update值。未注册的用户也可以进行发送短信。 2. 手机号绕过,修改密码漏洞(逻辑漏洞) 目前注册使用手机号与忘记密码的手机号验证测试都可以绕过, …

对象存储-OSS

目录 对象存储背景 阿里云OSS 对象存储背景 单节点环境下&#xff0c;文件往往存储在tomcat服务器内&#xff0c;随着业务需求的增多&#xff0c;单节点已不能满足需求&#xff0c;项目架构需要扩展到多节点&#xff08;见下图&#xff09;&#xff0c;此时文…

C语言函数的声明

1定义&#xff1a;在C语言中&#xff0c;函数是一段具有特定功能的独立代码块&#xff0c;它可以接收输入参数、执行相关操作并返回结果。2为什么需要函数&#xff08;1&#xff09;代码复用&#xff1a;避免重复编写相同功能的代码&#xff0c; &#xff08;2&#xff09;模块…

AI人工智能名片小程序源码系统,名片小程序+分销商城+AI客服,包含完整搭建教程

智能名片核心功能AI人工智能名片小程序的核心功能设计旨在彻底改变传统商务交流方式&#xff0c;为用户提供前所未有的智能化体验。个性化名片展示是系统的基础功能&#xff0c;用户可以通过丰富的模板库和自定义设计工具&#xff0c;创建独具特色的电子名片。系统提供多种预设…