博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Spring AOP+反射实现自定义动态配置校验规则,让校验规则飞起来
阅读量:5847 次
发布时间:2019-06-19

本文共 11092 字,大约阅读时间需要 36 分钟。

原文作者:弥诺R

原文地址:
转载声明:转载请注明原文地址,注意版权维护,谢谢!

场景小计

之前项目都是使用hibernate-validator来校验参数,但是实际上会出现一些小问题,就是校验规则都是通过注解的方式来完成,这样如果项目上线了,这个参数校验规则就没办法修改,如果出现校验规则问题,就必须修改后重新紧急上线(之前因为手机号码格式校验就出现过这个问题,因为新的号段不支持)。为了适应动态配置校验规则,在新起的项目我们就不再使用hibernate-validator校验规则,而是自己写个小功能来实现。

实现思路

1、实现这种动态配置,就要能随时修改规则,并应用到实际业务逻辑中,直接在代码中写是不行的,因此这里采用数据库记录的方式是一个不错的选择;

2、需要对所有controller进入的参数校验,不能每个方法中加调用逻辑,这个必须写一个公共的方法,使用Spring AOP做切面切入所有的controller方法;
3、服务的请求方式,使用这种方式,最方便的就是使用post请求,入参后,参数都在一个类中封装,拿到类,使用反射,拿出参数的参数名和参数值。
基本都是以上思路,切面切入controller类中所有方法,拿到请求Dto类,利用反射技术拿出所有的参数名和参数值,从数据库中获取当前Dto类下所有参数的校验规则,依次对参数进行校验。

项目构建

项目结构

项目结构

aspect:切面(DynamicCheckAspect)和校验引擎(DynamicCheckEngine),切面中反射出字段,查询校验规则,然后将字段交给检验引擎完成校验动作;
controller:接口入口,DynamicCheckController提供校验测试;
dao:dao下有两个目录,分别是mapper和model,用于存放Mapper接口类和查询结果数据封装类;
dto:请求参数封装类(DynamicCheckReqDto),响应参数封装类(DynamicCheckRespDto);
exception:自定义异常类存放位置;
service:业务逻辑代码;
ApplicationStart:Spring Boot启动入口;
resource:存放mapper.xml文件和application.properties配置以及日志配置logback.xml。

数据库准备

数据库需要建三张表,校验模板表(t_template_info),校验模板规则表(t_template_rule_info),实体规则关联表(t_bean_rule_info),只说表的基本字段,需要SQL可以到码云或者git上现在原代码,项目中有datasql.sql文件中很详细,还包含初始数据。

t_template_info:

template_id varchar(16) NOT NULL COMMENT '模板编号',

template_desc varchar(64) DEFAULT NULL COMMENT '模板描述',
template_status tinyint(4) NOT NULL DEFAULT '1' COMMENT '模板状态(0:不使用,1:使用)',
check_level int(11) NOT NULL COMMENT '检查优先级'

t_template_rule_info:

rule_id varchar(16) NOT NULL COMMENT '规则编号',

template_id varchar(16) NOT NULL COMMENT '模板编号',
rule_express varchar(128) NOT NULL COMMENT '规则表达式',
toast_msg varchar(128) NOT NULL COMMENT '提示信息',
rule_status tinyint(4) NOT NULL DEFAULT '1' COMMENT '规则状态'

t_bean_rule_info:

bean_id varchar(32) NOT NULL COMMENT '实体类编号',

rule_id varchar(16) NOT NULL COMMENT '规则编号',
field_name varchar(32) NOT NULL COMMENT '字段名',
field_desc varchar(128) DEFAULT NULL COMMENT '字段描述',
check_status tinyint(4) DEFAULT '1' COMMENT '是否校验'

上手代码

#####pom.xml配置

