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

Spring MVC中Java Record抛出CustomException未被正确捕获的问题

问题根源

Spring在将请求体绑定到Java Record实例时,会通过反射调用构造器。如果构造器抛出异常,Spring会把它包装成BeanInstantiationException,导致你的@RestControllerAdvice无法直接捕获到自定义的CustomException,最终返回500状态码。

解决方案与最佳实践

1. 优先用Spring Validation注解(最推荐)

这是符合Spring生态的最优方案,无需手动写校验逻辑,直接借助JSR-380注解完成参数校验:

  • 给Record字段添加@NotNull等校验注解
  • 控制器方法参数上标记@Valid触发校验
  • 全局异常处理器捕获MethodArgumentNotValidException,返回440状态码

示例代码:

// 请求Record
public record RequestRecord(@NotNull(message = "s1不能为空") String s1,
                            @NotNull(message = "s2不能为空") String s2) {}

// 控制器
@RestController
public class TestController {
    @PostMapping("/test")
    public ResponseEntity<String> test(@Valid @RequestBody RequestRecord request) {
        return ResponseEntity.ok("success");
    }
}

// 全局异常处理器
@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<String> handleValidationException(MethodArgumentNotValidException e) {
        String errorMsg = e.getBindingResult().getFieldErrors().stream()
                .map(FieldError::getDefaultMessage)
                .collect(Collectors.joining(","));
        return ResponseEntity.status(440).body(errorMsg);
    }
}

2. 自定义参数解析器捕获构造器异常

如果必须在Record构造器里手动抛CustomException,可以自定义参数解析器,绕开Spring的默认实例化逻辑,直接捕获原始异常:

示例代码:

// 自定义参数解析器
@Component
public class RecordArgumentResolver implements HandlerMethodArgumentResolver {
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.getParameterType().isRecord();
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
                                  NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
        ObjectMapper objectMapper = new ObjectMapper();
        try {
            String requestBody = request.getReader().lines().collect(Collectors.joining());
            return objectMapper.readValue(requestBody, parameter.getParameterType());
        } catch (JsonProcessingException e) {
            throw new CustomException("请求格式错误");
        } catch (InvocationTargetException e) {
            // 提取构造器抛出的自定义异常
            if (e.getTargetException() instanceof CustomException customException) {
                throw customException;
            }
            throw e;
        }
    }
}

// 注册参数解析器
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    private RecordArgumentResolver recordArgumentResolver;

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(recordArgumentResolver);
    }
}

// 带手动校验的Record
public record RequestRecord(String s1, String s2) {
    public RequestRecord {
        if (s1 == null) throw new CustomException("s1不能为空");
        if (s2 == null) throw new CustomException("s2不能为空");
    }
}

// 全局异常处理器捕获CustomException
@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(CustomException.class)
    public ResponseEntity<String> handleCustomException(CustomException e) {
        return ResponseEntity.status(440).body(e.getMessage());
    }
}

3. 从BeanInstantiationException中提取原始异常

如果不想修改参数解析逻辑,也可以在全局异常处理器中直接处理Spring包装后的异常,提取内部的CustomException:

示例代码:

@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(BeanInstantiationException.class)
    public ResponseEntity<String> handleBeanInstantiationException(BeanInstantiationException e) {
        Throwable cause = e.getCause();
        if (cause instanceof CustomException customException) {
            return ResponseEntity.status(440).body(customException.getMessage());
        }
        // 其他情况返回500
        return ResponseEntity.status(500).body("服务器内部错误");
    }
}
最佳实践总结
  • 优先使用Spring Validation注解:代码简洁、符合规范,彻底避免异常包装问题
  • 尽量不在Record构造器中直接抛业务异常:Spring的参数绑定机制会自动包装异常,增加处理复杂度
  • 若必须手动校验,优先选择自定义参数解析器的方式,直接捕获原始异常,保证异常处理的准确性
  • 全局异常处理器要考虑异常的嵌套包装情况,确保能正确识别业务异常并返回对应状态码

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 09:57:17