You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Java与Spring Boot分层架构下的异常处理最佳实践咨询

Java与Spring Boot分层架构下的异常处理最佳实践咨询

嘿,刚上手Java和Spring Boot就关注分层架构里的异常处理最佳实践,这点真的值得点赞!我结合实际项目踩过的坑和经验,给你拆解下这个问题~

一、各分层该在哪抛出异常?

核心原则是让异常和分层的职责匹配,不同层抛的异常要对应各自的关注点:

1. Repository层

Repository层的核心职责是封装数据库CRUD操作,所以这里尽量少手动抛异常

  • 数据库层面的异常(比如SQL语法错误、唯一键约束违反、查询无结果),Spring Data JPA/MyBatis这类框架会自动抛出对应的RuntimeException(比如EmptyResultDataAccessExceptionDataIntegrityViolationException),这些异常可以直接往上抛给Service层,不用自己捕获再包装。
  • 除非你在Repository里做了极少量的数据合法性校验(比如查询到的状态不符合预期),这时候可以抛出通用的IllegalStateException,但这种情况建议尽量把校验逻辑移到Service层,保持Repository的纯粹性。

2. Service层

Service层是业务逻辑的核心,所有和业务规则相关的异常都应该在这里抛出

  • 比如用户余额不足无法下单、订单状态不允许修改、传入的参数不符合业务规则(比如用户年龄不能小于18岁),这些场景都要在这里抛出异常。
  • 这里可以抛自定义业务异常,也可以用Spring的Assert工具类简化代码,比如:
    Assert.isTrue(user.getBalance() >= orderAmount, "用户余额不足,无法完成下单");
    
    不满足条件时会自动抛出IllegalArgumentException
  • 另外,Repository层抛上来的数据库异常,建议在Service层包装成业务相关的异常,比如把EmptyResultDataAccessException包装成UserNotFoundException,这样上层Controller不用关心数据库细节,只需要处理业务语义明确的异常。

3. Controller层

Controller层的职责是接收请求、调用Service、返回响应,这里尽量不要主动抛业务异常

  • 它的核心工作是处理Service层抛上来的各种异常,把异常转化为符合HTTP规范的响应(比如400 Bad Request、404 Not Found、500 Internal Server Error)。
  • 对于请求参数的校验异常(比如@Valid注解校验不通过抛出的MethodArgumentNotValidException),也建议在这里或全局处理器中统一处理,返回友好的参数错误提示。

二、除了自定义异常,还有哪些实用的异常处理方式?

自定义异常确实是业务异常处理的利器,但Spring生态还提供了很多现成的工具和方式,能帮我们减少重复代码:

  • 利用Spring自带的通用异常
    Spring已经为大部分通用场景定义了异常,比如:

    • EntityNotFoundException:JPA中查询实体不存在时抛出
    • MethodArgumentNotValidException:请求参数校验不通过时抛出
    • HttpRequestMethodNotSupportedException:请求方法不匹配时抛出
      这些异常覆盖了很多通用场景,不用自己重复定义类似的自定义异常。
  • 全局异常处理器
    @RestControllerAdvice(针对REST接口)或@ControllerAdvice配合@ExceptionHandler实现全局异常统一处理,这是Spring项目中最常用的方式,不用在每个Controller里写重复的try-catch。
    举个简单的实现示例:

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.ResponseEntity;
    import org.springframework.validation.BindingResult;
    import org.springframework.validation.FieldError;
    import org.springframework.web.bind.MethodArgumentNotValidException;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.annotation.RestControllerAdvice;
    
    import java.util.stream.Collectors;
    
    @RestControllerAdvice
    public class GlobalExceptionHandler {
        private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
    
        // 处理自定义业务异常
        @ExceptionHandler(BusinessException.class)
        public ResponseEntity<ErrorResponse> handleBusinessException(BusinessException e) {
            logger.warn("业务异常触发:{}", e.getMessage());
            ErrorResponse response = new ErrorResponse(HttpStatus.BAD_REQUEST.value(), e.getMessage());
            return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
        }
    
        // 处理请求参数校验异常
        @ExceptionHandler(MethodArgumentNotValidException.class)
        public ResponseEntity<ErrorResponse> handleValidationException(MethodArgumentNotValidException e) {
            BindingResult result = e.getBindingResult();
            String errorMsg = result.getFieldErrors().stream()
                    .map(fieldError -> fieldError.getField() + ": " + fieldError.getDefaultMessage())
                    .collect(Collectors.joining("; "));
            logger.warn("参数校验失败:{}", errorMsg);
            ErrorResponse response = new ErrorResponse(HttpStatus.BAD_REQUEST.value(), errorMsg);
            return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
        }
    
        // 处理系统级异常
        @ExceptionHandler(Exception.class)
        public ResponseEntity<ErrorResponse> handleSystemException(Exception e) {
            logger.error("系统未知异常", e);
            ErrorResponse response = new ErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR.value(), "服务器内部错误,请稍后重试");
            return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR);
        }
    
        // 内部错误响应类
        static class ErrorResponse {
            private int code;
            private String message;
    
            public ErrorResponse(int code, String message) {
                this.code = code;
                this.message = message;
            }
    
            // getter、setter 省略
        }
    }
    
  • 参数校验注解
    用JSR-380的校验注解(比如@NotNull@Size(min = 6)@Email)配合@Valid/@Validated注解,自动完成请求参数的合法性校验,Spring会在参数不合法时抛出MethodArgumentNotValidException,不用自己写一堆if-else判断抛异常。
    示例:

    @PostMapping("/users")
    public ResponseEntity<User> createUser(@RequestBody @Valid UserCreateRequest request) {
        User user = userService.createUser(request);
        return ResponseEntity.ok(user);
    }
    
    // 请求参数类
    static class UserCreateRequest {
        @NotNull(message = "用户名不能为空")
        @Size(min = 3, max = 20, message = "用户名长度必须在3-20之间")
        private String username;
    
        @Email(message = "邮箱格式不正确")
        private String email;
    
        // getter、setter 省略
    }
    
  • 断言工具类
    Spring提供的org.springframework.util.Assert类,能简化参数或状态校验的代码,比如:

    public void updateOrderStatus(Long orderId, Integer newStatus) {
        Order order = orderRepository.findById(orderId).orElseThrow(() -> new OrderNotFoundException("订单不存在"));
        // 断言订单当前状态允许修改
        Assert.isTrue(order.getStatus().equals(OrderStatus.PENDING), "只有待支付订单才能修改状态");
        // 后续业务逻辑
    }
    

    不满足条件时会直接抛出对应的RuntimeException,比手动写if-else抛异常简洁很多。

三、最后再总结下核心思路

  • 异常要分层职责清晰:Service管业务异常,Repository管数据库底层异常,Controller管异常转响应。
  • 自定义异常用于业务语义明确的场景,通用场景优先用Spring自带的异常。
  • 全局异常处理器是减少重复代码、统一响应格式的最佳实践,一定要用起来!

如果还有具体场景的疑问,比如某个特殊业务该怎么处理异常,随时再问~

火山引擎 最新活动