Spring Boot微服务统一异常处理与全局响应规范设计实战

微服务架构中,统一异常处理和标准化响应格式是API接口规范的基础。Spring Boot提供了@RestControllerAdvice全局异常拦截机制,配合自定义异常体系可实现错误的精细化分类和友好响应。后端开发中,散落的try-catch块导致异常处理逻辑不一致,全局拦截器能将异常处理收敛到统一入口。本文基于Spring Boot框架实践,从异常体系设计到响应规范落地,拆解微服务统一错误处理的完整方案。

Spring Boot全局异常处理器配置

@RestControllerAdvice是Spring Boot 3.x中处理REST API异常的核心注解,通过@ExceptionHandler方法拦截Controller抛出的异常。基本配置:

@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    @ExceptionHandler(BusinessException.class)
    public ResponseEntity<ApiResponse<Void>> handleBusiness(
            BusinessException ex, HttpServletRequest request) {
        log.warn("业务异常: code={}, message={}, path={}",
            ex.getCode(), ex.getMessage(), request.getRequestURI());
        return ResponseEntity
            .status(ex.getHttpStatus())
            .body(ApiResponse.error(ex.getCode(), ex.getMessage()));
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ApiResponse<Void>> handleValidation(
            MethodArgumentNotValidException ex) {
        List<FieldError> errors = ex.getBindingResult()
            .getFieldErrors().stream()
            .map(e -> new FieldError(
                e.getField(),
                e.getDefaultMessage(),
                e.getRejectedValue()))
            .collect(Collectors.toList());
        log.warn("参数校验失败: {}", errors);
        return ResponseEntity
            .status(HttpStatus.BAD_REQUEST)
            .body(ApiResponse.error(400, "参数校验失败", errors));
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ApiResponse<Void>> handleUnknown(
            Exception ex, HttpServletRequest request) {
        log.error("未处理异常: path={}", request.getRequestURI(), ex);
        return ResponseEntity
            .status(HttpStatus.INTERNAL_SERVER_ERROR)
            .body(ApiResponse.error(500, "系统内部错误"));
    }
}

异常处理器的匹配顺序:Spring按异常类型从具体到通用匹配。BusinessException比Exception更具体,会优先匹配。多个@ExceptionHandler覆盖同一异常层级时,最具体的处理器生效。

自定义业务异常体系设计

异常体系设计遵循单一继承根、按业务域分类的原则。基础异常类定义通用字段,子类按业务场景细化:

@Getter
public class BusinessException extends RuntimeException {
    private final int code;
    private final HttpStatus httpStatus;

    public BusinessException(int code, String message) {
        super(message);
        this.code = code;
        this.httpStatus = HttpStatus.BAD_REQUEST;
    }

    public BusinessException(int code, String message, HttpStatus httpStatus) {
        super(message);
        this.code = code;
        this.httpStatus = httpStatus;
    }
}

// 认证异常
public class AuthenticationException extends BusinessException {
    public AuthenticationException(String message) {
        super(401, message, HttpStatus.UNAUTHORIZED);
    }
}

// 权限异常
public class AuthorizationException extends BusinessException {
    public AuthorizationException(String message) {
        super(403, message, HttpStatus.FORBIDDEN);
    }
}

// 资源不存在
public class NotFoundException extends BusinessException {
    public NotFoundException(String resource, Object id) {
        super(404, resource + "不存在: " + id, HttpStatus.NOT_FOUND);
    }
}

// 冲突异常(重复创建)
public class ConflictException extends BusinessException {
    public ConflictException(String message) {
        super(409, message, HttpStatus.CONFLICT);
    }
}

// 限流异常
public class RateLimitException extends BusinessException {
    public RateLimitException(String message) {
        super(429, message, HttpStatus.TOO_MANY_REQUESTS);
    }
}

业务代码中直接抛出语义化异常,无需手动包装响应:

@Service
public class OrderService {

    public Order createOrder(OrderRequest req) {
        User user = userRepository.findById(req.getUserId())
            .orElseThrow(() -> new NotFoundException("用户", req.getUserId()));

        if (orderRepository.existsByOrderNo(req.getOrderNo())) {
            throw new ConflictException("订单号已存在: " + req.getOrderNo());
        }

        if (user.getStatus() != UserStatus.ACTIVE) {
            throw new BusinessException(40010, "用户已被冻结");
        }

        Order order = new Order();
        order.setOrderNo(req.getOrderNo());
        order.setUser(user);
        return orderRepository.save(order);
    }
}

统一响应结果封装规范

统一响应体使用泛型支持不同场景的数据返回:

@Data
@Schema(description = "统一响应")
public class ApiResponse<T> {
    @Schema(description = "状态码,0表示成功")
    private int code;

    @Schema(description = "提示信息")
    private String message;

    @Schema(description = "响应数据")
    private T data;

    @Schema(description = "时间戳")
    private long timestamp = System.currentTimeMillis();

    @Schema(description = "请求追踪ID")
    private String traceId;

    public static <T> ApiResponse<T> success(T data) {
        ApiResponse<T> resp = new ApiResponse<>();
        resp.code = 0;
        resp.message = "success";
        resp.data = data;
        return resp;
    }

    public static <T> ApiResponse<T> success(T data, String message) {
        ApiResponse<T> resp = success(data);
        resp.message = message;
        return resp;
    }

    public static <T> ApiResponse<T> error(int code, String message) {
        ApiResponse<T> resp = new ApiResponse<>();
        resp.code = code;
        resp.message = message;
        return resp;
    }

    public static <T> ApiResponse<T> error(int code, String message, T data) {
        ApiResponse<T> resp = error(code, message);
        resp.data = data;
        return resp;
    }
}

Controller层配合统一响应,通过ResponseBodyAdvice自动包装成功响应,避免每个方法手动包装:

@RestControllerAdvice
public class ResponseWrapperAdvice implements ResponseBodyAdvice<Object> {

    @Override
    public boolean supports(MethodParameter returnType,
            Class converterType) {
        // 排除已经包装过和错误响应
        return !returnType.getParameterType().equals(ApiResponse.class)
            && !returnType.getParameterType().equals(ResponseEntity.class);
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType,
            MediaType selectedContentType,
            Class selectedConverterType,
            ServerHttpRequest request,
            ServerHttpResponse response) {
        if (body == null) {
            return ApiResponse.success(null);
        }
        return ApiResponse.success(body);
    }
}

Controller方法直接返回业务数据,自动包装为统一格式:

@RestController
@RequestMapping("/api/orders")
public class OrderController {

    @GetMapping("/{id}")
    public Order getOrder(@PathVariable Long id) {
        return orderService.findById(id); // 自动包装为ApiResponse.success(order)
    }

    @PostMapping
    public Order createOrder(@Valid @RequestBody OrderRequest req) {
        return orderService.createOrder(req);
    }
}

异常处理链路与参数校验集成

Spring Validation校验失败产生MethodArgumentNotValidException(表单校验)和ConstraintViolationException(方法参数校验),统一拦截处理:

@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<ApiResponse<Void>> handleConstraint(
        ConstraintViolationException ex) {
    List<FieldError> errors = ex.getConstraintViolations().stream()
        .map(v -> new FieldError(
            v.getPropertyPath().toString(),
            v.getMessage(),
            v.getInvalidValue()))
        .collect(Collectors.toList());
    return ResponseEntity.status(HttpStatus.BAD_REQUEST)
        .body(ApiResponse.error(400, "参数校验失败", errors));
}

@ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseEntity<ApiResponse<Void>> handleJsonParse(
        HttpMessageNotReadableException ex) {
    return ResponseEntity.status(HttpStatus.BAD_REQUEST)
        .body(ApiResponse.error(400, "请求体格式错误"));
}

@ExceptionHandler(MissingServletRequestParameterException.class)
public ResponseEntity<ApiResponse<Void>> handleMissingParam(
        MissingServletRequestParameterException ex) {
    return ResponseEntity.status(HttpStatus.BAD_REQUEST)
        .body(ApiResponse.error(400, "缺少必要参数: " + ex.getParameterName()));
}

自定义校验注解配合异常体系,在业务层面补充Bean Validation无法覆盖的跨字段校验:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = OrderValidator.class)
public @interface ValidOrder {
    String message() default "订单参数不合法";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

public class OrderValidator
        implements ConstraintValidator<ValidOrder, OrderRequest> {
    @Override
    public boolean isValid(OrderRequest req, ConstraintValidatorContext ctx) {
        if (req == null) return false;
        // 跨字段校验逻辑
        return req.getEndDate() == null
            || req.getStartDate() == null
            || !req.getEndDate().isBefore(req.getStartDate());
    }
}

生产环境异常日志与监控方案

异常日志分级记录:业务异常记录WARN级别,包含错误码和用户友好信息;系统异常记录ERROR级别,包含完整堆栈。通过MDC注入traceId实现链路追踪:

@Component
public class TraceIdFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request,
            HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException {
        String traceId = request.getHeader("X-Trace-Id");
        if (traceId == null) {
            traceId = UUID.randomUUID().toString().replace("-", "");
        }
        MDC.put("traceId", traceId);
        response.setHeader("X-Trace-Id", traceId);
        try {
            chain.doFilter(request, response);
        } finally {
            MDC.clear();
        }
    }
}

logback配置中使用traceId,日志中自动注入请求追踪ID,便于在全链路日志中关联同一次请求的所有异常记录。

异常监控告警:对ERROR级别异常接入告警系统,设置异常频次阈值。同一业务异常码在短时间内大量出现时触发告警,提示下游服务故障或参数问题。业务异常(4xx)频次监控用于发现API使用模式异常,如大量404可能表示前端调用路径错误。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/springboot-wei-fu-wu-tong-yi-yi-chang-chu-li-yu-quan-ju/

(0)
小编小编
上一篇 8小时前
下一篇 8小时前

相关推荐