SpringMVC集成Hibernate Validator进行注解式的参数校验

                                                        ——让代码更少、更加专注于业务逻辑

1 问题背景:

参数验证是一个常见的问题,例如验证用户输入的密码是否为空、邮箱是否合法等。但是无论是前端还是后台,都需对用户输入进行验证,以此来保证系统数据的正确性。对于web来说,有些人可能理所当然的想在前端验证就行了,但这样是非常错误的做法,前台的验证一般是通过Javascript,js代码是可以被禁用和篡改的,所以相对后台检验而言,安全性会低一些。前端代码对于用户来说是透明的,稍微有点技术的人就可以绕过这个验证,直接提交数据到后台。无论是前端网页提交的接口,还是提供给外部的接口,参数验证随处可见,也是必不可少的。总之,一切用户的输入都是不可信的。

基于这样的常识,在后端的代码开发中,参数校验是一个永远也绕不开的话题。然而编写参数校验代码的过程是一个技术含量不高,及其耗时,繁琐的过程。比如极大多数的参数校验代码就是:判断一下用户名是否已经存在、用户名长度是否满足要求、用户填写的邮箱是否合法。年龄参数是不是一个整数等等。为了减少重复代码的开发,许多有技术积淀的公司,一般都会提供一套或多套特有的参数校验工具类。以此减少代码量。这种做法在项目中非常普遍,有了这些工具类库,程序员在编写业务代码的过程中,工作效率可以大大提升。

然而,即便聪明如上述做法,我们的业务逻辑中依然还是可以见到大量的参数校验逻辑,倘若项目架构的不够合理,这些与参数校验逻辑有关的代码量会更多,真是XXX的裹脚布,又臭又长。大量繁杂的参数校验代码混杂在业务逻辑中,一来降低了代码的可读性;二是让人对业务本身的理解难度加大,很多刚加入公司的人往往是通过读码来理解业务的,公司的一些业务培训一般只能讲个大概,然而在阅读代码的过程中稍有不慎便会被这些“额外”的代码扰乱思路,对于新手,那更是异常噩梦,倘若老员工辞职了,撒手抛给新来的,这样的项目能不能维护下去都是个问题了。

2 Hibernate Validator介绍:

不过对于java开发者来说,解决上述难题越来越容易了。随着大量的Bean Validation 框架的问世,和主流框架对这些校验框架的集成和支持。我们是非常容易的能将业务逻辑和参数校验代码相分离的,其实说是分离还不够恰当,这里即将要介绍的hibernate Validator可以以注解的方式对参数进行校验,业务逻辑中的极大多数参数校验代码都可以省去,可以使代码更简洁,更加专注于业务逻辑。hibernate Validator是 Bean Validation 的参考实现,Hibernate Validator 提供了 JSR 303 规范中所有内置 constraint 的实现,除此之外还有一些附加的 constraint。为了满足特定的需求,用户还可以自实现更多的constraint以便满足特定的需求。

3 集成 Hibernate Validator前后代码对比:

为了能一目了然的看到集成Hibernate Validator的好处,我们来比较一下下述代码。假设我们有这样一个方法, 该方法是从某个SSM(Spring MVC + Spring + Mybatis)项目的controller层摘录下来的。该方法的目的是查询所有名字叫某某的男性或女性同胞。名字参数name不能为空值,性别参数gender必须为“F”或“M”,分别代表女性或男性。注意,这里的示例代码将参数校验逻辑放在了controller层,校验通过后再进入service层。可能有的项目会将参数校验挪到service层,当然这不是重点。这个查询方法的代码如下:

@RequestMapping(value = "/all", method = RequestMethod.GET)
@ResponseBody
public List<String> getAllPeople(String name, String gender) {if (StringUtils.isEmpty(name)) {throw new BusinessException(FALL, "用户名不能为空");}if (gender == null || (!gender.equals("F") && !gender.equals("M"))) {throw new BusinessException(FALL, "性别不合法");}return cityService.getAllCitys(name, gender);
}

上述查询方法是在没有集成Hibernate Validator的项目中,参数校验的一般方式,为了辅助完成参数校验,我们还不得不封装了一个工具类StringUtils用于判断字符串是否为空。如果参数异常,则抛出业务异常,该异常会交给框架的异常处理机制进行处理。我们可以看到,仅仅两个简单的参数就不得不写上多行代码来完成要求。然而,当我们将上述SSM项目与Hibernate Validator集成之后,参数校验可以在方法签名中完成,方法体内无需写任何与参数校验相关的代码逻辑。如果参数校验不通过,同样也会抛出异常,该异常同样会交给框架的异常处理机制进行处理。

@RequestMapping(value = "/all", method = RequestMethod.GET)
@ResponseBody
public List<String> getAllPeople(@NotBlank(message = "用户名不能为空" ) String name,@Gender(message = "性别不合法") String gender) {return cityService.getAllCitys(name, gender);
}

