最近端午很久没有和二胖聚一聚了,因而约了二胖到人民广场去宰他一顿,正好最近他跳槽加薪了。<br/>
我:二胖据说你最近跳槽了,而且仍是从传统软件公司跳到了互联网公司,工资是否是涨了一点啊,今天你请客哈。<br/>
二胖:别说了,工资是涨了点,可是性价比反而变低了,之前到点就下班,如今下班到家都快12点了。<br/>
我:新公司怎么样还适应吗?除了上班时间久点。<br/>
二胖:哎,这个还真稍微有点不适应,这不是刚进去没啥事,leader
就给我安排了一个简单的用户保存功能(参数校验),原来之前公司个把小时就作好了的功能,在这新公司硬是折腾了两三天,真是苦不堪言。我改了好几个版本最终leader
才满意的点了点头。html
review
了(二胖在之前的公司代码review
是不存在的,只要功能实现就行了)。正好leader
今天有点时间,看到新同事提交的代码看看写的怎么样。 看着这个裸奔的接口,leader
把二胖叫了过去,语重心长的跟二胖说道:"你这个参数校验不写写吗?不怕人家攻击你的接口吗?这里不校验,直接用,不怕引入sql注入吗?这里不校验下邮箱是否符合格式吗?这个判空也不写,不怕大量的空指针,服务熔断吗?..."。面对leader
的拼命十三问,二胖心想试用期怕是有点难过哦?只能低着头回到工位从新按照leader
的教育整改起来,而后又从新提交了。leader
看了看说到:“此次代码比上次好多了,功能基本没啥问题了,可是这一块代码是否是能够在优化下,这样写不是很优雅”前端
if(Objects.isNull(user)){ throw new IllegalArgumentException("用户不能为空"); } if(StringUtils.isEmpty(user.getUserName())){ throw new IllegalArgumentException("用户名不能为空"); } if(StringUtils.isEmpty(user.getUserName())){ throw new IllegalArgumentException("用户名不能为空"); } if(StringUtils.isEmpty(user.getSex())){ throw new IllegalArgumentException("用户性别不能为空"); } if(Objects.isNull(user.getUserDetail())){ throw new IllegalArgumentException("用户详细信息不能为空"); } if(Objects.isNull(user.getUserDetail().getAddress())){ throw new IllegalArgumentException("用户地址不能为空"); } if(!"M".equals(user.getSex()) && !"F".equals(user.getSex())){ throw new IllegalArgumentException("用户性别不合法"); }
二胖也是一阵郁闷,仍是怀念之前的公司啊,功能实现就好,代码想怎么写就怎么写。互联网公司就是规矩多,写完代码还要写单测,还要监控一堆破事,活该这群人996.时间都花到这上面去了。抱怨该抱怨可是代码还得改啊。如今疫情期间好不容易找一个工做不能丢啊。
二狗想到之前不是学过aop
吗?再配合下自定义注解,这样代码就应该比较优雅了吧,说干就干。java
@Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD,ElementType.METHOD}) public @interface ParameterValidator { }
ParameterValidator
注解的方法。而后经过切面获取全部请求的参数,获取参数以后就解析参数上面的注解。配置切面啥的都比较简单,稍微复杂的就是反射解析参数了,由于要涉及到请求参数的嵌套结构。二胖习惯性的面向百度编程能copy
别人的代码坚定不去本身写。百度出来的基本上都是单层结构,简单基本类型的对象,没有涉及到是嵌套、级联的类型的情趣参数。最后在github
(全球最大的同性交友网站)找了一圈也没有找到合适的。既然拿来主义没有结果那就只能哼次哼次的本身写了,幸亏本身之前学过点反射的知识。花了一个小时经过递归调用写了个粗糙的版本,比较粗糙还有不少场景没有考虑进去。不过基本能够知足条件了部分代码以下:git
public static void checkField(Object object, Class<?> aClass) throws IllegalAccessException { boolean primitive = isPrimitive(aClass); if (primitive) { return; } Field[] declaredFields = filterField(aClass.getDeclaredFields()); for (Field field : declaredFields) { makeAccessible(field); // 校验咱们自定义注解 MyNotBlank fieldAnnotation = field.getAnnotation(MyNotBlank.class); Object currentObject = field.get(object); if (Objects.nonNull(fieldAnnotation)) { if (StringUtils.isEmpty(currentObject)) { throw new IllegalArgumentException(field.getName()+":"+fieldAnnotation.message()); } } if (!isJavaClass(field.getType())) { // 递归调用,有级联参数时候 checkField(currentObject); } else if (field.getType().isPrimitive()) { } else if (field.getType().isAssignableFrom(List.class)) { // 递归调用,解析list类型 getList(field, currentObject); } } }
而后赶忙测试一波,还不错基本功能实现了,可以实现判空检验了,也能够实现级联校验了。效果以下:
不过这个如今支持类型为基本类型和String
、List
的
后续若是参数类型是数组、或者Map
等等还得去解析。 这时候同事二狗从旁边走过,看到二胖这么认真的在敲代码。<br/>
二狗:二胖你又在写什么bug
啊。<br/>
二胖:在本身造个轮子,写个通用的参数校验。<br/>
二狗:这个如今市面上不是已经有现成的方案了吗?jsr(Java Specification Requests)
能够去了解下哦。<br/>
二胖:好的我立刻去查下资料。github
jsr
咱们就得先了解下什么是JCP(Java Community Process)
?JCP(Java Community Process) 是一个开放的国际组织,主要由Java开发者以及被受权者组成,职能是发展和更新。
它是指向JCP提出新增一个标准化技术规范的正式请求。任何人均可以提交JSR,(若是你以为本身牛逼你也能够提交一个)
以向Java平台增添新的API和服务。JSR已成为Java界的一个重要标准。
Bean Validation 顾名思义是对 java Bean 的校验,目前为止,Java 对 Bean 的校验有3个规范。web
Hibernate Validator
是 Bean Validation
的参考实现 . Hibernate Validator
提供了 JSR 303
规范中全部内置 constraint 的实现,除此以外还有一些附加的 constraint
。正则表达式
2.3
之后的版本 spring-boot-starter-web
已经去除了这个依赖,须要手动引入 Hibernate-validator
依赖,详细内容见官网描述 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>
springboot
项目的话直接引入<dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>6.0.17.Final</version> </dependency>
代码演示:
方法前面这个注解@Valid
是必须的,不然不生效哦。spring
@PostMapping(value = "/save2") @ResponseBody public ResultViewModel save2(@Valid @RequestBody User user){ boolean saveUser = saveUser(user); if (saveUser) { return ResultViewModelUtil.success(); } return ResultViewModelUtil.error(); }
实体类上标上须要校验的规则注解就行了。sql
//被注释的元素,值必须是一个字符串,不能为null,且调用trim()后,长度必须大于0 @NotBlank(message = "") //被注释的元素,值不能为null,但能够为"空",用于基本数据类型的非空校验上,并且被其标注的字段可使用 @size、@Max、@Min 等对字段数值进行大小的控制 @NotNull(message = "") //被注释的的元素,值不能为null,且长度必须大于0,通常用在集合类上面 @NotEmpty(message = "") //被注释的元素必须符合指定的正则表达式。 @Pattern(regexp = "", message = "") //被注释的元素的大小必须在指定的范围内。 @Size(min =, max =) //被注释的元素,值必须是一个数字,且值必须大于等于指定的最小值 @Min(value = long之内的值, message = "") //被注释的元素,值必须是一个数字,且值必须小于等于指定的最大值 @Max(value = long之内的值, message = "") //被注释的元素,值必须是一个数字,其值必须大于等于指定的最小值 @DecimalMin(value = 能够是小数, message = "") //被注释的元素,值必须是一个数字,其值必须小于等于指定的最大值 @DecimalMax(value = 能够是小数, message = "") //被注释的元素,值必须为null @Null(message = "") //被注释的元素必须是一个数字,其值必须在可接受的范围内 @Digits(integer =, fraction =) //被注释的元素,值必须为true @AssertTrue(message = "") //被注释的元素,值必须为false @AssertFalse(message = "") //被注释的元素必须是一个过去的日期 @Past(message = "") //被注释的元素必须是一个未来的日期 @Future(message = "") //被注释的元素必须是电子邮件地址 @Email(regexp = "", message = "") //被注释的元素必须在合适的范围内 @Range(min =, max =, message = "") //被注释的字符串的大小必须在指定的范围内 @Length(min =, max =, message = "")
惟一须要注意的点就是若是是级联校验的话须要在最外层加上@Valid
为何须要在校验的上一次标上@Valid
这个注解,里面的校验才会生效列?有知道的或者感兴趣的能够去看看源码给我留言哦。
而后在配置一个全局的异常捕获器就行了,因为篇幅缘由代码就不贴了,代码上传到了github
上。
校验结果:编程
Hibernate-Validator
还能够自定义注解实现。userId
(由于系统生成);修改的时候须要验证userId
,这时候可用用户到validator
的分组验证功能)springboot
的、好比使用的是Jfinal框架(这个是个国产框架大多数人能都不知道)、或者soa
调用参数校验的时候,这时候能够怎么使用列?api
,以及炒鸡简单的用法,赶忙把本身写的轮子给删除了,立马换上了这个Hibernate-Validator
框架。从新修改提交后,leader
的脸上终于露出了满意的笑容。参考
http://docs.jboss.org/hiberna...(官网中文版本贴心吧
)
https://docs.jboss.org/hibern...
https://juejin.im/post/5dd8d4...
https://www.cnblogs.com/mr-ya...