You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot中金额校验异常处理:是否需自定义异常类?

问题描述

我需要比较两个字段,当余额小于转账金额时,抛出"Amount cannot be less than balance"异常。原本想使用MethodArgumentNotValidException,但这个异常没法直接传入自定义消息:

public void transfer(Request request) {
    if(sender.getBalance().compareTo(account.getAmount()) < 0)
        throw new MethodArgumentNotValidException(MESSAGE);
    // ...
}

我已经有全局异常处理器GlobalExceptionHandler,还有处理"Element Not found"或"Element already exists"场景的自定义异常类。那我是否需要实现如下自定义异常?

public class CustomMethodArgumentNotValidException extends RuntimeException {
    public MethodArgumentNotValidException() {
        super();
    }
    public NoSuchElementFoundException(String message) {
        super(message);
    }
}

之后要不要把这个自定义异常加到全局异常处理器里?还是不用自定义,直接用现有方案?


更新内容

全局异常处理器代码

@RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

    @Override
    @ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
    protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
                                                                  HttpHeaders headers,
                                                                  HttpStatusCode statusCode,
                                                                  WebRequest request) {
        ErrorResponse errorResponse = new ErrorResponse(HttpStatus.UNPROCESSABLE_ENTITY.value(), VALIDATION_ERROR);
        for (FieldError fieldError : ex.getBindingResult().getFieldErrors()) {
            errorResponse.addValidationError(fieldError.getField(), fieldError.getDefaultMessage());
        }
        return ResponseEntity.unprocessableEntity().body(errorResponse);
    }

    @ExceptionHandler(NoSuchElementFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public ResponseEntity<Object> handleNoSuchElementFoundException(NoSuchElementFoundException ex, WebRequest request) {
        return buildErrorResponse(ex, HttpStatus.NOT_FOUND, request);
    }

    @ExceptionHandler(InsufficientFundsException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ResponseEntity<Object> handleMethodArgumentInvalidException(InsufficientFundsException ex, WebRequest request) {
        return buildErrorResponse(ex, HttpStatus.BAD_REQUEST, request);
    }

    @ExceptionHandler(AuthenticationException.class)
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    public ResponseEntity<Object> handleAuthenticationException(AuthenticationException ex, WebRequest request) {
        return buildErrorResponse(ex, HttpStatus.UNAUTHORIZED, request);
    }

    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ResponseEntity<Object> handleAllUncaughtException(Exception ex, WebRequest request) {
        return buildErrorResponse(ex, UNKNOWN_ERROR, HttpStatus.INTERNAL_SERVER_ERROR, request);
    }

    private ResponseEntity<Object> buildErrorResponse(Exception ex,
                                                      HttpStatusCode statusCode,
                                                      WebRequest request) {
        return buildErrorResponse(ex, ex.getMessage(), statusCode, request);
    }

    private ResponseEntity<Object> buildErrorResponse(Exception ex,
                                                      String message,
                                                      HttpStatusCode statusCode,
                                                      WebRequest request) {
        final ErrorResponse errorResponse = new ErrorResponse(statusCode.value(), message);
        if (printStackTrace && isTraceOn(request)) {
            errorResponse.setStackTrace(ExceptionUtils.getStackTrace(ex));
        }
        return ResponseEntity.status(statusCode).body(errorResponse);
    }

    private boolean isTraceOn(WebRequest request) {
        String[] value = request.getParameterValues(TRACE); // TRACE from app properties
        return Objects.nonNull(value)
                && value.length > 0
                && value[0].contentEquals("true");
    }

    @Override
    public ResponseEntity<Object> handleExceptionInternal(
            Exception ex,
            Object body,
            HttpHeaders headers,
            HttpStatusCode statusCode,
            WebRequest request) {

        return buildErrorResponse(ex, statusCode, request);
    }
}

自定义异常代码

public class InsufficientFundsException extends RuntimeException {

    public InsufficientFundsException() {
        super();
    }

    public InsufficientFundsException(String message) {
        super(message);
    }

    public InsufficientFundsException(String message, Throwable cause) {
        super(message, cause);
    }
}

解决方案
  • 不要使用MethodArgumentNotValidException:这个异常是Spring专门用于处理@Valid等注解触发的请求参数校验场景,并非业务逻辑校验。它的构造依赖BindingResult等参数,无法直接传入自定义消息,强行使用会混淆异常语义。
  • 直接复用已定义的InsufficientFundsException:这个异常语义明确(余额不足),完全匹配你的业务场景,且你已经在全局异常处理器中配置了对应的处理逻辑,会返回BAD_REQUEST状态码和自定义消息,完全满足需求。
  • 修改业务代码:将抛出异常的逻辑替换为:
public void transfer(Request request) {
    if(sender.getBalance().compareTo(account.getAmount()) < 0)
        throw new InsufficientFundsException("Amount cannot be less than balance");
    // ...
}
  • 无需额外自定义CustomMethodArgumentNotValidException:现有InsufficientFundsException已适配全局处理器,无需重复实现。

内容的提问来源于stack exchange,提问作者Jack

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 17:43:09