通过上述代码的比较,可以很明显的看到,后者代码简洁了很多,在参数数量很多,业务复杂的的方法中,代码量的差异会更加明显,同样的,代码的可读性也会有了天壤之别。

4. Spring MVC集成Hibernate Validator步骤:

4.1 如果项目中使用Maven,就需要在pom.xml中添加如下一段,Hibernate需要Java EL表达式,因此需要添加EL的依赖项。

<dependency><groupId>org.hibernate</groupId><artifactId>hibernate-validator</artifactId><version>5.3.4.Final</version>
</dependency>
<dependency><groupId>javax.el</groupId><artifactId>javax.el-api</artifactId><version>2.2.4</version>
</dependency>
<dependency><groupId>org.glassfish.web</groupId><artifactId>javax.el</artifactId><version>2.2.4</version>
</dependency>

4.2 在springmvc的配置文件(此处使用默认名:spring-mvc.xml)中添加如下配置开启校验功能

    <mvc:annotation-driven validator="validator" />
<!--    bean级别的校验 方法中的参数bean必须添加@Valid注解,后面紧跟着BindingResult result参数--><bean id="validator"class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"><property name="providerClass" value="org.hibernate.validator.HibernateValidator" /></bean>
<!--    方法级别的校验  要校验的方法所在类必须添加@Validated注解--><beanclass="org.springframework.validation.beanvalidation.MethodValidationPostProcessor"><!-- 可以引用自己的 validator 配置,在本文中(下面)可以找到 validator 的参考配置,如果不指定则系统使用默认的 --><property name="validator" ref="validator" /></bean>

4.3 在校验的方法所在类上添加@Validated注解可以开启方法级别的校验,第3节代码对比的列子中,参数校验注解直接添加在方法签名上的方法参数前面,这就是所谓的方法级别的校验。所谓bean级别的校验,就是方法的参数是一个Java Bean,校验的注解则添加到Java Bean的相应属性上。目前,在使用Spring MVC的项目中,bean级别的校验必须给bean参数添加@Valid注解。但在使用Jersey(一个REST FULL的标准实现框架)替代Spring MVC的项目中,则没有此要求。所以代码还会更方便。

5 Hibernate Validator 中内置的 constraint简介

       注解                作用

@Valid 被注释的元素是一个对象,需要检查此对象的所有字段值 
@Null 被注释的元素必须为 null 
@NotNull 被注释的元素必须不为 null 
@AssertTrue 被注释的元素必须为 true 
@AssertFalse 被注释的元素必须为 false 
@Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 
@Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 
@DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 
@DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 
@Size(max, min) 被注释的元素的大小必须在指定的范围内 
@Digits (integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内 
@Past 被注释的元素必须是一个过去的日期 
@Future 被注释的元素必须是一个将来的日期 
@Pattern(value) 被注释的元素必须符合指定的正则表达式

6 自定义校验注解示例

6.1 在第3节中,我们使用到了@Gender注解表示男女的性别,这其实不是Hibernate Validator自带的,而是一个自定义的注解。由于HV中的标准注解可能满足不了某些校验场景,因此自定义校验注解是有必要的。之所以写本文,目的是推荐公司的项目也也可以尝试使用这个优秀的bean validation框架,所以自定义注解的实现细节此处不进一步讲述,只是给出两个已经实现的列子,一个是之前使用到的注解@Gender,用于校验用户的性别。另一个注解是@PhoneNumber, 用于限制上传的参数必须是手机号。

6.2 @Gender注解类代码如下:

@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = Gender.Validator.class)
@SuppressWarnings("javadoc")
public @interface Gender
{String message() default "invalid gender";boolean allowBlank() default false;Class<?>[] groups() default {};Class<? extends Payload>[] payload() default{};public class Validator implements ConstraintValidator<Gender, String>{boolean allowBlank;@Overridepublic void initialize(Gender gender){allowBlank = gender.allowBlank();}@Overridepublic boolean isValid(String arg0, ConstraintValidatorContext arg1){if (arg0 == null){return allowBlank;}return arg0.equalsIgnoreCase("M") || arg0.equalsIgnoreCase("F");}}
}

6.2 @PhoneNumber注解如下:

@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PhoneNumber.Validator.class)
@SuppressWarnings("javadoc")
public @interface PhoneNumber {String message() default "invalid phone number";Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};public class Validator implements ConstraintValidator<PhoneNumber, String> {@Overridepublic void initialize(PhoneNumber arg0){}@Overridepublic boolean isValid(String arg0, ConstraintValidatorContext arg1){return StringUtil.isPhoneNumber(arg0);}}
}

@PhoneNumber注解中用到的工具类StringUtil的isPhoneNumber方法如下:

public final String PHONE ="^((13[0-9])|(14[5,7])|(15[^4,\\D])|(17[0,5-8])|(18[0-9]))\\d{8}$";
public static boolean isPhoneNumber(String phone){if (StringUtil.isNullOrEmpty(phone)){return false;}return match(RegExp.PHONE, phone);}

