如何返回自定义错误而非500内部服务器错误?自定义GlobalExceptionHandler失效求助
首先,咱们拆解一下为什么你的全局异常处理器没生效:
核心问题1:Spring参数校验抛出的不是你自定义的异常
你在控制器方法上加了@Valid注解,Spring会在请求进入方法之前自动执行参数校验,当校验失败时(比如你的school字段为null),抛出的是Spring自带的MethodArgumentNotValidException,而不是你写的BadRequestException。你的全局异常处理器只监听了BadRequestException,自然抓不到这个校验异常,导致Spring返回默认的500错误。
核心问题2:你的控制器类没被Spring识别
看你的SchoolController代码,它没有添加@RestController或@Controller注解!Spring不会把这个类当作控制器来管理,那@PostMapping、@Valid这些注解都不会生效,业务逻辑里的异常抛出也走不到全局处理器。
核心问题3:自定义异常的触发逻辑没被执行
因为参数校验在进入控制器方法前就失败了,你的schoolRule方法根本没机会执行,所以里面的throw new BadRequestException()代码从来没被触发过。
解决方案
第一步:修复控制器类
先给SchoolController加上@RestController注解,确保Spring能识别它:
@RestController public class SchoolController { private final School schoolService; // 推荐用构造方法注入(避免字段注入的弊端) public SchoolController(School schoolService) { this.schoolService = schoolService; } @PostMapping(ApiPaths.schoolController.CTRL) public ResponseEntity<SchoolResponse> createSchool(@Valid @RequestBody SchoolRequest request) throws Exception { return schoolService.createSchool(request); } }
第二步:修改全局异常处理器,捕获参数校验异常
在GlobalExceptionHandler里新增一个方法,专门处理Spring的MethodArgumentNotValidException,把它转换成你自定义的响应格式:
import org.springframework.validation.FieldError; import java.util.stream.Collectors; @ControllerAdvice public class GlobalExceptionHandler { public static final String EXCEPTION_OCCURED_LOG_PREFIX = "Exception occured: {}"; public static final String EXCEPTION_OCCURED_LOG_DETAIL = "Exception log detail occured: {}, class: {}"; Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class); // 处理自定义的BadRequestException @ExceptionHandler({BadRequestException.class}) public ResponseEntity<BaseResponse> handleBadRequest(BadRequestException ex) { String logSuffix = Arrays.toString(ex.getStackTrace()); logger.error(EXCEPTION_OCCURED_LOG_PREFIX, logSuffix); logger.error(EXCEPTION_OCCURED_LOG_DETAIL, ex.getMessage(), ex.getClass()); BaseResponse response = new BaseResponse(); response.setCode(Constants.B_BADREQUEST); response.setMessage("BadRequestException occured! Exception details: ".concat(ex.getMessage())); return ResponseEntity .status(HttpStatus.BAD_REQUEST) .body(response); } // 新增:处理Spring参数校验抛出的MethodArgumentNotValidException @ExceptionHandler({MethodArgumentNotValidException.class}) public ResponseEntity<BaseResponse> handleValidationFailure(MethodArgumentNotValidException ex) { // 提取所有校验失败的字段和错误信息 String errorDetails = ex.getBindingResult() .getFieldErrors() .stream() .map(error -> String.format("%s: %s", error.getField(), error.getDefaultMessage())) .collect(Collectors.joining("; ")); logger.error(EXCEPTION_OCCURED_LOG_DETAIL, errorDetails, ex.getClass()); BaseResponse response = new BaseResponse(); response.setCode(Constants.B_BADREQUEST); response.setMessage("Parameter validation failed! Details: ".concat(errorDetails)); return ResponseEntity .status(HttpStatus.BAD_REQUEST) .body(response); } }
可选:如果想统一用自定义异常处理校验
如果你希望所有校验失败都抛出你自己的BadRequestException,可以禁用Spring自动校验,手动触发:
- 去掉控制器方法上的
@Valid注解 - 在业务类中注入
Validator,手动校验参数并抛出自定义异常:
import javax.validation.ConstraintViolation; import javax.validation.Validator; import java.util.Set; import java.util.stream.Collectors; @Service public class School { private final Validator validator; // 构造方法注入Validator public School(Validator validator) { this.validator = validator; } private void schoolRule(SchoolRequest schoolRequest) { Set<ConstraintViolation<SchoolRequest>> violations = validator.validate(schoolRequest); if (!violations.isEmpty()) { String errorMsg = violations.stream() .map(ConstraintViolation::getMessage) .collect(Collectors.joining("; ")); throw new BadRequestException(errorMsg); } } public ResponseEntity<SchoolResponse> createSchool(SchoolRequest request) { schoolRule(request); // 先执行手动校验 // 你的业务逻辑... return ResponseEntity.ok(new SchoolResponse()); } }
这样,校验失败时就会抛出BadRequestException,被你的全局处理器捕获,返回自定义的400响应。
内容的提问来源于stack exchange,提问作者mark whalf

