@Valid与@Validated区别

1.@Valid与@Validated作用

@Valid与@Validated都是用来校验接收参数的。
 
@Valid是使用Hibernate validation的时候使用
 
@Validated是只用Spring Validator校验机制使用
 
说明:java的JSR303声明了@Valid这类接口,而Hibernate-validator对其进行了实现。
 
 
 
@Validated与@Valid区别:
 
@Validated:可以用在类型、方法和方法参数上。但是不能用在成员属性(字段)上,不支持嵌套检测
@Valid:可以用在方法、构造函数、方法参数和成员属性(字段)上,支持嵌套检测
 
 
 
注意:SpringBoot使用@Valid注解需要引入如下POM
 
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

2.为什么要用@Valid或@Validated

@Valid与@Validated都是用来校验接收参数的,如果不使用注解校验参数,那么就需要在业务代码中校验,这
样会增加很多的工作量并且代码不优美。
@RestController
@RequestMapping("/valid")
public class ValidControllerTest {
 
    public static final Logger logger = LoggerFactory.getLogger(ValidControllerTest.class.getName());
 
    @PostMapping("/login")
    public String doLogin(@RequestBody LoginUser loginUser){
        if(StringUtils.isEmpty(loginUser.getPhone())){
          //抛出异常
          logger.error("伪代码抛出异常---可以用全局异常捕获");
        }
        if(StringUtils.isEmpty(loginUser.getPassWord())){
          //抛出异常
         logger.error("伪代码抛出异常---可以用全局异常捕获");
        }
        return "数据校验通过";
    }
}
 
如果需要判断多个参数,那就需要写多个if判断,这样会增加编码工作量并且这样写的不优美。

3.使用@Valid或@Validated来做一个简单的案例

如果@Valid|Validated校验不通过,那么错误信息就会封装到BindingResult对象了,可以通过
BindingResult的相关方法获取详细的错误信息并返回给用户。 代码如下:
 
 
public class LoginUser {
 
    @NotBlank(message = "手机号码不能为空")
    private String phone;
 
    @NotBlank(message = "密码不能为空")
    private String passWord;
}
 
 
@RestController
@RequestMapping("/valid")
public class ValidControllerTest {
 
    public static final Logger logger = LoggerFactory.getLogger(ValidControllerTest.class.getName());
 
    @PostMapping("/login")
    public String doLogin(@RequestBody @Validated LoginUser loginUser, BindingResult bindingResult){
        if(bindingResult.hasErrors()){
            List<ObjectError> allErrors = bindingResult.getAllErrors();
            allErrors.forEach( v ->{
                logger.error(v.getObjectName()+"======"+v.getField()+"======"+v.getDefaultMessage());
            });
        }
        return "数据校验通过";
    }
}
 
 
loginUser======phone======密码不能为空
loginUser======passWord======手机号码不能为空

4.使用@Valid实现嵌套检测

什么是嵌套检测?
 
嵌套检测就是在一个beanA中,存在另外一个beanB属性。嵌套检测beanA同时也检测beanB。
 
 
public class AgeBean {
 
    @NotNull(message = "年龄不能为空")
    @Max(value = 120,message = "年龄不能超过120岁")
    @Min(value = 0,message = "年龄不能为负数")
    private int age;
}
 
 
public class LoginUser {
 
    @NotBlank(message = "手机号码不能为空")
    private String phone;
    @NotBlank(message = "密码不能为空")
    private String passWord;
    @NotNull(message = "ageBean不能为null")
    private AgeBean ageBean;
}
 
注意:LoginUser中ageBean属性没有添加@Valid注解,所以只能检测ageBean属性不为空,但是不能
检测AgeBean对象中age属性定义的最大值和最小值。(@Validated不能添加属性上面,不支持嵌套检测所以
只有使用@Valid)
3. 嵌套验证

@Validated:用在方法入参上无法单独提供嵌套验证功能。不能用在成员属性(字段)上,也无法提示框架进行嵌套验证。能配合嵌套验证注解@Valid进行嵌套验证。

@Valid:用在方法入参上无法单独提供嵌套验证功能。能够用在成员属性(字段)上,提示验证框架进行嵌套验证。能配合嵌套验证注解@Valid进行嵌套验证。

也就是说单独一个@Validated或者@Valid都无法提供嵌套验证功能,都需要再结合一个@Valid注解进行嵌套验证
 
 
@RequestMapping("/valid")
public class ValidControllerTest {
 
