我们在平时的学习与工作中,都需要对参数进行校验,比如在注册时,用户名密码不能为空,用户名长度必须小于10等等。虽然有些校验在前端页面会进行验证,但是后端为了增加健壮性也需要对这些参数进行判断(比如绕过前端页面而直接调用了接口,参数的合法性未知),可能就会在controller或者service中就会有如下代码的出现

package com.beemo.validation.controller;
import com.beemo.validation.demo1.entity.Student;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestBody;
import java.util.Objects;
@RestController
public class DemoController {
    @RequestMapping("/demo")
    public String saveDemo(@RequestBody Student student) {
        if (StringUtils.isEmpty(student.getName())) {
            return "学生名称不能为空";
        if (student.getName().length() > 10) {
            return "学生名称长度不能超过10位";
        if (Objects.isNull(student.getAge())) {
            return "学生年龄不能为空";
        if (student.getAge() <= 0) {
            return "学生年龄不能为负数";
        if (Objects.isNull(student.getNumber())) {
            return "学号不能为空";
        if (student.getNumber().length() != 10) {
            return "学号长度必须为10";
        // 其他判断
        // 调用service的方法等
        return "ok";
    @Data
    class Student {
        private String name;
        private Integer age;
        private String number;

从例子中可以看到,这仅仅是一个实体类3个字段的简单验证,就已经占据了很多的篇幅,也需要我们进行手动编写这种判断代码,比较费时,代码读起来也没什么营养,大部分都是在判断合法性,等我们真正读到想要的业务逻辑代码可能需要往下翻好久,那么有没有办法能够让我们更简洁更优雅的去验证这些参数呢

2. Jakarta Bean Validation

2.1 Jakarta Bean Validation简介

首先要知道Jakarta就是Java更名之后的名称,Jakarta Bean Validation也就是Java Bean Validation,是一套Java的规范,它可以

通过使用注解的方式在对象模型上表达约束
以扩展的方式编写自定义约束
提供了用于验证对象和对象图的API
提供了用于验证方法和构造方法的参数和返回值的API
报告违反约定的集合
运行在Java SE,并且集成在Jakarta EE8中
例如:

 public class User {
    private String email;
    @NotNull @Email
    public String getEmail() {
      return email;
    public void setEmail(String email) {
      this.email = email;
public class UserService {
    public void createUser(@Email String email, @NotNull String name) {

虽然可以手动运行校验,但更加自然的做法是让其他规则和框架在适时对数据进行校验(用户在表示框架中进行输入,业务服务通过CDI执行,实体通过JPA插入或者更新)
换句话说,即运行一次,到处约束

2.2 相关网址

  • 2.0首页
  • 2.0官方规范学习文档
  • 在2020年2月份已经发布了3.0.0-M1版本
    其中Jakarta Bean Validation只是一套标准,我们需要使用其他组织机构提供的实现来进行验证,官方支持的为Hibernate Validator

    3. 动手实践

    3.1 所需环境

    这里JDK使用了JDK1.8,使用maven进行所需jar文件依赖,使用springboot搭建框架脚手架,使用lombok简化代码
    如果用的不是这几个可以适当修改,大同小异,而且springboot以及或其他依赖的版本每天都在变化,各个版本之间难免有或多或少的差别,可能细节处与本文章有所不同,需要大家知晓,并且根据自己的版本进行调整(比如spring-boot-starter-parent版本2.2.7与2.3.0在验证异常时返回json格式与内容就有很大不同)

    3.2 搭建空框架

  • 使用spring initializr创建springboot项目,依次选择添加webvalidation以及lombok模块,生成的pom.xml依赖如下。我这里spring-boot-starter-parent的版本为2.3.0,再添加其他所需的pom依赖
  • <!-- spring-boot版本 --> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.0.RELEASE</version> <!-- web模块 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 验证模块,hibernate-validation --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> <!- lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.12</version> </dependency> <!-- guava --> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>29.0-jre</version> </dependency>

    3.3 编写代码

    编写背景:模拟英雄联盟游戏的技能与英雄的保存

    这里的命名遵循外服名称而不是国服直译,例如英雄为champion而不是hero,技能为ability而不是skill

    3.3.1 实体类

    package com.beemo.validation.demo2.entity;
    import lombok.Data;
    import javax.validation.constraints.NotBlank;
    import javax.validation.constraints.NotNull;
     * 英雄entity
    @Data
    public class Champion {
         * 英雄名称
        @NotBlank(message = "英雄名称不能为空")
        private String name;
         * 英雄头衔
        @NotBlank(message = "英雄头衔不能为空")
        private String title;
         * 英雄描述
        @NotBlank(message = "英雄描述不能为空")
        private String description;
         * 英雄类型
         * 坦克、刺客、射手、法师、辅助以及战士
        @NotNull(message = "英雄类型不能为空")
        private Byte type;
    
  • 技能entity
  • package com.beemo.validation.demo2.entity;
    import lombok.Data;
    import javax.validation.constraints.NotBlank;
    import javax.validation.constraints.NotNull;
    @Data
    public class Ability {
         * 技能名称
        @NotBlank(message = "技能名称不能为空")
        private String name;
         * 技能描述
        @NotBlank(message = "技能描述不能为空")
        private String description;
         * 技能类型
         * 例如魔法值、怒气、能量等
        @NotNull(message = "技能类型不能为空")
        private Byte type;
    

    3.3.2 控制层

  • 英雄controller
  • package com.beemo.validation.demo2.controller;
    import com.beemo.validation.demo2.entity.Champion;
    import org.springframework.validation.annotation.Validated;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    import javax.validation.Valid;
    @RestController
    @RequestMapping("/demo2/champion")
    @Validated
    public class ChampionController {
         * @param entity 要保存的英雄实体
         * @return 保存结果
        @PostMapping("save")
        public String save(@Valid @RequestBody Champion entity) {
            // 调用service等
            return "ok";
    
  • 技能controller
  • package com.beemo.validation.demo2.controller;
    import com.beemo.validation.demo2.entity.Ability;
    import org.springframework.validation.annotation.Validated;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    import javax.validation.Valid;
    @RestController
    @RequestMapping("/demo2/ability")
    @Validated
    public class AbilityController {
         * @param entity 要保存的技能实体
         * @return 保存结果
        @PostMapping("save")
        public String save(@Valid @RequestBody Ability entity) {
            // 调用service等
            return "ok";
    

    3.3.3 测试

    使用postman或其他工具发送POST请求,进行验证,我们直接输入我们参数直接传一个内容为空的json,查看结果

     可以看到,这里返回了400异常,意为参数错误
    我们再把所有参数补全,再试一下

    可以看到,如果我们把参数补全之后,返回的是“ok”,即进入controller执行该方法。
    那么,例子中添加的几个注解都是什么意思,有什么作用,而且注解中写的message信息在验证后并没有输出,那么我们怎么样输出这些message呢

    4. 注解含义

    4.1 开启验证

    首先我们看controller类最上方,我们标注了@Validataed(这里可以去掉,不用在控制类上添加该注解),该注解的含义是:这个类要启用参数校验。在save方法的参数中标注了@Valid,含义为我们要对紧跟的实体进行校验,而具体校验的内容,为实体类中的我们的定义的约束
    以Ability类举例,在name字段上方标记了@NotBlank,意为定义了该字段不允许为空的约束,如果name为空,校验就不通过,就会返回我们之前碰到的400异常。而type字段也标注了@NotNull,也定义了该字段不允许为空的约束,具体的区别以及其他内置的约束如3.5所示

    4.2 内置约束

    内置约束位于javax.validation.constraints包内,列表如下

    4.2.1 @Null

  • 被标注元素必须为null
  • 接收任意类型
  • 被标注元素必须为是一个数字,其值必须大于等于指定的最小值
  • 支持的类型为BigDecimal 、BigInteger 、byte 、short 、int 、long 以及各自的包装类
  • 注意double以及float由于舍入错误而不被支持
  • null被认为是有效的
  • 被标注元素必须为是一个数字,其值必须小于等于指定的最大值
  • 支持的类型为BigDecimal 、BigInteger 、byte 、short 、int 、long 以及各自的包装类
  • 注意double以及float由于舍入错误而不被支持
  • null被认为是有效的
  • 被标注元素必须为是一个数字,其值必须大于等于指定的最小值
  • 支持的类型为BigDecimal 、BigInteger 、CharSequencebyte 、short 、int 、long 以及各自的包装类
  • 注意double以及float由于舍入错误而不被支持
  • null被认为是有效的
  • 被标注元素必须为是一个数字,其值必须小于等于指定的最大值
  • 支持的类型为BigDecimal 、BigInteger 、CharSequencebyte 、short 、int 、long 以及各自的包装类
  • 注意double以及float由于舍入错误而不被支持
  • null被认为是有效的
  • 被标注元素必须为是一个严格意义上的负数(即0被认为是无效的)
  • 支持的类型为BigDecimal 、BigIntegerbyte 、short 、int 、long 、floatdouble以及各自的包装类
  • null被认为是有效的
  • 被标注元素必须为是负数或者0
  • 支持的类型为BigDecimal 、BigIntegerbyte 、short 、int 、long 、floatdouble以及各自的包装类
  • null被认为是有效的
  • 被标注元素必须为是一个严格意义上的正数(即0被认为是无效的)
  • 支持的类型为BigDecimal 、BigIntegerbyte 、short 、int 、long 、floatdouble以及各自的包装类
  • null被认为是有效的
  • 被标注元素必须为是正数或者0
  • 支持的类型为BigDecimal 、BigIntegerbyte 、short 、int 、long 、floatdouble以及各自的包装类
  • null被认为是有效的
  • 被标注元素的大小必须在指定的边界区间
  • 支持的类型为CharSequence(计算字符序列的长度) 、Collection(计算集合的大小)、Map(计算map的大小) 、Array(计算数组的长度)
  • null被认为是有效的
  • 被标注元素必须是在可接受范围内的数字
  • 支持的类型为BigDecimal 、BigIntegerCharSequencebyte 、short 、int 、long 以及各自的包装类
  • null被认为是有效的
  • 被标注元素必须是过去的某个时刻、日期或者时间
  • “现在”的概念是附加在Validator或者ValidatorFactory中的ClockProvider定义的,默认的ClockProvider根据虚拟机定义了当前时间,如果需要的话,会应用当前默认时区
  • 支持的类型为java.util.Date 、java.util.Calendar、java.time.Instant、java.time.LocalDate 、java.time.LocalDateTime 、java.time.LocalTime} 、java.time.MonthDay 、java.time.OffsetDateTime 、java.time.OffsetTime 、java.time.Year 、java.time.YearMonth 、java.time.ZonedDateTime 、java.time.chrono.HijrahDate 、java.time.chrono.JapaneseDate 、java.time.chrono.MinguoDate、java.time.chrono.ThaiBuddhistDate 以及各自的包装类

  • 被标注元素必须是过去或现在的某个时刻、日期或者时间
  • “现在”的概念是附加在Validator或者ValidatorFactory中的ClockProvider定义的,默认的ClockProvider根据虚拟机定义了当前时间,如果需要的话,会应用当前默认时区
  • “现在”的概念相对的定义在使用的约束上,例如,如果约束在Year上,那么现在表示当前年份
  • 支持的类型为java.util.Date 、java.util.Calendar、java.time.Instant、java.time.LocalDate 、java.time.LocalDateTime 、java.time.LocalTime} 、java.time.MonthDay 、java.time.OffsetDateTime 、java.time.OffsetTime 、java.time.Year 、java.time.YearMonth 、java.time.ZonedDateTime 、java.time.chrono.HijrahDate 、java.time.chrono.JapaneseDate 、java.time.chrono.MinguoDate、java.time.chrono.ThaiBuddhistDate 以及各自的包装类

  • 被标注元素必须是未来的某个时刻、日期或者时间
  • “现在”的概念是附加在Validator或者ValidatorFactory中的ClockProvider定义的,默认的ClockProvider根据虚拟机定义了当前时间,如果需要的话,会应用当前默认时区
  • 支持的类型为java.util.Date 、java.util.Calendar、java.time.Instant、java.time.LocalDate 、java.time.LocalDateTime 、java.time.LocalTime} 、java.time.MonthDay 、java.time.OffsetDateTime 、java.time.OffsetTime 、java.time.Year 、java.time.YearMonth 、java.time.ZonedDateTime 、java.time.chrono.HijrahDate 、java.time.chrono.JapaneseDate 、java.time.chrono.MinguoDate、java.time.chrono.ThaiBuddhistDate 以及各自的包装类

  • 被标注元素必须是未来或现在的某个时刻、日期或者时间
  • “现在”的概念是附加在Validator或者ValidatorFactory中的ClockProvider定义的,默认的ClockProvider根据虚拟机定义了当前时间,如果需要的话,会应用当前默认时区
  • “现在”的概念相对的定义在使用的约束上,例如,如果约束在Year上,那么现在表示当前年份
  • 支持的类型为java.util.Date 、java.util.Calendar、java.time.Instant、java.time.LocalDate 、java.time.LocalDateTime 、java.time.LocalTime} 、java.time.MonthDay 、java.time.OffsetDateTime 、java.time.OffsetTime 、java.time.Year 、java.time.YearMonth 、java.time.ZonedDateTime 、java.time.chrono.HijrahDate 、java.time.chrono.JapaneseDate 、java.time.chrono.MinguoDate、java.time.chrono.ThaiBuddhistDate 以及各自的包装类

    5. 异常模块

    还有一个问题,就是我们定义的message没有生效,比如“技能名称不能为空”,并没有出现在返回结果中,取而代之的是400异常,那么怎样才能返回我们想要的message呢
    首先我们在controller当中定一个一个方法,用@ExceptionHandler注解标注一下,用来获取controller抛出的异常,然后我们跟踪一下断点,看一下到底是什么异常

    package com.beemo.validation.demo2.controller;
    import com.beemo.validation.demo2.entity.Ability;
    import org.springframework.validation.annotation.Validated;
    import org.springframework.web.bind.annotation.*;
    import javax.validation.ConstraintViolationException;
    import javax.validation.Valid;
    @RestController
    @RequestMapping("/demo2/ability")
    @Validated
    public class AbilityController {
         * @param entity 要保存的技能实体
         * @return 保存结果
        @PostMapping("save")
        public String save(@Valid @RequestBody Ability entity) {
            // 调用service等
            return "ok";
        @ExceptionHandler
        public void handleException(Exception e) {
            e.printStackTrace();
    

    抛出的是org.springframework.web.bind.MethodArgumentNotValidException
    在看一下DEBUG窗口中的每个参数,发现bindingResult->errors->field和defaultMessage,一个违反约束的字段名称,另一个是违我们自定义的message

     此时我们就可以进行处理,返回我们想要的结果,而不是抛出400

    5.1 优化返回值

    在实际开发中,一般不会返回一个“ok”或者“success”这种字符串,通常情况下会返回一个json字符串,其中包含

  • 一个表示结果的状态值,例如HTML状态码或自定义状态值
  • 一个返回消息,解释该状态值或结果
  • public static R violateConstraint(List<Map<String, String>> violation) { return new R(2, "参数校验未通过", violation);

    修改controller

    package com.beemo.demo2.controller;
    import com.beemo.demo2.common.R;
    import com.beemo.demo2.entity.Ability;
    import org.springframework.validation.FieldError;
    import org.springframework.validation.annotation.Validated;
    import org.springframework.web.bind.MethodArgumentNotValidException;
    import org.springframework.web.bind.annotation.*;
    import javax.validation.Valid;
    import java.util.List;
    import java.util.stream.Collectors;
    @RestController
    @RequestMapping("/demo2/ability")
    @Validated
    public class AbilityController {
         * @param entity 要保存的技能实体
         * @return 保存结果
        @PostMapping("save")
        public R save(@Valid @RequestBody Ability entity) {
            // 调用service等
            return R.success();
    

    将异常处理方法提出,标注@ControllerAdvice注解,使得每个controller的异常都可以用该方法处理,并修改返回值,并且如果是单独提出来一个模块,需要在启引用该模块的启动类上加扫描

    package com.beemo.common.config;
    import com.beemo.common.common.R;
    import org.springframework.stereotype.Component;
    import org.springframework.validation.FieldError;
    import org.springframework.web.bind.MethodArgumentNotValidException;
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.annotation.ResponseBody;
    import javax.validation.ConstraintViolationException;
    import java.util.List;
    import java.util.stream.Collectors;
    @ControllerAdvice
    @ResponseBody
    public class MyExceptionHandler {
        @ExceptionHandler
        public R handleException(MethodArgumentNotValidException e) {
            List<String> violations = e.getBindingResult().getFieldErrors().stream().map(FieldError::getDefaultMessage).
                    collect(Collectors.toList());
            return R.violateConstraint(violations);
    
    package com.beemo.demo2;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    @SpringBootApplication(scanBasePackages = "com.beemo.*")
    public class Demo2Application {
        public static void main(String[] args) {
            SpringApplication.run(Demo2Application.class, args);
    

    然后我们再测试一下

     发现得到的结果再也不是400异常,而是我们指定的message集合了

    6. 验证非前台传递的参数

    除了在controller验证前台传递的参数之外,有时我们还需要验证诸如自己new的对象,或者从其他方法查询出来的对象,这时候我们可能需要把这些操作放在service层或其他层

    6.1 调用非本类的校验方法

    例如我们自己new了一个对象,然后调用其他类的一个验证方法
    建立一个service接口以及一个实现类
    我们在实现类上,模拟controller校验,加上@Validated以及@Valid注解

    package com.beemo.demo3.service;
    import com.beemo.demo3.entity.Ability;
     * 技能service接口
    public interface IAbilityService {
         * @param ability
        void saveOne(Ability ability);
    
    package com.beemo.demo3.service.impl;
    import com.beemo.demo3.entity.Ability;
    import com.beemo.demo3.service.IAbilityService;
    import org.springframework.stereotype.Service;
    import org.springframework.validation.annotation.Validated;
    import javax.validation.Valid;
    import javax.validation.constraints.NotNull;
    import java.util.Arrays;
    import java.util.List;
     * 技能service实现类
    @Validated
    @Service
    public class AbilityServiceImpl implements IAbilityService {
        @Override
        public void saveOne(@Valid @NotNull Ability ability) {
            System.out.println("通过校验");
            // 进行保存操作等...
    

    然后在controller中调用该方法

    package com.beemo.demo3.controller;
    import com.beemo.demo3.entity.Ability;
    import com.beemo.demo3.service.IAbilityService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.validation.annotation.Validated;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    @RestController
    @RequestMapping("/demo3/ability")
    @Validated
    public class AbilityController {
        @Autowired
        private IAbilityService abilityService;
         * @return 保存结果
        @PostMapping("save")
        public String save() {
            // new
            Ability ability = new Ability();
            abilityService.saveOne(ability);
            return "ok";
    

    我们进行测试发现,并没有我们符合想象的返回R,相反在后台控制台报了一个异常

    javax.validation.ConstraintDeclarationException: HV000151: A method overriding another method must not redefine the parameter constraint configuration, but method AbilityServiceImpl#saveOne(Ability) redefines the configuration of IAbilityService#saveOne(Ability).
        at org.hibernate.validator.internal.metadata.aggregated.rule.OverridingMethodMustNotAlterParameterConstraints.apply(OverridingMethodMustNotAlterParameterConstraints.java:24) ~[hibernate-validator-6.1.5.Final.jar:6.1.5.Final]
        at org.hibernate.validator.internal.metadata.aggregated.ExecutableMetaData$Builder.assertCorrectnessOfConfiguration(ExecutableMetaData.java:462) ~[hibernate-validator-6.1.5.Final.jar:6.1.5.Final]
        at org.hibernate.validator.internal.metadata.aggregated.ExecutableMetaData$Builder.build(ExecutableMetaData.java:380) ~[
        ......
    一个重写的方法禁止重新定义参数的约束配置,但是方法AbilityServiceImpl#saveOne(Ability) 重新定义了 IAbilityService#saveOne(Ability)的配置

    翻译过来就是
    如果你的接口没有定义约束,那么你的实现类就不能够定义该约束

    按照异常信息,我们试着将验证放在接口中在尝试一下

    package com.beemo.demo3.service;
    import com.beemo.demo3.entity.Ability;
    import org.springframework.validation.annotation.Validated;
    import javax.validation.Valid;
    import javax.validation.constraints.NotNull;
    @Validated
     * 技能service接口
    public interface IAbilityService {
         * @param ability
        void saveOne(@Valid @NotNull Ability ability);
    

    测试之后发现返回结果为500异常,这次控制器打印异常信息明显跟上次不一样,貌似确实是通过校验了,只不过抛出的异常不一样

    javax.validation.ConstraintViolationException: saveOne.ability: 不能为null
        at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:117) ~[spring-context-5.2.6.RELEASE.jar:5.2.6.RELEASE]
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.2.6.RELEASE.jar:5.2.6.RELEASE]
        at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749) ~[spring-aop-5.2.6.RELEASE.jar:5.2.6.RELEASE]
        ......

    我们发现在service层中如果违法约束抛出的异常为ConstraintViolationException,而并非在controller中的MethodArgumentNotValidException
    我们再次改进异常处理方法,然后跟踪一下异常的信息

     根据调试的信息,我们就可以处理我们的返回值了

        @ExceptionHandler
        public R handleException2(ConstraintViolationException e) {
            List<String> violations = e.getConstraintViolations().stream()
                    .map(ConstraintViolation::getMessageTemplate).collect(Collectors.toList());
            return R.violateConstraint(violations);
    

    再测试一下

    6.2 调用本类的校验方法

    场景:我们需要从EXCEL中读取数据,然后保存数据库中,需要判断每一条记录,如果正确就进行保存,如果失败则打印日志,接口和实现类如下

    package com.beemo.demo3.service;
    import com.beemo.demo3.entity.Ability;
    import org.springframework.validation.annotation.Validated;
    import javax.validation.Valid;
    import javax.validation.constraints.NotNull;
    @Validated
     * 技能service接口
    public interface IAbilityService {
         * @param ability
        void saveOne(@Valid @NotNull Ability ability);
         * 批量保存EXCEL中的数据
        void saveOnesFromExcel();
    
    package com.beemo.demo3.service.impl;
    import com.beemo.demo3.entity.Ability;
    import com.beemo.demo3.service.IAbilityService;
    import com.google.common.collect.Lists;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.stereotype.Service;
    import javax.validation.ConstraintViolationException;
    import java.util.List;
     * 技能service实现类
    @Service
    @Slf4j
    public class AbilityServiceImpl implements IAbilityService {
        @Override
        public void saveOne(Ability ability) {
            System.out.println("通过校验");
            // 进行保存操作等...
         * 批量保存EXCEL中的数据
        @Override
        public void saveOnesFromExcel() {
            List<Ability> data = readFromExcel();
            for (int i = 0, size = data.size(); i < size; i ++) {
                try {
                    saveOne(data.get(i));
                    System.out.println("第" + i + "条记录保存成功");
                } catch (ConstraintViolationException e) {
                    log.error("第" + i + "条记录违法约束:" + e.getMessage());
                } catch (Exception e) {
                    log.error("第" + i + "条记录保存失败");
         * 从EXCEL中读取
         * @return
        private List<Ability> readFromExcel() {
            return Lists.newArrayList(new Ability(null, null, (byte)1),
                    new Ability(null, "测试描述", null),
                    new Ability("测试名称", null, null),
                    new Ability("约德尔诱捕器", "布置一个陷阱,陷阱可以束缚敌方英雄2秒并将目标暴露在己方视野内3秒。", (byte)1));
    

    我们模拟了一个从EXCEL中读取list的方法,然后调用了save方法,该方法有参数验证,我们来进行测试

    控制台打印成功,证明我们的约束并没有成功,但是我们的写法看似没问题
    其实这个原因就是因为第一个方法saveFromExcel并没有标注验证,不论该方法怎么调用本类的验证方法都不会生效,此问题原因同@Transactional以及@Aysnc标注的方法,其本质原因是因为代理的问题,这里不做过多探讨,解决该问题的方法有三种

  • (不推荐)将验证方法移到其他类中 。这种方法奏效,但是无缘无故需要多建立一个service,有时候可能就是一个空方法,只不过参数有验证,其他不知道的小伙伴看到可能会比较懵
  • 注入ApplicationContext获取bean
  • public void saveOnesFromExcel() { List<Ability> data = readFromExcel(); for (int i = 0, size = data.size(); i < size; i ++) { try { applicationContext.getBean(IAbilityService.class).saveOne(data.get(i)); System.out.println("第" + i + "条记录保存成功"); } catch (ConstraintViolationException e) { log.error("第" + i + "条记录违法约束:" + e.getMessage()); } catch (Exception e) { log.error("第" + i + "条记录保存失败");

    3. 通过注入自己来获取当前类的实例,再调用该实例的方法。需要加@Lazy注解防止自我注入时spring抛出org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.core.env.Environment' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}异常

    @Autowired
    @Lazy
    private IAbilityService abilityService;
      * 批量保存EXCEL中的数据
    @Override
    public void saveOnesFromExcel() {
        List<Ability> data = readFromExcel();
        for (int i = 0, size = data.size(); i < size; i ++) {
            try {
                abilityService.saveOne(data.get(i));
                System.out.println("第" + i + "条记录保存成功");
            } catch (ConstraintViolationException e) {
                log.error("第" + i + "条记录违法约束:" + e.getMessage());
            } catch (Exception e) {
                log.error("第" + i + "条记录保存失败");
    

    6.3 关于@Validated的位置

    我们已经清楚,约束配置的注解,例如@Valid@NotNull等,需要在接口上进行配置,那么@Validated需要标注在哪里呢,答案是接口和实现类都可以,但是标注位置不同,也有一些区别

  • 标注在接口:意为实现类都回开启验证
  • 标注在实现类:意为标注该注解的实现类才会开启验证,如果有一个实现类未标注@Validated,那么即使接口有约束配置,也不会在该实现类上进行校验
  • 6.4 关于实现类需不需要标注约束配置

    个人感觉有优点优缺点
    优点:一般看代码的时候,都不会看接口,而是直接看实现类。如果标注在实现类上,可以更直观的看到该方法的约束配置
    缺点:必须与接口完全对应,如果接口修改约束配置,那么实现类必须相应的进行修改,否则会抛出异常

    6.5 想让枚举类属性验证生效,需要添加@valid注解

    6.6 feign调用中校验也可以生效 

    无需在feign接口中添加校验注解,只需在controller的接口方法参数中添加@valid

    转载:https://blog.csdn.net/csdn_mrsongyang/article/details/106115243

    EMBED THE SNIPPET BELOW IN YOUR SITE