7 简单总结

本文主要介绍了hibernate Validator这一优秀的Bean Validation框架。并介绍了Spring MVC中集成这个参数校验框如何将业务逻辑与参数校验逻辑相分离,从而保持代码的精简和优雅。易读和易维护。本人之前所在公司的多个项目采用了这种方式,使得项目代码无比清爽。本人也愿意分享自己积累的一点微薄知识,如果将本文涉及的技术与上一篇篇经验案列讲到的内容联合使用,更会使项目增色不少,但愿在后续的项目中,本人能见证这一成熟技术的应用。

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/nmgrd/article/details/57088192

SpringMVC集成Hibernate Validator进行注解式的参数校验——让代码更少、更加专注于业务逻辑相关推荐

  1. java自定义注解实现前后台参数校验

    其实是可以通过@Constraint来限定自定义注解的方法. @Constraint(validatedBy = xxxx.class) 下面是我做的 java自定义注解实现前后台参数校验 的代码示例 ...

  2. Spring Boot集成Hibernate Validator

    废话不多说,直接开始集成环境. 一.环境集成 在项目中hibernate-Validator包在spring-boot-starter-web包里面有,不需要重复引用 .(整个Demo都是用PostM ...

  3. 自定义Hibernate Validator规则注解

    自定义规则注解 除了使用已定义的校验规则外,我们也可以根据自定的业务自定义校验规则,接下来我们介绍一下如何自定义 Hibernate Validator校验规则. 创建自定义规则无参数注解介绍 声明自 ...

  4. Springboot2参数校验: Hibernate Validator自定义注解

    1.Hibernate Validator介绍 Hibernate Validator 提供了 JSR 303 规范中所有内置 constraint 的实现,除此之外还有一些附加的 constrain ...

  5. 使用自定义注解实现接口参数校验

    1.前言 在接口的开发中,我们有时会想让某个接口只可以被特定的人(来源)请求,那么就需要在服务端对请求参数做校验. 这种情况我们可以使用interceptor来统一进行参数校验,但是如果很多个接口,有 ...

  6. SpringBoot 2 快速整合 | Hibernate Validator 数据校验

    概述 在开发RESTFull API 和普通的表单提交都需要对用户提交的数据进行校验,例如:用户姓名不能为空,年龄必须大于0 等等.这里我们主要说的是后台的校验,在 SpringBoot 中我们可以通 ...

  7. Spring Validation(使用Hibernate Validator)

    1.需要的jar包 hibernate-validator.5.1.3.Final.jar validation-api.1.1.0.Final.jar 2.springsevlet-config.x ...

  8. SpringBoot中使用Hibernate Validator校验工具类

    1.说明 在Spring Boot已经集成Hibernate Validator校验器的情况下, 对于配置了校验注解的请求参数, 框架会自动校验其参数, 但是如果想手动校验一个加了注解的普通对象, 比 ...

  9. postmapping注解参数说明_这么写参数校验(validator)就不会被劝退了~

    作者: 锦成同学http://juejin.im/post/5d3fbeb46fb9a06b317b3c48 整理:后端技术精选 很痛苦遇到大量的参数进行校验,在业务中还要抛出异常或者不断的返回异常时 ...

最新文章

  1. Linux-CAN Bus
  2. 最老程序员创业札记:全文检索、数据挖掘、推荐引擎应用48
  3. iOS Social框架
  4. js中call()方法和apply方法的使用
  5. jgGrid常用操作--持续更新
  6. 【读史笔记】《晋书·卫玠列传》
  7. Java回调函数实现案例
  8. matlab的simulink文件mdl和slx对比
  9. Unity接入Steam成就
  10. 施一公:如何写好一篇学术论文?
  11. hiveserver2 HA
  12. DFS判断回路及回路个数
  13. 【Unity】四叉树/八叉树管理和动态加载场景物件
  14. 在出境通关中如何应用智能智慧护照阅读器技术呢
  15. 微信 SDK for Laravel, 基于 overtrue/wechat
  16. 《星科快报》2021.11.11何为NFT
  17. android studio获取IMEI码
  18. android手势密码源码,Android自定义UI手势密码改进版源码下载
  19. 北大方正与火星人的恩怨
  20. Halcon 塑料制品表面的缺陷检测

热门文章

  1. 如何在线制作QQ微信表情包
  2. 基于翔云人工智能平台的人脸识别开发
  3. 基于树莓派(C语音)实现人脸识别(翔云平台)
  4. maven的repositories和pluginRepositories区别
  5. python mat文件_python变量保存为.mat文件
  6. “相信讲好一门课比写好一篇论文重要的人,今夜死去了”
  7. SQL Server 2017安装问题
  8. Android应用权限
  9. Linux中使用RAID技术提升磁盘读写速度及数据安全
  10. memcpy 引出的 chunk size 计算与内存对齐