    public static final Logger logger = LoggerFactory.getLogger(ValidControllerTest.class.getName());
 
    @PostMapping("/login")
    public String doLogin(@RequestBody @Validated LoginUser loginUser, BindingResult bindingResult){
        if(bindingResult.hasErrors()){
            List<ObjectError> allErrors = bindingResult.getAllErrors();
            allErrors.forEach( v ->{
                logger.error(v.getObjectName()+"======"+v.getField()+"======"+v.getDefaultMessage());
            });
        }
        return "数据校验通过";
    }
}
 
 
模拟入参:
 
{
    "phone":"111",
    "passWord":"",
    "ageBean":{
        "age":-1
    }
}
 
只抛出密码不为空,没有嵌套检测ageBean属性中的age属性最大值最小值。
loginUser======passWord======密码不能为空
 
 
 
添加上@Valid注解,重新测试
public class LoginUser {
 
    @NotBlank(message = "手机号码不能为空")
    private String phone;
    @NotBlank(message = "密码不能为空")
    private String passWord;
    
    @Valid
    @NotNull(message = "ageBean不能为null")
    private AgeBean ageBean;
}
 
模拟参数:
 
{
    "phone":"111",
    "passWord":"",
    "ageBean":{
        "age":-1
    }
}
 
添加@Valid注解便可以嵌套检测ageBean属性中的age属性的最大值|最小值
 
loginUser======年龄不能为负数
loginUser======密码不能为空

 5.常用注解说明

@AssertFalse:
 
所注解的元素必须是Boolean类型,并且值为false
 
@AssertTrue:
 
所注解的元素必须是Boolean类型,并且值为true
 
@DecimalMax:
 
所注解的元素必须是数字,并且值要小于或等于给定的BigDecimalString值
 
@DecimalMin:
 
所注解的元素必须是数字,并且值要大于或等于给定的BigDecimalString值
 
@Digits:
 
所注解的元素必须是数字,并且它的值必须有指定的位数
 
@Email:
 
所注解的元素要匹配指定的正则表达式
 
@Max:
 
所注解的元素必须是数字,并且值要小于或等于给定的值。注意如果@Max所注解的元素是null,则@Max注解
会返回true,所以应该把@Max注解和@NotNull注解结合使用
 
@Min:
 
所注解的元素必须是数字,并且值要大于或等于给定的值。注意如果@Min所注解的元素是null,则@Min注解
会返回true,即也会通过校验,所以应该把@Min注解和@NotNull注解结合使用。
 
@NotBlank:
 
所注解的元素不能为null且不能为空白,并且必须至少包含一个非空白字符,用于校验CharSequence(含String、StringBuilder和StringBuffer)。只支持字符类型。
 
@NotEmpty:
 
所注解的元素不能为null且长度大于0,可以是空白,用于校验CharSequence、数组、Collection和Map
 
@NotNull:
 
所注解的元素不能为null,接受任何类型
 
@Null:
 
所注解的元素必须为null,接受任何类型
 
@Pattern:
 
所注解的元素必须匹配指定的正则表达式。注意如果@Pattern所注解的元素是null,则@Pattern注解会返回
true,即也会通过校验,所以应该把@Pattern注解和@NotNull注解结合使用
 
@Size:
 
所注解的元素必须符合指定的大小,该注解可用于数组,CharSequence(含String、StringBuilder和
StringBuffer),Collection和Map。注意如果@Size所注解的元素是null,则@Size注解会返回true,即也
会通过校验,所以应该把@Size注解和@NotNull注解结合使用

Validated、Valid、Validator,他们的区别你知道几个

1. 结论先出

Valid VS Validated 相同点

  • 都可以对方法和参数进行校验
  • @Valid@Validated

    两种注释都会导致应用标准Bean验证。

    如果验证不通过会抛出BindException异常,并变成400(BAD_REQUEST)响应;或者可以通过ErrorsBindingResult参数在控制器内本地处理验证错误。另外,如果参数前有@RequestBody注解,验证错误会抛出MethodArgumentNotValidException异常。

JSR 380

JSR 380 是用于 bean 验证的 Java API 规范,是 Jakarta EE 和 JavaSE 的一部分。这确保 bean 的属性满足特定条件,使用诸如@NotNull@Min@Max 之类的注释。

