Java内嵌Groovy脚本引擎进行业务规则剥离(一)

一些常见商业应用程序或企业应用,大多都会遇上业务规则在一定的条件下,允许进行一些灵活的配置,以满足业务变化的需要。 解决的方式大致有以下几个方面:

  1. 最为传统的方式是java程序直接写死提供几个可调节的参数配置然后封装成为独立的业务模块组件,在增加参数或简单调整规则后,重新调上线。
  2. 最为彻底的解决方式,引入商业化规则引擎,如iLog,国产的“旗正规则引擎”等。
  3. 使用开源解决方案,典型的drools规则引擎。不过,我个人觉得,使用drools,多少有点换个地方写程序的意思,中国化支持比较弱。(PS:鄙人还没有正式使用过这款规则引擎,简单的做了几个案例,觉得不太适合我的业务场景,因此暂时放弃。希望有高手给予纠正)

受限于项目预算限制(国内的交付型项目,大家都懂的),引入商业化规则引擎几乎不能考虑,直接使用java实现相对独立的业务组件,在规则的维护上,并不太方便。使用开源drools,总是觉得这东西外国人用应该很不错,中国化的应用,还没有找到相对好一些的方案。

使用动态脚本引擎,是一个不错的方案。在JSR223规则中,已经对java中集成脚本引擎有了规范。目前较为常见的是在java中动态解析javascript,groovy。曾经使用过javascript作为脚本引擎,总是觉得他做得还不够,规则配置的灵活程度,还达不到自己的预期。试验了一段时间的groovy之后,各方面相对比较符合自己的预期。另外在activiti流程引擎中,内置支持groovy作为脚本引擎。

下面,我将举一个案例(MBA培训)为原形进行案例说明。如果对groovy不熟悉的同学,请自行学习groovy。

给几个参考:

http://www.groovy-lang.org/documentation.html#languagespecification(英文不好的同学不用担心,直接看它的案例代码就可以了,作为了一个英文四级没过的我,表示基本都能看懂)

http://www.jianshu.com/p/777cc61a6202

http://blog.csdn.net/a253664942/article/details/51182619

groovy是运行在JVM之上的,因此,和java天生就是兼容的。

先贴上数据模型

1 def student = ['id' : 'E9527', 2 'name' : '于小小', 3 'gender' : 'F', 4 'kind' : 'EMBA', 5 'className' : '重庆理工大学MBA三年级四班', 6 'grade' : 4, 7 'birth' : '1989/03/02', 8 'address' : '重庆市巴南区红光大道', 9 'salary' : 50000, 10 'createTime' : '2016/01/21 11:31:00', 11 'courses' : [ 12 ['id': 'GJC', name: '公共基础', 'classHour': 32], 13 ['id': 'ZXW', name: '组织行为学', 'classHour': 40], 14 ['id': 'TJX', name: '统计学', 'classHour': 20], 15 ['id': 'CJR', name: '财务与金融', 'classHour': 60], 16 ['id': 'JJF', name: '经济法', 'classHour': 48], 17 ['id': 'JSJ', name: '计算机技能', 'classHour': 16], 18 ], 19 'mainInstructor': ['id': 'TE007', 'name': '杨大大'], 20 'extra' : [ 21 'attendanceLog': ['2016/02/01 09:00:02', '2016/02/03 08:50:03', '2016/02/15 09:12:34', '2016/04/01 07:30:11'], 22 'progressState': 'finished' 23 ] 24 25 26 ];

每个属性名称以及字段,我想不需要解释,单单从数据上看,也能看懂95%以上。

使用这个数据模型,我需要计算出另一个业务对象模型(BOM)

1 def 规则集 = [ 2 '基础信息': 3 [ 4 '是否90后': '', 5 '性别' : '', 6 '注册天数':0, 7 '地区':'' 8 ], 9 10 '评级' : [ 11 '学费档次' : '', 12 '收入档次':'' 13 ], 14 '学习情况': [ 15 '总学时':0, 16 '迟到次数':0, 17 '出勤次数':0, 18 '单门课程最长学时':0, 19 ] 20 ]

这样的一个业务模型,对于业务规则而言,可以进行一个统一的管理维护,可视化程度较好。由于是动态脚本因此可以动态拼装,这样的模型可以使用关系数据库统一维护,提供较好的GUI界面进行统一管理。

整体groovy代码

