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

Spring Boot中如何结合@PreAuthorize动态值与@ExceptionHandler?

Spring Boot中@PreAuthorize动态参数与@ExceptionHandler结合的异常处理问题

问题场景

项目中使用带动态值的@PreAuthorize做权限校验,同时在请求模型上用@NotEmpty等注解做Bean校验,控制器代码如下:

@RestController
@RequestMapping(value = "/../{id}/...")
@PreAuthorize("hasAuthority(#id+'abc')")
public class TestController {

    @Autowired
    SerivceAbc service;

    @PostMapping("/test")
    public ResponseEntity<Object> validate(@PathVariable String id,
            @Valid @RequestBody RequestModel Request,
            ContentCachingRequestWrapper requestWrapper) {
        return ResponseEntity.ok(service.abc(Request, requestWrapper));
    }

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler( MethodArgumentNotValidException.class)
    ResponseEntity<Object> handleValidationExceptions(MethodArgumentNotValidException ex,
            ContentCachingRequestWrapper requestWrapper) {
        ResponseModel invalidResponse = service.invalidResponse(ex,
                requestWrapper);
        return ResponseEntity.badRequest().body(invalidResponse);
    }

    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(Exception.class)
    ResponseEntity<Object> handleValidationExceptions(Exception ex, ContentCachingRequestWrapper requestWrapper) {
        ResponseModel invalidResponse = service.invalidResponse(ex,
                requestWrapper);
        return ResponseEntity.badRequest().body(invalidDssReponse);
    }
}

问题现象

  • 请求字段合法时,动态参数id能正常配合@PreAuthorize完成权限校验,接口正常返回。
  • 触发Bean校验(比如请求字段为空)时,handleValidationExceptions方法无法被调用。
  • 尝试给异常处理方法添加id参数时,抛出错误:

java.lang.IllegalStateException: Could not resolve parameter [2] in org.springframework.http.ResponseEntity<java.lang.Object> com.controller.TestController.handleValidationExceptions(org.springframework.web.bind.MethodArgumentNotValidException,org.springframework.web.util.ContentCachingRequestWrapper,java.lang.String): No suitable resolver

原因分析

  1. 类级别@PreAuthorize作用于所有控制器方法,但异常处理方法不属于请求处理方法范畴,Spring无法自动为其解析路径变量id。
  2. 异常处理方法的参数解析逻辑和常规请求方法不同,默认不支持直接注入@PathVariable参数。

解决方案

方案1:从请求对象手动获取路径变量

不需要修改异常处理方法的参数列表,直接从HttpServletRequest中提取路径变量:

@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentNotValidException.class)
ResponseEntity<Object> handleValidationExceptions(MethodArgumentNotValidException ex,
                                                 HttpServletRequest request) {
    // 从请求属性中获取已解析的路径变量
    Map<String, String> uriVars = (Map<String, String>) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
    String id = uriVars.get("id");
    
    // 包装成ContentCachingRequestWrapper(如果需要)
    ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request);
    ResponseModel invalidResponse = service.invalidResponse(ex, requestWrapper);
    return ResponseEntity.badRequest().body(invalidResponse);
}

方案2:将@PreAuthorize移至方法级别

把类上的权限注解移到具体的请求处理方法上,避免类级别注解对异常处理逻辑的干扰:

@RestController
@RequestMapping(value = "/../{id}/...")
public class TestController {

    @Autowired
    SerivceAbc service;

    @PostMapping("/test")
    @PreAuthorize("hasAuthority(#id+'abc')") // 移到方法上
    public ResponseEntity<Object> validate(@PathVariable String id,
            @Valid @RequestBody RequestModel Request,
            ContentCachingRequestWrapper requestWrapper) {
        return ResponseEntity.ok(service.abc(Request, requestWrapper));
    }

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    ResponseEntity<Object> handleValidationExceptions(MethodArgumentNotValidException ex,
            ContentCachingRequestWrapper requestWrapper) {
        ResponseModel invalidResponse = service.invalidResponse(ex, requestWrapper);
        return ResponseEntity.badRequest().body(invalidResponse);
    }

    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(Exception.class)
    ResponseEntity<Object> handleGlobalExceptions(Exception ex, ContentCachingRequestWrapper requestWrapper) {
        ResponseModel invalidResponse = service.invalidResponse(ex, requestWrapper);
        // 修正状态码,和注解保持一致
        return ResponseEntity.internalServerError().body(invalidResponse);
    }
}

备注:原代码中全局异常处理标注了INTERNAL_SERVER_ERROR却返回badRequest(),这里需要修正状态码,避免前后不一致。

方案3:自定义参数解析器(复用性强)

如果多个异常处理方法都需要获取路径变量,可以自定义参数解析器,让Spring支持在异常处理方法中注入@PathVariable:

  1. 实现参数解析器:
public class PathVariableExceptionResolver implements HandlerMethodArgumentResolver {

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        // 支持带@PathVariable注解的参数
        return parameter.hasParameterAnnotation(PathVariable.class);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
                                  NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
        if (request == null) {
            return null;
        }
        // 获取已解析的路径变量映射
        Map<String, String> uriVars = (Map<String, String>) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
        String varName = parameter.getParameterAnnotation(PathVariable.class).value();
        return uriVars.get(varName);
    }
}
  1. 注册解析器:
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(new PathVariableExceptionResolver());
    }
}
  1. 使用方式:
    现在异常处理方法可以直接添加@PathVariable参数:
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentNotValidException.class)
ResponseEntity<Object> handleValidationExceptions(MethodArgumentNotValidException ex,
                                                 ContentCachingRequestWrapper requestWrapper,
                                                 @PathVariable String id) {
    ResponseModel invalidResponse = service.invalidResponse(ex, requestWrapper);
    return ResponseEntity.badRequest().body(invalidResponse);
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 15:45:06