作为服务端开发,验证前端传入的参数的合法性是一个必不可少的步骤,但是验证参数是一个基本上是一个体力活,而且冗余代码繁多,也影响代码的可阅读性,所以有没有一个比较优雅的方式来解决这个问题?
这么简单的问题当然早就有大神遇到并且解决了,这一篇文章主要讲一下解决基于spring-boot的验证参数的比较好的方法:利用validator-api来进行验证参数。
在spring-boot-starter-web
包里面有hibernate-validator
包,它提供了一系列验证各种参数的方法,所以说spring-boot已经帮我们想好要怎么解决这个问题了。
这篇文章针对spring-boot里面的spring-mvc介绍三种方式来验证参数。
(一):这个方法在网上大部分都可以查到,先假设我们的restful的接口接受一个GradeAndClassroomModel
类型的对象,并且这个类被定义成
@Data
public class GradeAndClassroomModel {
@Range(min = 1, max = 9, message = "年级只能从1-9")
private int grade;
@Range(min = 1, max = 99, message = "班级只能从1-99")
private int classroomNumber;
}
利用validator
提供的一系列注解,比如本例中的@Range
,就可以表示参数的范围和出错时候的提示信息。还有很多其他注解,这里就不一一列出
然后我们的Controller层的代码为
@RequestMapping(value = "/paramErrorTest", method = RequestMethod.GET)
public String paramErrorTest(
@Valid
@ModelAttribute
GradeAndClassroomModel gradeAndClassroomModel,
BindingResult result) {
return classroomService.getTeacherName(gradeAndClassroomModel.getGrade(), gradeAndClassroomModel.getClassroomNumber());
}
其中如果验证出错,result对象里面就会有错误信息,然后可以自己进行处理。
(二): 针对上面的例子,会有人说,就两个参数,为什么要作为对象呢?会不会太麻烦?确实,如果只有少数对象,直接把参数写到Controller层,然后在Controller层进行验证就可以了。
@RequestMapping(value = "/teacherName", method = RequestMethod.GET)
public String teacherName(
@Range(min = 1, max = 9, message = "年级只能从1-9")
@RequestParam(name = "grade", required = true)
int grade,
@Min(value = 1, message = "班级最小只能1")
@Max(value = 99, message = "班级最大只能99")
@RequestParam(name = "classroom", required = true)
int classroom) {
return classroomService.getTeacherName(grade, classroom);
}
如果直接把validator
提供的注解移除来写到请求参数上面的话是不是就可以了呢?答案是错,为什么这样不能成功的验证参数呢?具体原因大家可以参考官方文档:http://docs.spring.io/spring-framework/docs/current/spring-framework-reference/htmlsingle/#validation-beanvalidation-spring-method
上面的文档已经说的很清楚了,所以我们需要创建一个Bean
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
然后在类方法上面加上注解@Validated
@RestController
@RequestMapping("/spring-boot/classroom")
@Validated
public class ClassroomController {
...
}
然后之前没有生效的注解@Range
、@Min
、@Max
等validator
包里面提供的注解就可以生效了。
(三)估计到了这里又会有人问,如果validator
包里面注解不能满足我们的需求,我们是否可以自己定义参数验证的逻辑。答案是肯定的,我们可以利用
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER, ElementType.FIELD})
@Constraint(validatedBy = {Validator.class})
public @interface ParamValidator {
String message() default "Parameter error!";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
和
public class Validator implements ConstraintValidator<ParamValidator, Object> {
...
}
组合进行自定义,具体的例子网上其他文章就很多了,这里就不进行详细的例子了,但是最终使用的时候就是
@RequestMapping(value = "/paramValidator", method = RequestMethod.GET)
public String paramValidator(
@ParamValidator(isRequired = true, desc = "年级", range = "int:1~9", message = "年级只能从1-9")
@RequestParam(name = "grade", required = true)
int grade,
@ParamValidator(isRequired = true, desc = "班级", range = "int:1~99", message = "班级只能从1-99")
@RequestParam(name = "classroom", required = true)
int classroom) {
return classroomService.getTeacherName(grade, classroom);
}
另外不要忘记方法二
里面里面提到的MethodValidationPostProcessor
这个bean,如果没有初始化这个bean,自定义的验证方法也不会执行。验证逻辑会失效。
是不是通过这样写注解的方式来验证进行请求的参数,代码逻辑更佳清晰和优雅?表达的含义也会更佳清楚?并且没有了大量重复的类似的验证代码。
Ps:这里的代码都是基于spring-mvc框架来试验的,如果有人并没有使用spring-mvc作为rest框架,而是使用jersey来作为rest框架的话,可能一些细节方面需要调整, 但是这三种方案应该都是可以兼容的。
最后如果觉得所讲的东西能够帮助到你,并且希望了解更多的知识,进行更详细的深入的学习,欢迎加群632109190进行讨论和学习。