org.springframework.boot
spring-boot-starter-parent
1.5.6.RELEASE
1.8
1.16.10
1.1.0
1.3.0
5.1.35
3.5
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-logging
com.alibaba
druid-spring-boot-starter
${druid.version}
org.springframework.boot
spring-boot-starter-aop
org.mybatis.spring.boot
mybatis-spring-boot-starter
${mybatis.version}
mysql
mysql-connector-java
${mysql.version}
org.projectlombok
lombok
${lombok.version}
provided
org.apache.commons
commons-lang3
${commons-lang3.version}
org.apache.maven.plugins
maven-compiler-plugin
3.1
${java.version}
${java.version}
UTF-8
DynamicCheckAspect核心代码
@Component@Slf4j@Aspectpublic class DynamicCheckAspect {    @Autowired    private DynamicCheckRuleService dynamicCheckRuleService;    @Autowired    private DynamicCheckEngine paramCheckEngine;    /**     * 定义切点     */    @Pointcut("execution(* com.minuor.dynamic.check.controller.*.*(..))")    public void pointcut() {    }    /**     * 定义环切     */    @Around("pointcut()")    public void check(ProceedingJoinPoint joinPoint) {        try {            // 查询获取请求参数封装类(dto)的类名            MethodSignature signature = (MethodSignature) joinPoint.getSignature();            Class
[] parameterTypes = signature.getMethod().getParameterTypes(); String beanName = null; if (parameterTypes != null && parameterTypes.length > 0) { beanName = parameterTypes[0].getSimpleName(); } //查询当前beanName下字段的所有校验规则 List
modelList = null; if (StringUtils.isNotBlank(beanName)) { modelList = dynamicCheckRuleService.queryRuleByBeanName(beanName); } if (modelList != null && !modelList.isEmpty()) { //规则分类(根据字段名分类) Map
> ruleMap = new HashMap<>(); for (DynamicCheckRuleModel ruleModel : modelList) { List
fieldRules = ruleMap.get(ruleModel.getFieldName()); if (fieldRules == null) fieldRules = new ArrayList<>(); fieldRules.add(ruleModel); ruleMap.put(ruleModel.getFieldName(), fieldRules); } //获取请求参数 Object[] args = joinPoint.getArgs(); if (args != null && args.length > 0) { Object reqDto = args[0]; Field[] fields = reqDto.getClass().getDeclaredFields(); if (fields != null && fields.length > 0) { for (Field field : fields) { String fieldName = field.getName(); boolean isCheck = ruleMap.containsKey(fieldName); if (!isCheck) continue; field.setAccessible(true); List
paramRules = ruleMap.get(fieldName); for (DynamicCheckRuleModel ruleModel : ruleMap.get(fieldName)) { ruleModel.setFieldValue(field.get(reqDto)); } //校验 paramCheckEngine.checkParamter(paramRules); } } } } joinPoint.proceed(); } catch (Exception e) { throw new DynamicCheckException(e.getMessage()); } catch (Throwable throwable) { throw new DynamicCheckException(throwable.getMessage()); } }}

这里首先是获取Dto的名称,然后到数据库中查询校验规则列表,如果没有,就不需要校验,中间的校验逻辑就无需再走。

DynamicCheckEngine核心代码
@Slf4j@Componentpublic class DynamicCheckEngine {    /**     * 综合校验分发器     *     * @param paramRules     */    public void checkParamter(List
paramRules) throws Exception { paramRules.sort(Comparator.comparing(DynamicCheckRuleModel::getCheckLevel)); for (DynamicCheckRuleModel ruleModel : paramRules) { Method method = this.getClass().getMethod(ruleModel.getTemplateId(), DynamicCheckRuleModel.class); Object result = method.invoke(this, ruleModel); if (result != null) { throw new DynamicCheckException((String) result); } } } /** * 检查非空 * 模板编号:notBlank */ public String notBlank(DynamicCheckRuleModel roleModel) throws DynamicCheckException { Object fieldValue = roleModel.getFieldValue(); if (fieldValue == null) { return generateToastMsg(roleModel); } else { if ((fieldValue instanceof String) && StringUtils.isBlank((String) fieldValue)) { return generateToastMsg(roleModel); } } return null; } /** * 检查非空 * 模板编号:notNull */ public String notNull(DynamicCheckRuleModel roleModel) throws DynamicCheckException { if (roleModel.getFieldValue() == null) return generateToastMsg(roleModel); return null; } /** * 检查长度最大值 * 模板编号:lengthMax */ public String lengthMax(DynamicCheckRuleModel roleModel) throws DynamicCheckException { String fieldValue = (String) roleModel.getFieldValue(); if (fieldValue.length() > Integer.valueOf(roleModel.getRuleExpress().trim())) { return generateToastMsg(roleModel); } return null; } /** * 检查长度最小值 * 模板编号:lengthMin */ public String lengthMin(DynamicCheckRuleModel roleModel) throws DynamicCheckException { String fieldValue = (String) roleModel.getFieldValue(); if (fieldValue.length() < Integer.valueOf(roleModel.getRuleExpress().trim())) { return generateToastMsg(roleModel); } return null; } /** * 检查值最大值 * 模板编号:valueMax */ public String valueMax(DynamicCheckRuleModel roleModel) throws DynamicCheckException { Double fieldValue = Double.valueOf(roleModel.getFieldValue().toString()); if (fieldValue > Double.valueOf(roleModel.getRuleExpress())) { return generateToastMsg(roleModel); } return null; } /** * 检查值最小值 * 模板编号:valueMin */ public String valueMin(DynamicCheckRuleModel roleModel) throws DynamicCheckException { Double fieldValue = Double.valueOf(roleModel.getFieldValue().toString()); if (fieldValue < Double.valueOf(roleModel.getRuleExpress())) { return generateToastMsg(roleModel); } return null; } /** * 正则格式校验 * 模板编号:regex */ public String regex(DynamicCheckRuleModel roleModel) throws DynamicCheckException { String value = (String) roleModel.getFieldValue(); if (!Pattern.matches(roleModel.getRuleExpress(), value)) { return generateToastMsg(roleModel); } return null; } /** * 构建结果信息 */ private String generateToastMsg(DynamicCheckRuleModel roleModel) throws DynamicCheckException { String[] element = new String[]{StringUtils.isNotBlank(roleModel.getFieldDesc()) ? roleModel.getFieldDesc() : roleModel.getFieldName(), roleModel.getRuleExpress()}; String toast = roleModel.getToastMsg(); int index = 0; while (index < element.length) { String replace = toast.replace("{" + index + "}", element[index] + ""); if (toast.equals(replace)) break; toast = replace; index++; } return toast; }}

在校验方法checkParameter中,并不是去if else取判断校验模板名称,而是使用反射的方式执行方法,当然这里执行的校验的方法名要和模板名称相同,如校验非空,模板名是notBlank,那么对应的检验方法名就是notBlank。

总结

1、这里没有列出项目中的所有代码,感觉没有必要,太冗余,主要思路和核心代码足矣,其他的代码下面会提供git和码云上的下载链接地址;

2、这里校验及基于post请求,如果你所在的项目中必须有get请求,那么就需要重新筹划一下这个校验规则如何定义,如get采用方法名,post采用Dto名称;
3、这里代码作为demo展示,记得使用根据自己项目做优化;
4、这里面校验的异常都是往外抛出的,实际是不会把异常抛给用户,可以在controller中做异常的统一过滤封装。

项目代码

码云:

gitHub:

转载于:https://blog.51cto.com/simplelife/2107253

你可能感兴趣的文章
Ember.js 3.9.0-beta.3 发布,JavaScript Web 应用开发框架
查看>>
python标准库00 学习准备
查看>>
4.2. PHP crypt()
查看>>
commonservice-config配置服务搭建
查看>>
连接池的意义及阿里Druid
查看>>
ComponentOne 2019V1火热来袭!全面支持 Visual Studio 2019——亮点之WinForm篇
查看>>
Python递归函数与匿名函数
查看>>
loadrunner安装运行一步一步来(多图)
查看>>
git请求报错 401
查看>>
监控工具htop的安装及使用
查看>>
Nodejs使用图灵机器人获取笑话
查看>>
Spring 任务调度 简单的,使用Schedule
查看>>
SQL 2005删除作业计划出错(DELETE语句与 REFERENCE约束"FK_subplan_job_id"冲突。)的解决...
查看>>
【Touch&input 】支持多个游戏控制器(18)
查看>>
我的友情链接
查看>>
SQL语句学习
查看>>
What is Cluster Aware Updating in Windows Server 2012?
查看>>
进老男孩的自我介绍和决心书
查看>>
线上Linux服务器运维安全策略经验分享
查看>>
Android一些问题的解决方案
查看>>