此版本需要 Java 8 或更高版本,并利用 Java 8 中添加的新功能,例如类型注释和对OptionalLocalDate等新类型的支持。

有关规范的完整信息,请继续阅读JSR 380。

Valid VS Validated 不同点?

  • javax.validation.Valid
    • 是JSR-303规范标准注解支持,是一个标记注解。
    • 注解支持ElementType#METHOD,ElementType#FIELDElementType#CONSTRUCTOR,
    • ElementType#PARAMETERElementType#TYPE_USE
  • org.springframework.validation.annotation.Validated
    • 是Spring 做得一个自定义注解,增强了分组功能。
    • 注解支持 ElementType#TYPE,ElementType#METHOD,ElementType#PARAMETER

@Valid@Validated区别

Validator

Bean Validation 2.0(JSR 380)定义了用于实体和方法验证的元数据模型和API,Hibernate Validator是目前最好的实现

Validator接口有三个方法,可用于验证整个实体仅验证实体的单个属性

  • Validator#validate() 验证所有bean的所有约束
  • Validator#validateProperty() 验证单个属性
  • Validator#validateValue() 检查给定类的单个属性是否可以成功验证

不管是requestBody参数校验还是方法级别的校验,最终都是调用Hibernate Validator执行校验,Spring Validation只是做了一层封装

验证用户的输入是我们大多数应用程序中的常见功能。在 Java 生态系统中,我们专门使用Java Standard Bean Validation API来支持这一点。此外,从 4.0 版本开始,这也与 Spring 很好地集成在一起.

在接下来的部分中,让我们详细了解它们。

2. @Valid@Validated 注解

在 Spring 中,我们使用 JSR-303 的@Valid注释进行方法级别验证此外,我们还使用它来标记成员属性以进行验证。但是,此注释不支持组验证。

组有助于限制验证期间应用的约束。一个特殊的用例是 UI 界面(UI wizards)。在这里,在第一步中,我们可能有某个字段子组。在后续步骤中,可能有另一个组属于同一个 bean。因此我们需要在每一步中对这些有限的字段应用约束,但@Valid不支持这一点。

在这种情况下,对于组级别,我们必须使用 Spring 的@Validated,它是 JSR-303 的@Valid的变体。这是在方法级别使用的。对于标记成员属性,我们继续使用@Valid注释。

现在,让我们直接进入并通过一个例子来看看这些注解的用法。

3. 例子

让我们考虑一个使用 Spring Boot 开发的简单用户注册。首先,我们将只有名称密码属性

public class UserAccount {
    @NotNull
    @Size(min = 4, max = 15)
    private String password;
    @NotBlank
    private String name;
// standard constructors / setters / getters / toString 
} 
接下来,让我们看看控制器。在这里,我们将使用带有@Valid注释的saveBasicInfo方法来验证用户输入:
@RequestMapping(value = "/saveBasicInfo", method = RequestMethod.POST)
public String saveBasicInfo(
  @Valid @ModelAttribute("useraccount") UserAccount useraccount, 
  BindingResult result, 
  ModelMap model) {
    if (result.hasErrors()) {
        return "error";
    }
    return "success";
}
现在让我们测试这个方法:
@Test
public void givenSaveBasicInfo_whenCorrectInput_thenSuccess() throws Exception {
    this.mockMvc.perform(MockMvcRequestBuilders.post("/saveBasicInfo")
      .accept(MediaType.TEXT_HTML)
      .param("name", "test123")
      .param("password", "pass"))
      .andExpect(view().name("success"))
      .andExpect(status().isOk())
      .andDo(print());
}
确认测试运行成功后,我们现在扩展功能。下一个合乎逻辑的步骤是将其转换为复杂用户注册。第一步,名称密码保持不变。在第二步中,我们将获取诸如年龄 和 电话之类的附加信息。因此,我们将使用这些附加字段更新我们的域对象
public class UserAccount {
    @NotNull
    @Size(min = 4, max = 15)
    private String password;
    @NotBlank
    private String name;
    @Min(value = 18, message = "Age should not be less than 18")
    private int age;
    @NotBlank
    private String phone; 
// standard constructors / setters / getters / toString  
}

但是,这一次我们会注意到之前的测试失败了。这是因为我们没有传入agephone字段为了支持这种行为,我们需要组验证和@Validated注释。

