您的位置:首页 > 编程语言 > Java开发

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

2017-02-25 18:42 1096 查看

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注解-->
<bean
class="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;

@Override
public void initialize(Gender gender)
{
allowBlank = gender.allowBlank();
}

@Override
public 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> {

@Override
public void initialize(PhoneNumber arg0)
{

}

@Override
public 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中集成这个参数校验框如何将业务逻辑与参数校验逻辑相分离,从而保持代码的精简和优雅。易读和易维护。本人之前所在公司的多个项目采用了这种方式,使得项目代码无比清爽。本人也愿意分享自己积累的一点微薄知识,如果将本文涉及的技术与上一篇篇经验案列讲到的内容联合使用,更会使项目增色不少,但愿在后续的项目中,本人能见证这一成熟技术的应用。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