;
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
、CharSequence
、byte
、short
、int
、long
以及各自的包装类
注意double
以及float
由于舍入错误而不被支持
null
被认为是有效的
被标注元素必须为是一个数字,其值必须小于等于指定的最大值
支持的类型为BigDecimal
、BigInteger
、CharSequence
、byte
、short
、int
、long
以及各自的包装类
注意double
以及float
由于舍入错误而不被支持
null
被认为是有效的
被标注元素必须为是一个严格意义上的负数(即0被认为是无效的)
支持的类型为BigDecimal
、BigInteger
、byte
、short
、int
、long
、float
、double
以及各自的包装类
null
被认为是有效的
被标注元素必须为是负数或者0
支持的类型为BigDecimal
、BigInteger
、byte
、short
、int
、long
、float
、double
以及各自的包装类
null
被认为是有效的
被标注元素必须为是一个严格意义上的正数(即0被认为是无效的)
支持的类型为BigDecimal
、BigInteger
、byte
、short
、int
、long
、float
、double
以及各自的包装类
null
被认为是有效的
被标注元素必须为是正数或者0
支持的类型为BigDecimal
、BigInteger
、byte
、short
、int
、long
、float
、double
以及各自的包装类
null
被认为是有效的
被标注元素的大小必须在指定的边界区间
支持的类型为CharSequence
(计算字符序列的长度) 、Collection
(计算集合的大小)、Map
(计算map的大小) 、Array
(计算数组的长度)
null
被认为是有效的
被标注元素必须是在可接受范围内的数字
支持的类型为BigDecimal
、BigInteger
、CharSequence
、byte
、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
![](https://img2020.cnblogs.com/blog/893356/202201/893356-20220104152225356-1491344586.png)
此时我们就可以进行处理,返回我们想要的结果,而不是抛出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 ![]()