Spring Boot异常处理实践:构建统一的JSON响应机制

# Spring Boot异常处理实践:构建统一的JSON响应机制


在Web应用开发中,异常处理是保证系统稳定性和用户体验的重要环节。Spring Boot提供了强大的异常处理机制,通过合理的封装和统一处理,可以让API返回规范的JSON格式响应,提升系统的可维护性和可用性。


## 一、异常处理的必要性分析


未处理的异常会导致不友好的用户界面、不一致的API响应格式,甚至可能暴露系统内部信息。统一的异常处理机制能够确保:


1. 所有异常被适当捕获并转化为标准响应格式

2. 敏感信息不被泄露到客户端

3. 提供清晰的错误信息供前端处理

4. 保持API响应的一致性


## 二、基础响应结构设计


首先定义统一的API响应格式,确保所有接口返回一致的结构:


```java

@Data

@AllArgsConstructor

@NoArgsConstructor

public class ApiResponse {

    private Integer code;      // 状态码

    private String message;    // 描述信息

    private T data;           // 业务数据

    private Long timestamp;   // 时间戳

    

    public static ApiResponse success(T data) {

        return new ApiResponse<>(200, "操作成功", data, System.currentTimeMillis());

    }

    

    public static ApiResponse error(Integer code, String message) {

        return new ApiResponse<>(code, message, null, System.currentTimeMillis());

    }

}

```


## 三、自定义业务异常体系


创建一套业务异常类,区分不同类型的异常情况:


```java

// 基础业务异常

public class BusinessException extends RuntimeException {

    private Integer code;

    

    public BusinessException(Integer code, String message) {

        super(message);

        this.code = code;

    }

    

    public Integer getCode() {

        return code;

    }

}


// 具体业务异常示例

public class ResourceNotFoundException extends BusinessException {

    public ResourceNotFoundException(String message) {

        super(404, message);

    }

}


public class ValidationException extends BusinessException {

    public ValidationException(String message) {

        super(400, message);

    }

}


public class UnauthorizedException extends BusinessException {

    public UnauthorizedException(String message) {

        super(401, message);

    }

}

```


## 四、全局异常处理器实现


使用`@RestControllerAdvice`注解创建全局异常处理器:


```java

@RestControllerAdvice

@Slf4j

public class GlobalExceptionHandler {

    

    // 处理业务异常

    @ExceptionHandler(BusinessException.class)

    public ApiResponse handleBusinessException(BusinessException e) {

        log.warn("业务异常: {}", e.getMessage(), e);

        return ApiResponse.error(e.getCode(), e.getMessage());

    }

    

    // 处理参数校验异常

    @ExceptionHandler(MethodArgumentNotValidException.class)

    public ApiResponse handleValidationException(

            MethodArgumentNotValidException e) {

        String message = e.getBindingResult()

                .getFieldErrors()

                .stream()

                .map(error -> error.getField() + ": " + error.getDefaultMessage())

                .collect(Collectors.joining("; "));

        

        log.warn("参数校验失败: {}", message);

        return ApiResponse.error(400, message);

    }

    

    // 处理认证授权异常

    @ExceptionHandler(AccessDeniedException.class)

    public ApiResponse handleAccessDeniedException(

            AccessDeniedException e) {

        log.warn("访问拒绝: {}", e.getMessage());

        return ApiResponse.error(403, "权限不足");

    }

    <"m7.p5k3.org.cn"><"a9.p5k3.org.cn"><"d2.p5k3.org.cn">

    // 处理其他未预期异常

    @ExceptionHandler(Exception.class)

    public ApiResponse handleException(Exception e) {

        log.error("系统异常: ", e);

        return ApiResponse.error(500, "系统繁忙,请稍后再试");

    }

}

```


## 五、数据校验与异常结合


将数据校验与异常处理结合,提供更友好的错误提示:


```java

@Data

public class UserCreateRequest {

    

    @NotBlank(message = "用户名不能为空")

    @Size(min = 3, max = 20, message = "用户名长度应在3-20个字符之间")

    private String username;

    

    @Email(message = "邮箱格式不正确")

    private String email;

    

    @Pattern(regexp = "^(?=.*[A-Za-z])(?=.*\\d)[A-Za-z\\d]{8,}$",

            message = "密码必须包含字母和数字,且长度至少8位")

    private String password;

}


@RestController

@RequestMapping("/api/users")

public class UserController {

    

    @PostMapping

    public ApiResponse createUser(@Valid @RequestBody UserCreateRequest request) {

        // 参数校验会自动触发MethodArgumentNotValidException

        // 业务逻辑处理

        User user = userService.createUser(request);

        return ApiResponse.success(user);

    }

    

    @GetMapping("/{id}")

    public ApiResponse getUser(@PathVariable Long id) {

        User user = userService.getUserById(id);

        if (user == null) {

            throw new ResourceNotFoundException("用户不存在");

        }

        return ApiResponse.success(user);

    }

}

```


