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

Spring Boot RestController如何无需@Valid注解实现所有参数校验

核心原因

类级别标注@Validated仅能开启方法简单参数的直接校验能力:也就是直接在@PathVariable、@RequestParam参数上标注@Min、@NotBlank这类约束注解时无需额外配置即可生效。但该注解不会自动触发@RequestBody、@ModelAttribute这类复杂封装对象的级联校验,Spring默认要求这类参数必须在定义处标注@Valid或@Validated,才会递归校验对象内部字段的约束,这也是手动在参数上加@Valid后校验正常的原因。

无侵入自动校验实现方案

以下两种方案均基于Spring原生能力实现,无需额外引入第三方依赖,配置完成后所有控制器参数无需逐一枚举@Valid注解即可自动触发校验。

方案1:自定义请求参数解析逻辑(稳定性最高)

直接修改Spring MVC核心请求适配器的参数解析逻辑,给所有复杂类型参数自动追加校验流程,和原生@Valid的校验逻辑完全一致。

首先编写自动配置类,替换默认的参数解析器:

import org.springframework.context.annotation.Configuration
import org.springframework.validation.Validator
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
import javax.annotation.PostConstruct

@Configuration
class AutoValidationConfig(
    private val requestMappingHandlerAdapter: RequestMappingHandlerAdapter,
    private val validator: Validator
) {
    @PostConstruct
    fun init() {
        val argumentResolvers = requestMappingHandlerAdapter.argumentResolvers
        val wrappedResolvers = argumentResolvers.map { resolver ->
            when (resolver) {
                is RequestResponseBodyMethodProcessor -> 
                    AutoValidatingRequestResponseBodyMethodProcessor(resolver, validator)
                is ModelAttributeMethodProcessor -> 
                    AutoValidatingModelAttributeMethodProcessor(resolver, validator)
                else -> resolver
            }
        }
        requestMappingHandlerAdapter.argumentResolvers = wrappedResolvers
    }
}

实现@RequestBody参数的自动校验包装类:

import org.springframework.core.MethodParameter
import org.springframework.http.converter.HttpMessageConverter
import org.springframework.validation.Validator
import org.springframework.web.bind.MethodArgumentNotValidException
import org.springframework.web.bind.WebDataBinder
import org.springframework.web.bind.support.WebDataBinderFactory
import org.springframework.web.context.request.NativeWebRequest
import org.springframework.web.method.support.ModelAndViewContainer
import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor

class AutoValidatingRequestResponseBodyMethodProcessor(
    private val delegate: RequestResponseBodyMethodProcessor,
    private val validator: Validator
) : RequestResponseBodyMethodProcessor(emptyList<HttpMessageConverter<*>>()) {

    override fun supportsParameter(parameter: MethodParameter): Boolean {
        return delegate.supportsParameter(parameter)
    }

    override fun supportsReturnType(returnType: MethodParameter): Boolean {
        return delegate.supportsReturnType(returnType)
    }

    override fun handleReturnValue(
        returnValue: Any?,
        returnType: MethodParameter,
        mavContainer: ModelAndViewContainer,
        webRequest: NativeWebRequest
    ) {
        delegate.handleReturnValue(returnValue, returnType, mavContainer, webRequest)
    }

    override fun resolveArgument(
        parameter: MethodParameter,
        mavContainer: ModelAndViewContainer?,
        webRequest: NativeWebRequest,
        binderFactory: WebDataBinderFactory?
    ): Any? {
        val argument = delegate.resolveArgument(parameter, mavContainer, webRequest, binderFactory)
        argument?.let {
            val binder = binderFactory!!.createBinder(webRequest, it, parameter.parameterName)
            binder.validator = validator
            binder.validate(it)
            if (binder.bindingResult.hasErrors()) {
                throw MethodArgumentNotValidException(parameter, binder.bindingResult)
            }
        }
        return argument
    }
}

实现@ModelAttribute参数的自动校验包装类:

import org.springframework.core.MethodParameter
import org.springframework.validation.BindException
import org.springframework.validation.Validator
import org.springframework.web.bind.WebDataBinder
import org.springframework.web.bind.support.WebDataBinderFactory
import org.springframework.web.context.request.NativeWebRequest
import org.springframework.web.method.support.ModelAndViewContainer
import org.springframework.web.servlet.mvc.method.annotation.ModelAttributeMethodProcessor

class AutoValidatingModelAttributeMethodProcessor(
    private val delegate: ModelAttributeMethodProcessor,
    private val validator: Validator
) : ModelAttributeMethodProcessor(false) {

    override fun supportsParameter(parameter: MethodParameter): Boolean {
        return delegate.supportsParameter(parameter)
    }

    override fun resolveArgument(
        parameter: MethodParameter,
        mavContainer: ModelAndViewContainer?,
        webRequest: NativeWebRequest,
        binderFactory: WebDataBinderFactory?
    ): Any? {
        val argument = delegate.resolveArgument(parameter, mavContainer, webRequest, binderFactory)
        argument?.let {
            val binder = binderFactory!!.createBinder(webRequest, it, parameter.parameterName)
            binder.validator = validator
            binder.validate(it)
            if (binder.bindingResult.hasErrors()) {
                throw BindException(binder.bindingResult)
            }
        }
        return argument
    }
}

方案2:AOP切面拦截校验(实现成本最低)

如果不想修改Spring MVC内置组件逻辑,可引入AOP依赖实现全局拦截校验,仅需一个切面类即可完成配置:

import org.aspectj.lang.JoinPoint
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Before
import org.aspectj.lang.reflect.MethodSignature
import org.springframework.stereotype.Component
import org.springframework.validation.BeanPropertyBindingResult
import org.springframework.validation.Validator
import org.springframework.web.bind.MethodArgumentNotValidException
import java.util.*
import javax.validation.Valid
import javax.validation.Validated

@Aspect
@Component
class AutoValidAspect(
    private val validator: Validator
) {
    // 拦截所有@RestController下的接口方法
    @Before("@within(org.springframework.web.bind.annotation.RestController)")
    fun beforeControllerMethod(joinPoint: JoinPoint) {
        val signature = joinPoint.signature as MethodSignature
        val method = signature.method
        val args = joinPoint.args
        val parameters = method.parameters

        for (i in args.indices) {
            val arg = args[i] ?: continue
            val param = parameters[i]
            // 跳过简单值类型,避免无意义校验
            if (isSimpleValueType(arg.javaClass)) continue
            // 手动加了@Valid/@Validated的参数跳过,避免重复校验
            if (param.isAnnotationPresent(Valid::class.java) || param.isAnnotationPresent(Validated::class.java)) continue

            val bindingResult = BeanPropertyBindingResult(arg, param.name)
            validator.validate(arg, bindingResult)
            if (bindingResult.hasErrors()) {
                throw MethodArgumentNotValidException(MethodParameter(method, i), bindingResult)
            }
        }
    }

    private fun isSimpleValueType(clazz: Class<*>): Boolean {
        return (Void::class.java.isAssignableFrom(clazz)
                || clazz.isPrimitive
                || CharSequence::class.java.isAssignableFrom(clazz)
                || Number::class.java.isAssignableFrom(clazz)
                || Date::class.java.isAssignableFrom(clazz)
                || clazz.isEnum
                || UUID::class.java.isAssignableFrom(clazz)
                || java.time.temporal.Temporal::class.java.isAssignableFrom(clazz))
    }
}
配置注意事项
  • 上述方案生效后,仅需在控制器类上保留@Validated注解,即可同时支持简单参数的直接约束校验、复杂对象的内部字段递归校验,无需在参数上重复添加@Valid。
  • Spring Boot 2.3+版本需要手动引入spring-boot-starter-validation依赖,否则校验模块不会自动装配。
  • 需要配套全局异常处理器,捕获MethodArgumentNotValidException、ConstraintViolationException、BindException三类校验异常,返回统一的业务错误格式。
  • AOP方案中的简单类型判断列表可根据项目实际使用的类型扩展,比如新增项目自定义的通用值类型到跳过列表中。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 23:03:23