为此,我们需要对字段进行分组,创建两个不同的组。首先,我们需要创建两个标记接口。每个组或每个步骤单独一个。我们可以参考我们关于组验证的文章以了解具体的实现方式。在这里,让我们关注注释的差异。

我们将有第一步的BasicInfo接口和第二步的 AdvanceInfo 。此外,我们将更新UserAccount类以使用这些标记接口,如下所示:

public class UserAccount {
    @NotNull(groups = BasicInfo.class)
    @Size(min = 4, max = 15, groups = BasicInfo.class)
    private String password;
    @NotBlank(groups = BasicInfo.class)
    private String name;
    @Min(value = 18, message = "Age should not be less than 18", groups = AdvanceInfo.class)
    private int age;
    @NotBlank(groups = AdvanceInfo.class)
    private String phone; 
// standard constructors / setters / getters / toString  
} 


此外,我们现在将更新我们的控制器以使用@Validated批注而不是@Valid

@RequestMapping(value = "/saveBasicInfoStep1", method = RequestMethod.POST)
public String saveBasicInfoStep1(
  @Validated(BasicInfo.class) 
  @ModelAttribute("useraccount") UserAccount useraccount, 
  BindingResult result, ModelMap model) {
    if (result.hasErrors()) {
        return "error";
    }
    return "success";
}

由于此更新,我们的测试现在成功运行。现在让我们也测试一下这个新方法:

@Test
public void givenSaveBasicInfoStep1_whenCorrectInput_thenSuccess() throws Exception {
    this.mockMvc.perform(MockMvcRequestBuilders.post("/saveBasicInfoStep1")
            .accept(MediaType.TEXT_HTML).param("name", "test123")
            .param("password", "pass")).andExpect(view().name("success"))
            .andExpect(status().isOk()).andDo(print());
}

这也运行成功。因此,我们可以看到@Validated的使用 对于组验证至关重要。

接下来,让我们看看@Valid如何触发嵌套属性的验证。

4.使用@Valid嵌套校验

@Valid注释用于校验嵌套属性。这会触发嵌套对象的验证。例如,在我们当前的场景中,让我们创建一个 UserAddress 对象:

public class UserAddress {
    @NotBlank
    private String countryCode; 
// standard constructors / setters / getters / toString 
}

为了确保此嵌套对象的验证,我们将使用@Valid注释来装饰该属性: 

public class UserAccount { //... 
    @Valid 
    @NotNull(groups = AdvanceInfo.class) 
    private UserAddress useraddress; 
// standard constructors / setters / getters / toString  
}

5. 组合使用@Valid@Validated 进行集合校验

如果请求体直接传递了json数组给后台,并希望对数组中的每一项都进行参数校验。此时,如果我们直接使用java.util.Collection下的list或者set来接收数据,参数校验并不会生效!我们可以使用自定义list集合来接收参数:

  • 包装List类型,并声明@Valid注解
import javax.validation.Valid;
import java.util.*;

/**
 * @Title: 参数校验工具类, 用于校验List<E> 类型的请求参数 * 
 * @ClassName: com.devicemag.core.BO.ValidList.java * 
 * @Description: * * 
 * @Copyright 2020-2021 - Powered By 研发中心 * 
 * @author: 王延飞 * 
 * @date: 2020/12/25 20:23 * 
 * @version V1.0
 */
public class ValidList<E> implements List<E> {
    @Valid
    private List<E> list = new ArrayList<>();

    @Override
    public int size() {
        return list.size();
    }

    @Override
    public boolean isEmpty() {
        return list.isEmpty();
    }

    @Override
    public boolean contains(Object o) {
        return list.contains(o);
    }

    @Override
    public Iterator<E> iterator() {
        return list.iterator();
    }

    @Override
    public Object[] toArray() {
        return list.toArray();
    }

    @Override
    public <T> T[] toArray(T[] a) {
        return list.toArray(a);
    }

    @Override
    public boolean add(E e) {
        return list.add(e);
    }

    @Override
    public boolean remove(Object o) {
        return list.remove(o);
    }

    @Override
    public boolean containsAll(Collection<?> c) {
        return list.containsAll(c);
    }

    @Override
    public boolean addAll(Collection<? extends E> c) {
        return list.addAll(c);
    }

    @Override
    public boolean addAll(int index, Collection<? extends E> c) {
        return list.addAll(index, c);
    }

    @Override
    public boolean removeAll(Collection<?> c) {
        return list.removeAll(c);
    }