## 六、HTTP状态码映射策略


虽然RESTful API建议使用HTTP状态码,但在实际开发中,业务状态码可以提供更精确的错误信息。可以采用混合策略:


```java

@RestControllerAdvice

public class ResponseStatusHandler {

    

    @ExceptionHandler(ResourceNotFoundException.class)

    @ResponseStatus(HttpStatus.NOT_FOUND)

    public ApiResponse handleNotFound(ResourceNotFoundException e) {

        return ApiResponse.error(404, e.getMessage());

    }

    

    @ExceptionHandler(UnauthorizedException.class)

    @ResponseStatus(HttpStatus.UNAUTHORIZED)

    public ApiResponse handleUnauthorized(UnauthorizedException e) {

        return ApiResponse.error(401, e.getMessage());

    }

}

```


## 七、日志记录与监控集成


异常处理中的日志记录对问题排查至关重要:


```java

@Slf4j

@RestControllerAdvice

public class EnhancedExceptionHandler extends GlobalExceptionHandler {

    

    @Value("${app.exception-log.enable-stacktrace:false}")

    private boolean enableStacktrace;

    

    @ExceptionHandler(Exception.class)

    public ApiResponse handleExceptionWithMetrics(Exception e, 

            HttpServletRequest request) {

        

        // 记录异常基本信息

        log.error("请求异常 - URI: {}, Method: {}, Query: {}",

                request.getRequestURI(),

                request.getMethod(),

                request.getQueryString());

        

        // 根据配置决定是否记录堆栈信息

        if (enableStacktrace) {

            log.error("异常堆栈: ", e);

        } else {

            log.error("异常信息: {}", e.getMessage());

        }

        <"h5.p5k3.org.cn"><"v1.p5k3.org.cn"><"s8.p5k3.org.cn">

        // 可以集成监控系统

        // metricsCollector.recordException(e.getClass().getSimpleName());

        

        return ApiResponse.error(500, "系统繁忙,请稍后再试");

    }

}

```


## 八、异常处理的最佳实践


1. **异常分类明确**:区分业务异常、系统异常、第三方服务异常

2. **错误信息适度**:对用户显示友好信息,在日志中记录详细信息

3. **避免过度捕获**:只处理能处理的异常,其他交给上层

4. **资源清理保证**:在异常处理中确保资源正确释放


```java

@Service

@Slf4j

public class OrderService {

    

    @Transactional

    public Order createOrder(OrderRequest request) {

        try {

            // 业务逻辑

            validateOrder(request);

            Order order = buildOrder(request);

            orderRepository.save(order);

            

            // 可能抛出异常的外部调用

            inventoryService.updateStock(order);

            paymentService.processPayment(order);

            

            return order;

        } catch (InventoryException e) {

            log.error("库存操作失败", e);

            throw new BusinessException(1001, "库存不足,订单创建失败");

        } catch (PaymentException e) {

            log.error("支付处理失败", e);

            throw new BusinessException(1002, "支付处理失败");

        }

    }

}

```


## 九、测试异常处理逻辑


确保异常处理逻辑的正确性需要充分的测试:


```java

@SpringBootTest

@AutoConfigureMockMvc

class UserControllerTest {

    

    @Autowired

    private MockMvc mockMvc;

    

    @Test

    void testUserNotFound() throws Exception {

        mockMvc.perform(get("/api/users/99999"))

                .andExpect(status().isNotFound())

                .andExpect(jsonPath("$.code").value(404))

                .andExpect(jsonPath("$.message").value("用户不存在"));

    }

    

    @Test

    void testInvalidUserCreation() throws Exception {

        String invalidUserJson = "{\"username\":\"ab\",\"email\":\"invalid\"}";

        

        mockMvc.perform(post("/api/users")

                .contentType(MediaType.APPLICATION_JSON)

                .content(invalidUserJson))

                .andExpect(status().isBadRequest())

                .andExpect(jsonPath("$.code").value(400))

                .andExpect(jsonPath("$.message").contains("用户名长度"));

    }

}

```


通过本文介绍的Spring Boot统一异常处理方案,开发者可以构建健壮的API系统,提供一致的错误响应格式。这种处理方式不仅提升了用户体验,也便于前后端协作开发和问题排查。在实践中,应根据具体业务需求调整异常分类和响应细节,建立适合项目特点的异常处理体系。