Spring Boot中的参数校验

在软件开发中,通常需要对参数进行校验,比如某些参数不能为空,而且不光前端要校验,后端也要校验。

那么后端在哪里校验参数?是在控制层还是service层?参数校验有多种实现方式,采用哪一种校验方式呢? 一般来说,把无业务语义的校验放在action层,用validation做校验,比如数据类型,是否为空,数据长度、格式等,有业务语义的校验放在service层。

当然实际中往往并没有那么清晰,当校验足够简单时,可能只在前端校验就行了,或者简单的在service层校验一下,而当校验较复杂时,则可能需要多层校验。

2024年7月更新

参数校验规范与实现

Java中参数校验规范为Bean Validation,官网地址: https://beanvalidation.org/

Bean Validation先后经历了1.0(JSR 303)、1.1(JSR 349)、2.0(JSR 380)这3个版本,目前使用比较多的是Bean Validation 2.0。当然,现在改名叫Jakarta Bean Validation了。

Maven坐标为:

<dependency>
    <groupId>jakarta.validation</groupId>
    <artifactId>jakarta.validation-api</artifactId>
    <version>2.0.2</version>
</dependency>

Bean Validation的官方参考实现是hibernate Validator(与Hibernate ORM没有关系),maven坐标如下:

<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.2.5.Final</version>
</dependency>

注意,hibernate-validator中已经包含了validation-api,因此项目中如果引入了hibernate-validator,就没必要重复引入validation-api了。

如果是Springboot项目,可以引入spring-boot-starter-validation即可,其中包含了这两个包。

Bean Validation中原生的注解如下:
在这里插入图片描述
具体的每个注解的用法这里就不多说了。

Valid和Validated

@Valid 是Bean Validation中的标准注解,可以进行级联和递归校验。下面是一个示例:


// 要校验的bean
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

public class User {
    
    @NotNull(message = "姓名不能为空")
    @Size(min = 2, max = 30, message = "姓名长度必须在2到30个字符之间")
    private String name;
    
    @NotNull(message = "电子邮件不能为空")
    private String email;
	
	// 递归校验Address对象属性
	@Valid
	private Address address;

    // Getters and Setters
}

// 控制器类
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;

@RestController
@RequestMapping("/users")
public class UserController {

    @PostMapping
    public String createUser(@Valid @RequestBody User user) {
        // 如果验证通过,处理用户数据
        return "用户有效";
    }
}

当调用createUser接口而参数user中属性不符合要求时,系统会抛出异常MethodArgumentNotValidException。一般情况下,我们需要通过全局异常处理器来捕获和处理该异常:

@RestControllerAdvice
public class RestApiValidationExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Map<String, String> toResponse(final HttpServletRequest request, MethodArgumentNotValidException ex) {
        ... 
        Map<String, String> errors = new HashMap<>();
        ex.getBindingResult().getFieldErrors().forEach(error -> 
            errors.put(error.getField(), error.getDefaultMessage()));
        return errors;
    }
}

经过异常处理器处理后,系统将返回一个400错误,并提示错误消息。当然,这里可以根据业务需要来封装返回的数据结构。

这是Bean Validation的最常用的方式。

事实上在项目中,你可能还会经常看到另外一个注解,那就是@Validated。这个Spring的注解,是标准JSR-303的一个补充,主要提供了一个分组功能,可以在入参验证时,根据不同的分组采用不同的验证机制。

// 要校验的bean
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

public class User {
    
    @NotNull(message = "姓名不能为空")
    @Size(min = 2, max = 30, message = "姓名长度必须在2到30个字符之间")
    private String name;
    
    @NotNull(message = "电子邮件不能为空", groups = {AddUser.class})
    private String email;
	
	// 级联校验Address对象属性
	@Valid
	private Address address;

    // Getters and Setters
}

// 控制器类
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;

@RestController
@RequestMapping("/users")
public class UserController {

    @PostMapping
    public String createUser(@Validated(AddUser.class) @RequestBody User user) {
        // 如果验证通过,处理用户数据
        return "用户有效";
    }
    
    @PutMapping
    public String updateUser(@Validated(UpdateUser.class) @RequestBody User user) {
        // 如果验证通过,处理用户数据
        return "更新成功";
    }
}

AddUser和UpdateUser只需要定义成两个接口,标识参数验证时的分组即可。

Valid和Validated的区别

除了Valid不支持分组校验之外,其他的两者功能基本一样。主要的差异点在于:

ValidValidated
规范JSR 303JSR 303 补充
使用范围方法,参数,字段和构造器类,方法和参数
是否支持级联校验支持不能用在字段上,不支持
是否支持分组校验不支持支持

但是这两个注解是可以混合使用的,因此两者结合起来既可以支持级联校验,又可以支持分组校验。

参数校验异常

在上文中提到,当参数校验失败时,会抛出MethodArgumentNotValidException异常。事实上,除此之外,还可能抛出ConstraintViolationException异常。前者是Spring框架中定义的异常,而后者是Bean Validation规范中定义的异常。

通常情况下,当接口的入参为引用类型时,校验不通过会抛出MethodArgumentNotValidException异常。该异常继承于BindException,校验结果保存在bindingResult中;而当接口的入餐类型为基本类型时,校验不通过会抛出ConstraintViolationException异常。这与Spring Web框架的参数解析器有关,表单、普通类型和引用类型参数在解析时会使用不同的参数解析器,当参数解析失败时抛出不同的异常。

另外,直接使用Validator校验bean时也会抛出ConstraintViolationException异常。

一个正常的业务系统中这两种异常应该都会存在,在全局异常处理时捕获并处理即可。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值