1import java.text.SimpleDateFormat 2import java.text.ParseException 3 4class RuntimeContext { 5 //以下是英文部分变量,一般通过程序自动装载得到,用于从数据库或其他持久层加载业务数据 6 def collegeName = "EMBA业余大学" 7 def tuitionFee = 80000 8 def startDate = '2016/02/01', finishDate = '2016/09/01' 9 def today = "2017/01/21" 10 def student = ['id' : 'E9527', 11 'name' : '于小小', 12 'gender' : 'F', 13 'kind' : 'EMBA', 14 'className' : '重庆理工大学MBA三年级四班', 15 'grade' : 4, 16 'birth' : '1989/03/02', 17 'address' : '重庆市巴南区红光大道', 18 'salary' : 50000, 19 'createTime' : '2016/01/21 11:31:00', 20 'courses' : [ 21 ['id': 'GJC', name: '公共基础', 'classHour': 32], 22 ['id': 'ZXW', name: '组织行为学', 'classHour': 40], 23 ['id': 'TJX', name: '统计学', 'classHour': 20], 24 ['id': 'CJR', name: '财务与金融', 'classHour': 60], 25 ['id': 'JJF', name: '经济法', 'classHour': 48], 26 ['id': 'JSJ', name: '计算机技能', 'classHour': 16], 27 ], 28 'mainInstructor': ['id': 'TE007', 'name': '杨大大'], 29 'extra' : [ 30 'attendanceLog': ['2016/02/01 09:00:02', '2016/02/03 08:50:03', '2016/02/15 09:12:34', '2016/04/01 07:30:11'], 31 'progressState': 'finished' 32 ] 33 34 35 ]; 36 def toDate(_date){ 37 try{ 38 return (new SimpleDateFormat("yyyy/MM/dd hh:mm:ss")).parse(_date); 39 }catch(ParseException e){ 40 return (new SimpleDateFormat("yyyy/MM/dd")).parse(_date); 41 } 42 43 } 44 45 46 def 规则集 = [ 47 '基础信息': 48 [ 49 '是否90后': student.birth >= '1990/01/01'?'是':'否', 50 '性别' : student.gender == 'M' ? '男' : '女', 51 '注册天数':toDate(today) - toDate(student.createTime), 52 '地区':{ 53 def province = student.address.subSequence(0,3); 54 //做一个区域和省级行政单位的映射关系 55 def areaMapping = ['西南':['重庆市','四川省','贵州省','云南省'],'江浙沪':['上海市','江苏省','浙江省'],'京津冀':['北京市','天津市','河北省']]; 56 //进行筛选 57 def entry = areaMapping.find {key,value -> 58 value.contains(province); 59 } 60 entry.key 61 }()//最后这个"()"一定要,否则闭包不执行 62 ], 63 64 '评级' : [ 65 //使用三元表达式,大于6W --> A ,4-6W --> B,2-4W -->C,2W以下 --> D 66 '学费档次' : tuitionFee>=60000?'A':(tuitionFee>=40000&&tuitionFee<60000?'B':(tuitionFee>=20000&&tuitionFee<40000)?'C':'D'), 67 '收入档次':{ 68 if(student.salary>=20000) '高收入' 69 else if(student.salary>=10000) '中等收入' 70 else if(student.salary>=5000) '一般收入' 71 else '低收入' 72 }() //最后这个"()"一定要,否则闭包不执行 73 ], 74 '学习情况': [ 75 '总学时':{ 76 int totalHourse = 0; 77 student.courses.each { totalHourse += it.classHour} 78 totalHourse 79 }(), 80 '迟到次数':{ 81 int _count = 0 82 student.extra.attendanceLog.each { 83 Date _date = toDate(it) 84 _count += (_date.hours>=9&&_date.seconds>=1)?1:0 85 } 86 _count 87 }(), 88 '出勤次数':student.extra.attendanceLog.size(), 89 '单门课程最长学时':{ 90 int maxHour = 0; 91 student.courses.each { maxHour = Math.max(maxHour,it.classHour)} 92 student.courses.find({it.classHour == maxHour}) //默认最后一句为返回值 93 }(), 94 ] 95 ] 96 97} 98 99// ========运行验证======== 100 101//创建运行时对象 102def rctx = new RuntimeContext() 103//输出结果看 104//----简单的规则取值 105println "基础信息.是否90后 = ${rctx.规则集.基础信息.是否90后}" 106println "基础信息.性别 = ${rctx.规则集.基础信息['性别']}" 107println "基础信息.注册天数 = ${rctx.规则集.基础信息.注册天数}" 108println "评级.学费档次 = ${rctx.规则集.评级.学费档次}" 109//----使用闭包的方式取值 110println "基础信息.地区 = ${rctx.规则集.基础信息.地区}" 111println "评级.收入档次 = ${rctx.规则集.评级.收入档次}" 112println "学习情况.总学时 = ${rctx.规则集.学习情况.总学时}" 113println "学习情况.迟到次数 = ${rctx.规则集.学习情况.迟到次数}" 114println "学习情况.出勤次数 = ${rctx.规则集.学习情况.出勤次数}" 115println "学习情况.单门课程最长学时 = ${rctx.规则集.学习情况.单门课程最长学时}"

运行结果如下图:

下一篇文章,我将实现这段脚本的动态配置,并且嵌入到java中。

希望此文能够抛砖引玉。

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

​一篇文章总结一下Python库中关于时间的常见操作

前言本次来总结一下关于Python时间的相关操作,有一个有趣的问题。如果你的业务用不到时间相关的操作,你的业务基本上会一直用不到。但是如果你的业务一旦用到了时间操作,你就会发现,淦,到处都是时间操作。。。所以思来想去,还是总结一下吧,本次会采用类型注解方式。time包importtime时间戳从1970年1月1日00:00:00标准时区诞生到现在

Twitter的分布式自增ID算法snowflake (Java版)

概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移