Springboot 使用 JSR 303 对 Controller 控制层校验及 Service 服务层 AOP 校验 使用消息资源文件对消息国际化
导包和配置
导入 jsr 303 的包、hibernate valid 的包
<dependency> <groupid>org.hibernate.validator</groupid> <artifactid>hibernate-validator</artifactid> <version>6.0.5.final</version> </dependency> <dependency> <groupid>javax.validation</groupid> <artifactid>validation-api</artifactid> <version>2.0.0.final</version> </dependency>
springboot 配置
resources/application.yml 消息资源文件国际化处理配置
spring:
messages:
basename: base,todo # 资源文件 base.properties 和 todo.properties,多个用逗号隔开
encoding: utf-8 # 必须指定解析编码,否则中文乱码
在 springboot 启动类里面配置
@springbootapplication public class application extends webmvcconfigureradapter { @value("${spring.messages.basename}") private string basename; public static void main(string[] args) { springapplication.run(application.class, args); } @bean @primary public messagesource messagesource() { resourcebundlemessagesource resourcebundlemessagesource = new resourcebundlemessagesource(); resourcebundlemessagesource.setusecodeasdefaultmessage(false); resourcebundlemessagesource.setdefaultencoding("utf-8"); // 重复定义 resourcebundlemessagesource.setbasenames(basename.split(",")); return resourcebundlemessagesource; } @bean @primary public localvalidatorfactorybean validator() { localvalidatorfactorybean validatorfactorybean = new localvalidatorfactorybean(); validatorfactorybean.setproviderclass(hibernatevalidator.class); validatorfactorybean.setvalidationmessagesource(messagesource()); return validatorfactorybean; } @override public validator getvalidator() { return validator(); } /** * 方法级别的单个参数验证开启 */ @bean public methodvalidationpostprocessor methodvalidationpostprocessor() { return new methodvalidationpostprocessor(); } }
我们对于校验参数通过不了抛出的异常进行处理,是通过统一异常捕捉。
@controlleradvice @component public class bindvalidexceptionhandler { @responsestatus(value = httpstatus.ok) @exceptionhandler(constraintviolationexception.class) public @responsebody msg handleconstraintviolationexception(constraintviolationexception e) { string messagetemplate = e.getconstraintviolations().iterator().next().getmessagetemplate(); return msg.error(messagetemplate); } @responsestatus(value = httpstatus.ok) @exceptionhandler(bindexception.class) public @responsebody msg handlebindexception(bindexception e) { bindingresult bindingresult = e.getbindingresult(); string classname = bindingresult.gettarget().getclass().getname(); fielderror next = bindingresult.getfielderrors().iterator().next(); string fieldname = next.getfield(); string defaultmessage = next.getdefaultmessage(); if (pattern.compile("illegalargumentexception: no enum").matcher(defaultmessage).find()) { matcher matcher = pattern.compile("for value '(.*?)'").matcher(defaultmessage); if (matcher.find()) { defaultmessage = "找不到枚举类型【" + matcher.group(1) + "】"; } } return msg.error(defaultmessage); } @responsestatus(value = httpstatus.ok) @exceptionhandler(validerror.class) public @responsebody msg handlevaliderror(validerror e) { return msg.error(e.getmessage()); } }
resources/base.propertie
creatorid=创建者 id 不能为小于 {value}。
modifierid=修改者 id 不能为小于 {value}。
resources/todo.properties
todo.privateid.min=私有 id 不能为小于 {value}。
在 bean 字段上使用注解,其中 group 中的 c 和 s 接口是指 controller 和 service 的叫法简称,里面分别有 insert 接口、update 接口等等,都是自定义约定的东西。
/** * 私有 id,是代表项目任务/非项目任务/风险/问题/评审待办问题等多张表的外键 */ @min(value = 1, message = "{todo.privateid.min}", groups = {c.insert.class, c.update.class, s.insert.class, s.update.class}) private long privateid; /** * 创建者id */ @min(value = 1, message = "{creatorid}", groups = {s.insert.class}) private long creatorid; controller 控制层验证 @validated @restcontroller @requestmapping("todo") public class todocontroller { @autowired private todoservice todoservice; @getmapping("getvo") public msg getvo( @min(value = 1, message = "待办 id 不能小于 1。") @requestparam(required = false, defaultvalue = "0") long id ) { return this.todoservice.getvo(id); } @postmapping("add") public msg add(@validated({c.insert.class}) todo todo) { return this.todoservice.add(todo); } }
@validated({c.insert.class}) 声明启用 bean 注解上的验证组,其他验证组不会进行验证,这样可以区别开来进行单独验证。
而像没有实体,只有一个基础数据类型的,可以进行验证,但是需要满足三个条件:
- 在启动类配置方法级别验证启用类
- 在 controller 类上注解 @validated
- 在方法参数里使用验证注解如 @min,@notnull 等等
自行验证。
service 服务层 aop 验证
validutil 工具类
需要被 springboot 扫描并注册为单例
@component public class validutil { @autowired private validator validator; public <t> set<constraintviolation<t>> validate(t object, class<?>... groups) { return validator.validate(object, groups); } public <t> set<constraintviolation<t>> validatevalue(class<t> beantype, string propertyname, object value, class<?>... groups) { return validator.validatevalue(beantype, propertyname, value, groups); } /** * 校验参数,并返回第一个错误提示 * @param t 验证的对象 * @param groups 验证的组别 * @param <t> 对象擦除前原类型 * @return 第一个错误提示 */ public <t> void validandreturnfirsterrortips(t t, class<?>... groups) { set<constraintviolation<t>> validate = validator.validate(t, groups); if (validate.size() > 0) { constraintviolation<t> next = validate.iterator().next(); string message = next.getrootbeanclass().getname() + "-" + next.getpropertypath() + "-" + next.getmessage(); throw new validerror(message); } } /** * 校验参数,并返回第一个错误提示 * @param targetclass 验证的对象的 class 类型 * @param fieldname 需要验证的名字 * @param obj 需要属性值 * @param groups 验证的组别 * @param <t> 对象擦除前原类型 * @return 第一个错误提示 */ public <t> void validandreturnfirsterrortips(class targetclass, string fieldname, object obj, class<?>... groups) { set<constraintviolation<t>> validate = validator.validatevalue(targetclass, fieldname, obj, groups); if (validate.size() > 0) { string message = targetclass.getname() + "-" + fieldname + "-" + validate.iterator().next().getmessage(); throw new validerror(message); } } }
aop 配置
主要原理是利用 aop 拦截方法执行参数,对参数获取注解。再利用工具类来验证参数,如果验证不通过,直接抛出自定义错误,自定义错误已经全局统一处理了。
@aspect @component public class validatoraop { @autowired private validutil validutil; /** * 定义拦截规则:拦截 com.servic 包下面的所有类中,有 @service 注解的方法。 */ @pointcut("execution(* com.service..*(..)) and @annotation(org.springframework.stereotype.service)") public void controllermethodpointcut() { } /** * 拦截器具体实现 */ @around("controllermethodpointcut()") // 指定拦截器规则;也可以直接把 “execution(* com.xjj.........)” 写进这里 public object interceptor(proceedingjoinpoint pjp) { methodsignature methodsignature = (methodsignature) pjp.getsignature(); method method = methodsignature.getmethod(); annotation[][] argannotations = method.getparameterannotations(); object[] args = pjp.getargs(); for (int i = 0; i < args.length; i++) { for (annotation annotation : argannotations[i]) { if (validated.class.isinstance(annotation)) { validated validated = (validated) annotation; class<?>[] groups = validated.value(); validutil.validandreturnfirsterrortips(args[i], groups); } } } try { return pjp.proceed(args); } catch (throwable throwable) { throwable.printstacktrace(); } return true; } }
验证注解 @min @notnull 使用方法
不能写在实现类上,只能在接口中使用注解
与 controller 使用方式基本一样
@validated public interface todoservice { /** * 查询 单个待办 * @param id 序号 * @return 单个待办 */ msg getvo(@min(value = 1, message = "待办 id 不能小于 1。") long id); /** * 添加数据 * @param todo 对象 */ msg add(@validated({s.insert.class}) todo todo); }
分享几个自定义验证注解
字符串判空验证
package javax.validation.constraints; import javax.validation.constraint; import javax.validation.constraintvalidator; import javax.validation.constraintvalidatorcontext; import javax.validation.payload; import java.lang.annotation.*; /** * 字符串判空验证,hibernate 自带的可能有问题,使用不了,需要重写,package 是不能变的。 */ @documented @constraint( validatedby = {notblank.notblankvalidator.class} ) @target({elementtype.field, elementtype.annotation_type, elementtype.parameter}) @retention(retentionpolicy.runtime) public @interface notblank { class<?>[] groups() default {}; string message() default "{notblank}"; class<? extends payload>[] payload() default {}; class notblankvalidator implements constraintvalidator<notblank, object> { public notblankvalidator() { } @override public void initialize(notblank constraintannotation) { } @override public boolean isvalid(object value, constraintvalidatorcontext context) { return value != null && !value.tostring().isempty(); } } }
类型判断,判断 type 是否为其中一个值,可以根据验证组自定义判断
resources/todo.properties todo.todotype.insert=新增时,待办类型只能是 非项目任务、项目任务、问题 之中一。 todo.todotype.update=修改时,待办类型只能是风险、评审待办问题 之中一。 bean /** * 待办类型0非项目任务1项目任务2问题3风险4评审待办问题 */ @todotypevalid(value = {"0", "1", "2"}, message = "{todo.todotype.insert}", groups = {c.insert.class, s.insert.class}) @todotypevalid(value = {"3", "4"}, message = "{todo.todotype.update}", groups = {c.update.class, s.update.class}) private string todotype;
自定义注解
@documented @constraint(validatedby = {todotypevalid.todotypevalidfactory.class}) @target({elementtype.field, elementtype.annotation_type, elementtype.parameter}) @retention(retentionpolicy.runtime) @repeatable(todotypevalid.list.class) public @interface todotypevalid { string message() default "请输入正确的类型"; string[] value() default {}; class<?>[] groups() default {}; class<? extends payload>[] payload() default {}; class todotypevalidfactory implements constraintvalidator<todotypevalid, string> { private string[] annotationvalue; @override public void initialize(todotypevalid todostatusvalid) { this.annotationvalue = todostatusvalid.value(); } @override public boolean isvalid(string value, constraintvalidatorcontext context) { if (arrays.aslist(annotationvalue).contains(value)) return true; return false; } } @target({elementtype.field, elementtype.annotation_type, elementtype.parameter}) @retention(retentionpolicy.runtime) @documented @interface list { todotypevalid[] value(); } }
@repeatable(todotypevalid.list.class) 是 jdk8 支持的同一注解多次特性。
根据上面的同样也可以用在枚举类上
resources/todo.properties todo.todostatus.insert=新增时,状态只能是未开始。 todo.todostatus.update=修改时,状态只能是进行中或已完成。 bean /** * 待办状态0未开始1进行中2已完成 */ @todostatusvalid(enums = {todostatus.not_started}, message = "{todo.todostatus.insert}", groups = {c.insert.class, s.insert.class}) @todostatusvalid(enums = {todostatus.processing, todostatus.completed}, message = "{todo.todostatus.update}", groups = {c.update.class, s.update.class}) private todostatus todostatus;
自定义注解
@documented @constraint(validatedby = {todostatusvalid.todostatusvalidfactory.class}) @target({elementtype.field, elementtype.annotation_type, elementtype.parameter}) @retention(retentionpolicy.runtime) @repeatable(todostatusvalid.list.class) public @interface todostatusvalid { string message() default "请输入正确的状态"; todostatus[] enums() default {}; class<?>[] groups() default {}; class<? extends payload>[] payload() default {}; class todostatusvalidfactory implements constraintvalidator<todostatusvalid, todostatus> { private todostatus[] enums; @override public void initialize(todostatusvalid todostatusvalid) { this.enums = todostatusvalid.enums(); } @override public boolean isvalid(todostatus value, constraintvalidatorcontext context) { todostatus[] values = todostatus.values(); if (enums != null && enums.length != 0) { values = enums; } if (arrays.aslist(values).contains(value)) return true; return false; } } @target({elementtype.field, elementtype.annotation_type, elementtype.parameter}) @retention(retentionpolicy.runtime) @documented @interface list { todostatusvalid[] value(); } }
总结
以上所述是小编给大家介绍的springboot 使用 jsr 303 对 controller 控制层校验及 service 服务层 aop 校验 使用消息资源文件对消息国际化,希望对大家有所帮助