    @Override
    public boolean retainAll(Collection<?> c) {
        return list.retainAll(c);
    }

    @Override
    public void clear() {
        list.clear();
    }

    @Override
    public E get(int index) {
        return list.get(index);
    }

    @Override
    public E set(int index, E element) {
        return list.set(index, element);
    }

    @Override
    public void add(int index, E element) {
        list.add(index, element);
    }

    @Override
    public E remove(int index) {
        return list.remove(index);
    }

    @Override
    public int indexOf(Object o) {
        return list.indexOf(o);
    }

    @Override
    public int lastIndexOf(Object o) {
        return list.lastIndexOf(o);
    }

    @Override
    public ListIterator<E> listIterator() {
        return list.listIterator();
    }

    @Override
    public ListIterator<E> listIterator(int index) {
        return list.listIterator(index);
    }

    @Override
    public List<E> subList(int fromIndex, int toIndex) {
        return list.subList(fromIndex, toIndex);
    }

    public List<E> getList() {
        return list;
    }

    public void setList(List<E> list) {
        this.list = list;
    }     
    // 一定要记得重写toString方法     
    @Override
    public String toString() {
        return "ValidList{" + "list=" + list + '}';
    }
}

比如,我们需要一次性保存多个UserAccount 对象,Controller层的方法可以这么写:

@PostMapping("/saveList")
public Result saveList(@RequestBody @Validated(UserAccount.class) ValidationList<UserAccount> userList) {
    // 校验通过,才会执行业务逻辑处理
    return Result.ok();
}

6. 自定义校验

validator-api-2.0的约束注解有22个,具体我们看下面表格

空与非空检查

注解支持Java类型说明
@NullObject为null
@NotNullObject不为null
@NotBlankCharSequence不为null,且必须有一个非空格字符
@NotEmptyCharSequence、Collection、Map、Array不为null,且不为空(length/size>0)

Boolean值检查

注解支持Java类型说明备注
@AssertTrueboolean、Boolean为true为null有效
@AssertFalseboolean、Boolean为false为null有效

日期检查

注解支持Java类型说明备注
@FutureDate、Calendar、Instant、LocalDate、LocalDateTime、LocalTime、MonthDay、OffsetDateTime、OffsetTime、Year、YearMonth、ZonedDateTime、HijrahDate、JapaneseDate、MinguoDate、ThaiBuddhistDate验证日期为当前时间之后为null有效
@FutureOrPresentDate、Calendar、Instant、LocalDate、LocalDateTime、LocalTime、MonthDay、OffsetDateTime、OffsetTime、Year、YearMonth、ZonedDateTime、HijrahDate、JapaneseDate、MinguoDate、ThaiBuddhistDate验证日期为当前时间或之后为null有效
@PastDate、Calendar、Instant、LocalDate、LocalDateTime、LocalTime、MonthDay、OffsetDateTime、OffsetTime、Year、YearMonth、ZonedDateTime、HijrahDate、JapaneseDate、MinguoDate、ThaiBuddhistDate验证日期为当前时间之前为null有效
@PastOrPresentDate、Calendar、Instant、LocalDate、LocalDateTime、LocalTime、MonthDay、OffsetDateTime、OffsetTime、Year、YearMonth、ZonedDateTime、HijrahDate、JapaneseDate、MinguoDate、ThaiBuddhistDate验证日期为当前时间或之前为null有效

数值检查

注解支持Java类型说明备注
@MaxBigDecimal、BigInteger,byte、short、int、long以及包装类小于或等于为null有效
@MinBigDecimal、BigInteger,byte、short、int、long以及包装类大于或等于为null有效
@DecimalMaxBigDecimal、BigInteger、CharSequence,byte、short、int、long以及包装类小于或等于为null有效
@DecimalMinBigDecimal、BigInteger、CharSequence,byte、short、int、long以及包装类大于或等于为null有效
@NegativeBigDecimal、BigInteger,byte、short、int、long、float、double以及包装类负数为null有效,0无效
@NegativeOrZeroBigDecimal、BigInteger,byte、short、int、long、float、double以及包装类负数或零为null有效
@PositiveBigDecimal、BigInteger,byte、short、int、long、float、double以及包装类正数为null有效,0无效
@PositiveOrZeroBigDecimal、BigInteger,byte、short、int、long、float、double以及包装类正数或零为null有效
@Digits(integer = 3, fraction = 2)BigDecimal、BigInteger、CharSequence,byte、short、int、long以及包装类整数位数和小数位数上限为null有效

