SpringBoot开发秘籍 - 集成参数校验及高阶技巧
对于 web
服务来说,为防止非法参数对业务造成影响,在 Controller
层一定要对参数进行校验!本章我们以SpringBoot项目为例,介绍参数校验的基本用法以及一些高级技巧,希望能对你有所帮助。
简单使用
-
要在Springboot项目中加入参数校验功能首先得加入
spring-boot-starter-validation
依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>
-
然后给需要校验的字段添加上约束性注解,如我们对实体类参数进行校验
@Data public class ValidEntity{ private int id; @NotBlank private String appId; @NotBlank private String name; @Email private String email; }
常见约束注解如下:
注解 | 功能 |
---|---|
@AssertFalse | 可以为null,如果不为null的话必须为false |
@AssertTrue | 可以为null,如果不为null的话必须为true |
@DecimalMax | 设置不能超过最大值 |
@DecimalMin | 设置不能超过最小值 |
@Digits | 设置必须是数字且数字整数的位数和小数的位数必须在指定范围内 |
@Future | 日期必须在当前日期的未来 |
@Past | 日期必须在当前日期的过去 |
@Max | 最大不得超过此最大值 |
@Min | 最大不得小于此最小值 |
@NotNull | 不能为null,可以是空 |
@Null | 必须为null |
@Pattern | 必须满足指定的正则表达式 |
@Size | 集合、数组、map等的size()值必须在指定范围内 |
必须是email格式 | |
@Length | 长度必须在指定范围内 |
@NotBlank | 字符串不能为null,字符串trim()后也不能等于“” |
@NotEmpty | 不能为null,集合、数组、map等size()不能为0;字符串trim()后可以等于“” |
@Range | 值必须在指定范围内 |
@URL | 必须是一个URL |
注:此表格只是简单的对注解功能的说明,并没有对每一个注解的属性进行说明;可详见源码。
-
在Controller层对需要参数校验的方法加上@Validated注解
参数校验一般分为两类:在Controller使用模型接收数据时, @Validated注解直接放在该模型参数前即可。
@PostMapping(value = "test1") public String test1(@Validated @RequestBody ValidEntity validEntity){ return "test1 valid success"; } @PostMapping(value = "test3") public String test3(@Validated ValidEntity validEntity){ return "test3 valid success"; }
当我们是直接在Controller层中的参数前,使用约束注解时,@Validated要直接放在类上
@PostMapping(value = "test2") public String test2(@Email String email){ return "test2 valid success"; }
此时需要在主类上增加@Validated注解
@Validated @RestController @RequestMapping("/demo/valid") public class ValidController { ... }
在参数校验时我们既可以使用@Validated也可以使用@Valid注解,两者功能大部分类似;主要区别在于:
@Valid属于javax下的,而@Validated属于spring下;
@Valid支持嵌套校验、而@Validated不支持,@Validated支持分组,而@Valid不支持。
统一异常处理
如果参数校验未通过Spring会抛出三种类型的异常
-
当对@RequestBody需要的参数进行校验时会出现
org.springframework.web.bind.MethodArgumentNotValidException
-
当直接校验具体参数时会出现
javax.validation.ConstraintViolationException
,也属于ValidationException
异常
-
当直接校验对象时会出现
org.springframework.validation.BindException
在SpringBoot中统一拦截处理只需要在配置类上添加 @RestControllerAdvice
注解,然后在具体方法中通过 @ExceptionHandler
指定需要处理的异常,具体代码如下:
@RestControllerAdvice @Slf4j public class GlobalExceptionHandler { public static final String ERROR_MSG = "系统异常,请联系管理员。"; @ExceptionHandler(value = {BindException.class, ValidationException.class, MethodArgumentNotValidException.class}) public ResponseEntity<Result<String>> handleValidatedException(Exception e) { Result<String> resp = null; if (e instanceof MethodArgumentNotValidException) { // BeanValidation exception MethodArgumentNotValidException ex = (MethodArgumentNotValidException) e; resp = new Result<>(Integer.toString(HttpStatus.BAD_REQUEST.value()), ex.getBindingResult().getAllErrors().stream().map(ObjectError::getDefaultMessage).collect(Collectors.joining(", ")) , getStackTrace(ex)); } else if (e instanceof ConstraintViolationException) { // BeanValidation GET simple param ConstraintViolationException ex = (ConstraintViolationException) e; resp = new Result<>(Integer.toString(HttpStatus.BAD_REQUEST.value()), ex.getConstraintViolations().stream().map(ConstraintViolation::getMessage).collect(Collectors.joining(", ")) , getStackTrace(ex)); } else if (e instanceof BindException) { // BeanValidation GET object param BindException ex = (BindException) e; resp = new Result<>(Integer.toString(HttpStatus.BAD_REQUEST.value()), ex.getAllErrors().stream().map(ObjectError::getDefaultMessage).collect(Collectors.joining(", ")) , getStackTrace(ex)); } return new ResponseEntity<>(resp,HttpStatus.BAD_REQUEST); } private String getStackTrace(Exception e) { //打印日志开关,可通过配置读取 boolean printStrackTrace = false; if(printStrackTrace){ StringWriter sw = new StringWriter(); e.printStackTrace(new PrintWriter(sw)); return sw.toString(); }else{ return ERROR_MSG; } } }
最终实现效果如下:
参数分组
有下面一个实体类,我们需要对其进行参数校验。
@Data public class ValidEntity { private int id; @NotBlank private String appId; @NotBlank private String name; @Email private String email; }
但是实际业务是在编辑的时候 appId
才是必填,在新增的时候 name
必填,这时候可以用groups分组功能来实现:同一个模型在不同场景下,动态区分校验模型中的不同字段。
使用方式
-
首先我们定义一个分组接口ValidGroup,再在分组接口总定义出多个不同的操作类型,Create,Update,Query,Delete
public interface ValidGroup extends Default{ interface Crud extends ValidGroup{ interface Create extends Crud{ } interface Update extends Crud{ } interface Query extends Crud{ } interface Delete extends Crud{ } } }
这里的 ValidGroup
继承了Default,当然也可以不继承,具体区别我们后面再说。
-
在模型中给校验参数分配分组
@Data @ApiModel(value="ValidEntity") public class ValidEntity { private int id; @NotBlank(groups = ValidGroup.Crud.Update.class) private String appId; @NotBlank(groups = ValidGroup.Crud.Create.class) private String name; @Email private String email; }
tips:这里@Email注解未指定分组,默认会属于Default分组,appId和name指定了分组就不会再属于Default分组了。
-
在参数校验时通过value属性指定分组
这里通过 @Validated(value = ValidGroup.Crud.Update.class)
指定了具体的分组,上面提到的是否继承Default的区别在于:
-
如果继承了Default,@Validated标注的注解也会校验未指定分组或者Default分组的参数,比如email
-
如果不继承Default则不会校验未指定分组的参数,需要加上
@Validated(value = {ValidGroup.Crud.Update.class, Default.class}
才会校验
快速失败(Fali Fast)
默认情况下在对参数进行校验时Spring Validation会校验完所有字段然后才抛出异常,可以通过配置开启 Fali Fast
模式,一旦校验失败就立即返回。
@Configuration public class ValidatedConfig { @Bean public Validator validator() { ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class) .configure() // 快速失败模式 .failFast(true) .buildValidatorFactory(); return validatorFactory.getValidator(); } }
以上,希望对你有所帮助!
这里为大家准备了一份小小的礼物,关注公众号,输入如下代码,即可获得百度网盘地址,无套路领取!
001:《程序员必读书籍》
002:《从无到有搭建中小型互联网公司后台服务架构与运维架构》
003:《互联网企业高并发解决方案》
004:《互联网架构教学视频》
006:《SpringBoot实现点餐系统》
007:《SpringSecurity实战视频》
008:《Hadoop实战教学视频》
009:《腾讯2019Techo开发者大会PPT》
010: 微信交流群
本文分享自微信公众号 - JAVA日知录(javadaily)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
Kafka万亿级消息实战
一、Kafka应用 本文主要总结当Kafka集群流量达到万亿级记录/天或者十万亿级记录/天 甚至更高后,我们需要具备哪些能力才能保障集群高可用、高可靠、高性能、高吞吐、安全的运行。 这里总结内容主要针对Kafka2.1.1版本,包括集群版本升级、数据迁移、流量限制、监控告警、负载均衡、集群扩/缩容、资源隔离、集群容灾、集群安全、性能优化、平台化、开源版本缺陷、社区动态等方面。本文主要是介绍核心脉络,不做过多细节讲解。下面我们先来看看Kafka作为数据中枢的一些核心应用场景。 下图展示了一些主流的数据处理流程,Kafka起到一个数据中枢的作用。 接下来看看我们Kafka平台整体架构; 1.1 版本升级 1.1.1开源版本如何进行版本滚动升级与回退 官网地址:http://kafka.apache.org 1.1.1.2 源码改造如何升级与回退 由于在升级过程中,必然出现新旧代码逻辑交替的情况。集群内部部分节点是开源版本,另外一部分节点是改造后的版本。所以,需要考虑在升级过程中,新旧代码混合的情况,如何兼容以及出现故障时如何回退。 1.2 数据迁移 由于Kafka集群的架构特点,这必然导致...
- 下一篇
基于 Qt Quick Plugin 快速构建桌面端跨平台组件
桌面端的 UI 开发框架对比移动端、Web 端的成熟方案,一直处于不温不火的状态。随着疫情掀起的风波,桌面端在线教育、视频会议等需求不断涌现。传统平台下的开发框架难以满足需求,而类 DirectUI 的框架因跨平台、可拓展性差、门槛高等问题并不能得到一些企业的认可。桌面端 Electron、Flutter 类框架出于性能、原生平台支持等个性化需求考虑,往往得不到最好的解决方案。 Qt Quick 可以较好得解决上述提到的问题。本文将从两个方面介绍通过 Qt Quick 是如何快速实现桌面端跨平台业务组件构建的,首先我们聊一下 Qt Quick 在桌面端开发的优势,再详细如何创建一个 C++ 拓展插件给 Qt Quick 应用来使用。 Qt Quick 优势 跨平台特性 Qt Quick Plugin 机制可以满足上面提到的诸多需求。首先 Qt 对跨平台支持非常友好,仅需要对特殊平台做一些简单适配就可以使用一套代码可跑在不同终端。官方以“One framework. One codebase. Any platform” 作为标题也突显了其在跨平台的方面所做的工作。 易分发组件 使用 Q...
相关文章
文章评论
共有0条评论来说两句吧...