其他

注解支持Java类型说明备注
@PatternCharSequence匹配指定的正则表达式为null有效
@EmailCharSequence邮箱地址为null有效,默认正则 '.*'
@SizeCharSequence、Collection、Map、Array大小范围(length/size>0)为null有效

hibernate-validator扩展约束(部分)

注解支持Java类型说明
@LengthString字符串长度范围
@Range数值类型和String指定范围
@URLURL地址验证

自定义约束注解

除了以上提供的约束注解(大部分情况都是能够满足的),我们还可以根据自己的需求自定义自己的约束注解

定义自定义约束,有三个步骤

  • 创建约束注解
  • 实现一个验证器
  • 定义默认的错误信息

那么下面就直接来定义一个简单的验证手机号码的注解

@Documented
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Constraint(validatedBy = {MobileValidator.class})
@Retention(RUNTIME)
@Repeatable(Mobile.List.class)
public @interface Mobile {
    /**
     * 错误提示信息,可以写死,也可以填写国际化的key
     */
    String message() default "手机号码不正确";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    String regexp() default "^1([38][0-9]|4[579]|5[0-3,5-9]|6[6]|7[0135678]|9[89])\\d{8}$";

    @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
    @Retention(RUNTIME)
    @Documented
    @interface List {
        Mobile[] value();
    }
}

关于注解的配置这里不说了,自定义约束需要下面3个属性

  • message 错误提示信息,可以写死,也可以填写国际化的key
  • groups 分组信息,允许指定此约束所属的验证组(下面会说到分组约束)
  • payload 有效负载,可以通过payload来标记一些需要特殊处理的操作
  • @Repeatable注解List定义可以让该注解在同一个位置重复多次,通常是不同的配置(比如不同的分组和消息)
  • @Constraint(validatedBy = {MobileValidator.class})该注解是指明我们的自定义约束的验证器,那下面就看一下验证器的写法,需要实现javax.validation.ConstraintValidator接口
public class MobileValidator implements ConstraintValidator<Mobile, String> {
    /**
     * 手机验证规则
     */
    private Pattern pattern;

    @Override
    public void initialize(Mobile mobile) {
        pattern = Pattern.compile(mobile.regexp());
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (value == null) {
            return true;
        }
        return pattern.matcher(value).matches();
    }
}

ConstraintValidator接口定义了在实现中设置的两个类型参数。

  1. 第一个指定要验证的注解类(如Mobile),
  2. 第二个指定验证器可以处理的元素类型(如String);initialize()方法可以访问约束注解的属性值;isValid()方法用于验证,返回true表示验证通过

Bean验证规范建议将空值视为有效。如果null不是元素的有效值,则应使用@NotNull 显式注释

到这里我们自定义的约束就写好了,可以用个例子来测试一下

public class MobileTest {
    public void setMobile(@Mobile String mobile) {
        // to do    
    }

    private static ExecutableValidator executableValidator;

    @BeforeAll
    public static void setUpValidator() {
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        executableValidator = factory.getValidator().forExecutables();
    }

    @Test
    public void manufacturerIsNull() throws NoSuchMethodException {
        MobileTest mobileTest = new MobileTest();
        Method method = MobileTest.class.getMethod("setMobile", String.class);
        Object[] parameterValues = {"1111111"};
        Set<ConstraintViolation<MobileTest>> violations = executableValidator.validateParameters(mobileTest, method, parameterValues);
        violations.forEach(violation -> System.out.println(violation.getMessage()));
    }
}
手机号码不正确

工作原理

@Validated的工作原理

方法级别参数校验

在每个参数前面声明约束注解,然后通过解析参数注解完成校验,这就是方法级别的参数校验。 这种方式可以用于任何的Spring Bean的方法上,一般来说,这种方式一般会采用AOPAround增强完成 在Spring中,是通过以下步骤完成

  1. MethodValidationPostProcessor在Bean的初始化完成之后,判断是否要进行AOP代理(类是否被@Validated标记)
  2. MethodValidationInterceptor拦截所有方法,执行校验逻辑
  3. 委派Validator执行参数校验和返回值校验,得到ConstraintViolation
  4. 处理ConstraintViolation

结论

总之,对于任何基本验证,我们将在方法调用中使用 JSR @Valid注释。另一方面,对于任何组验证,包括组序列,我们需要 在我们的方法调用中使用 Spring 的@Validated注释。所述@Valid 还需要注释来触发嵌套属性的验证。

  • @Validated的原理本质还是AOP。在方法校验上,利用AOP动态拦截方法,利用JSR303 Validator实现完成校验。在Bean的属性校验上,则是基于Bean的生命周期,在其初始化前后完成校验
  • Spring Validator本质实现还是JSR303 Validaotr,只是能让其更好的适配Spring Context
  • @javax.validation.ValidJSR303的核心标记注解,但是在Spring Framework中被@Validated取代,但是Spring Validator的实现可以支持兼容@javax.validation.Valid

例如,在MethodValidationPostProcessor提供了setValidatedAnnotationType方法,替换默认的@Validated

Spring MVC中,RequestResponseBodyMethodProcessor@RequestBody@ResponseBody的校验处理,就兼容了@javax.validation.Valid@Validated

public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {
    @Override
    protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {
        Annotation[] annotations = parameter.getParameterAnnotations();
        for (Annotation ann : annotations) {
            Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
            if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {
                Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));
                Object[] validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[]{hints});
                binder.validate(validationHints);
                break;
            }
        }
    }
}

参考链接:

https://blog.csdn.net/fly910905/article/details/119850168

  • 20
    点赞
  • 109
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: @valid和@validated区别在于: @valid是一个注解,用于标记一个方法或参数是否有效。它通常用于验证输入参数的有效性,例如检查参数是否为空或是否符合特定的格式要求。 @validated是一个Spring框架中的注解,用于标记一个方法或参数是否已经通过验证。它通常用于在控制器层面验证输入参数的有效性,例如检查参数是否为空或是否符合特定的格式要求,并在验证失败时返回错误信息。 ### 回答2: @valid和@validated都是Spring框架中用于校验数据的注解。它们的主要区别如下: 1. 作用范围不同:@valid注解通常用于校验方法参数,可以标记在方法的参数或参数类型上,用于校验请求参数的有效性。而@validated注解一般用于校验方法、类或接口上,用于校验方法的返回值或类的字段的有效性。 2. 使用校验器不同:@valid注解使用Java的Bean Validation 标准的校验器(如Hibernate Validator)进行数据校验,用于校验数据对象的属性是否合法。而@validated注解使用Spring框架内置的校验器(如SpringValidator)进行数据校验,用于校验Spring Bean对象。 3. 错误处理方式不同:当校验失败时,@valid注解会抛出MethodArgumentNotValidException或ConstraintViolationException异常,你需要自己处理这些异常并返回错误信息给用户。而@validated注解会自动将校验失败的信息封装到BindingResult对象中,你可以通过该对象获取校验失败的详细信息。 4. 异常转换不同:Spring框架对于@valid注解处理的异常会自动转换为HTTP状态码为400的错误响应,适用于RESTful风格的接口。而对于@validated注解,Spring框架不会进行自动的异常转换,你需要在代码中手动处理异常并返回错误信息。 总的来说,@valid注解主要用于方法参数的校验,并使用Java标准的校验器,适用于校验请求参数。而@validated注解主要用于方法、类或接口的校验,并使用Spring框架内置的校验器,适用于校验Spring Bean对象。 ### 回答3: @valid和@validated都是Java Spring框架中的注解,用于验证方法参数的合法性。它们的区别主要在于适用的场景和验证方式。 @valid注解通常用于基于Java Bean Validation规范的验证。这意味着当添加了@valid注解的方法参数时,会自动触发Java Bean Validation规范中定义的验证规则。例如,可以对参数应用注解如@NotBlank、@NotNull、@Min等,以验证参数的非空、最小值等条件。使用@valid注解能够方便地进行基于注解的验证。 @validated注解则是Spring框架提供的注解,主要用于验证自定义的验证规则。相比于@valid注解,@validated注解更加灵活,可用于各种自定义验证方法。@validated注解可以与其他验证注解一起使用,例如@NotNull、@Min、@Size等,来创建自定义的参数验证规则。通过使用@validated注解,可以根据自己的业务需求来编写验证逻辑。 总结来说,@valid注解适用于基于Java Bean Validation规范的验证,而@validated注解则适用于基于Spring框架的自定义验证规则。根据需求和情况选择合适的注解,能够提高代码的可读性和可维护性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值