文档章节

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

O龙猫O
 O龙猫O
发布于 2017/01/21 19:14
字数 1804
阅读 8135
收藏 45

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

  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天生就是兼容的。

先贴上数据模型

    def student = ['id'            : 'E9527',
                   'name'          : '于小小',
                   'gender'        : 'F',
                   'kind'          : 'EMBA',
                   'className'     : '重庆理工大学MBA三年级四班',
                   'grade'         : 4,
                   'birth'         : '1989/03/02',
                   'address'       : '重庆市巴南区红光大道',
                   'salary'        : 50000,
                   'createTime'    : '2016/01/21 11:31:00',
                   'courses'       : [
                           ['id': 'GJC', name: '公共基础', 'classHour': 32],
                           ['id': 'ZXW', name: '组织行为学', 'classHour': 40],
                           ['id': 'TJX', name: '统计学', 'classHour': 20],
                           ['id': 'CJR', name: '财务与金融', 'classHour': 60],
                           ['id': 'JJF', name: '经济法', 'classHour': 48],
                           ['id': 'JSJ', name: '计算机技能', 'classHour': 16],
                   ],
                   'mainInstructor': ['id': 'TE007', 'name': '杨大大'],
                   'extra'         : [
                           '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'],
                           'progressState': 'finished'
                   ]


    ];

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

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

    def 规则集 = [
            '基础信息':
                    [
                            '是否90后': '',
                            '性别'   : '',
                            '注册天数':0,
                            '地区':''
                    ],

            '评级'  : [
                    '学费档次' : '',
                    '收入档次':''
            ],
            '学习情况': [
                    '总学时':0,
                    '迟到次数':0,
                    '出勤次数':0,
                    '单门课程最长学时':0,
            ]
    ]

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

整体groovy代码

import java.text.SimpleDateFormat
import java.text.ParseException

class RuntimeContext {
    //以下是英文部分变量,一般通过程序自动装载得到,用于从数据库或其他持久层加载业务数据
    def collegeName = "EMBA业余大学"
    def tuitionFee = 80000
    def startDate = '2016/02/01', finishDate = '2016/09/01'
    def today = "2017/01/21"
    def student = ['id'            : 'E9527',
                   'name'          : '于小小',
                   'gender'        : 'F',
                   'kind'          : 'EMBA',
                   'className'     : '重庆理工大学MBA三年级四班',
                   'grade'         : 4,
                   'birth'         : '1989/03/02',
                   'address'       : '重庆市巴南区红光大道',
                   'salary'        : 50000,
                   'createTime'    : '2016/01/21 11:31:00',
                   'courses'       : [
                           ['id': 'GJC', name: '公共基础', 'classHour': 32],
                           ['id': 'ZXW', name: '组织行为学', 'classHour': 40],
                           ['id': 'TJX', name: '统计学', 'classHour': 20],
                           ['id': 'CJR', name: '财务与金融', 'classHour': 60],
                           ['id': 'JJF', name: '经济法', 'classHour': 48],
                           ['id': 'JSJ', name: '计算机技能', 'classHour': 16],
                   ],
                   'mainInstructor': ['id': 'TE007', 'name': '杨大大'],
                   'extra'         : [
                           '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'],
                           'progressState': 'finished'
                   ]


    ];
    def toDate(_date){
        try{
            return (new SimpleDateFormat("yyyy/MM/dd hh:mm:ss")).parse(_date);
        }catch(ParseException e){
            return (new SimpleDateFormat("yyyy/MM/dd")).parse(_date);
        }

    }


    def 规则集 = [
            '基础信息':
                    [
                            '是否90后': student.birth >= '1990/01/01'?'是':'否',
                            '性别'   : student.gender == 'M' ? '男' : '女',
                            '注册天数':toDate(today) - toDate(student.createTime),
                            '地区':{
                                def province = student.address.subSequence(0,3);
                                //做一个区域和省级行政单位的映射关系
                                def areaMapping = ['西南':['重庆市','四川省','贵州省','云南省'],'江浙沪':['上海市','江苏省','浙江省'],'京津冀':['北京市','天津市','河北省']];
                                //进行筛选
                                def entry = areaMapping.find {key,value ->
                                    value.contains(province);
                                }
                                entry.key
                            }()//最后这个"()"一定要,否则闭包不执行
                    ],

            '评级'  : [
                    //使用三元表达式,大于6W --> A ,4-6W --> B,2-4W -->C,2W以下 --> D
                    '学费档次' : tuitionFee>=60000?'A':(tuitionFee>=40000&&tuitionFee<60000?'B':(tuitionFee>=20000&&tuitionFee<40000)?'C':'D'),
                    '收入档次':{
                        if(student.salary>=20000) '高收入'
                        else if(student.salary>=10000) '中等收入'
                        else if(student.salary>=5000) '一般收入'
                        else '低收入'
                    }() //最后这个"()"一定要,否则闭包不执行
            ],
            '学习情况': [
                    '总学时':{
                        int totalHourse = 0;
                        student.courses.each { totalHourse += it.classHour}
                        totalHourse
                    }(),
                    '迟到次数':{
                        int _count = 0
                        student.extra.attendanceLog.each {
                            Date _date = toDate(it)
                            _count += (_date.hours>=9&&_date.seconds>=1)?1:0
                        }
                        _count
                    }(),
                    '出勤次数':student.extra.attendanceLog.size(),
                    '单门课程最长学时':{
                        int maxHour = 0;
                        student.courses.each { maxHour = Math.max(maxHour,it.classHour)}
                        student.courses.find({it.classHour == maxHour}) //默认最后一句为返回值
                    }(),
            ]
    ]

}

// ========运行验证========

//创建运行时对象
def rctx = new RuntimeContext()
//输出结果看
//----简单的规则取值
println "基础信息.是否90后  =  ${rctx.规则集.基础信息.是否90后}"
println "基础信息.性别      = ${rctx.规则集.基础信息['性别']}"
println "基础信息.注册天数      = ${rctx.规则集.基础信息.注册天数}"
println "评级.学费档次      = ${rctx.规则集.评级.学费档次}"
//----使用闭包的方式取值
println "基础信息.地区      = ${rctx.规则集.基础信息.地区}"
println "评级.收入档次      = ${rctx.规则集.评级.收入档次}"
println "学习情况.总学时      = ${rctx.规则集.学习情况.总学时}"
println "学习情况.迟到次数      = ${rctx.规则集.学习情况.迟到次数}"
println "学习情况.出勤次数      = ${rctx.规则集.学习情况.出勤次数}"
println "学习情况.单门课程最长学时      = ${rctx.规则集.学习情况.单门课程最长学时}"

运行结果如下图:

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

希望此文能够抛砖引玉。

 

© 著作权归作者所有

O龙猫O

O龙猫O

粉丝 124
博文 39
码字总数 21143
作品 1
苏州
部门经理
私信 提问
加载中

评论(12)

剑神卓不凡
剑神卓不凡
很好,我最近也在研究groovy
j
john_guo
groovy 在动态配置、规则集方面使用实在是很方便,用过都知道!
尼古拉希
尼古拉希
不过,我个人觉得,使用drools,多少有点换个地方写程序的意思
--------
楼主和我情况差不多,这句话深表赞同。
O龙猫O
O龙猫O 博主

引用来自“追峰路”的评论

我们用drools,可以写脚本规则,也可以用决策表(一种批量规则excel),还是很方便的。支持规则的版本管理,在线热加载。价格,促销,积分的一些计算规则都可以放进来。
drools是个好东西,不过,一方面我手里的关于drools怎么样进行应用集成的案例少,另一方面,需要引入的第三方包比较重。再有,groovy我只是用来解决一些非常轻量的规则可配置化问题。groovy也可以和activiti流程引擎无缝集成 。因此暂时没有使用drools,如果你的好的drools在你的业务场景使用的一些案例能够分享出来,那就更好了。
追峰路
追峰路
我们用drools,可以写脚本规则,也可以用决策表(一种批量规则excel),还是很方便的。支持规则的版本管理,在线热加载。价格,促销,积分的一些计算规则都可以放进来。
_hadooper
_hadooper

引用来自“_hadooper”的评论

恭喜开始入坑,脚本确实方便,但是神坑

引用来自“田舍先生”的评论

坑是哪里都有的,掉下去后,能爬上来。同样的坑,下回不再掉下去就好。

引用来自“_hadooper”的评论

脚本后期的维护会很蛋疼,同时因为没有工程化的东西,导致原先的业务脚本基本很少去变动

引用来自“田舍先生”的评论

脚本,我对它一般定位在一些规则相对较为简单,变动相对较为频繁的功能上。引入版本管理以及测试机制。可以解决你所说的问题。毕竟业务规则的一点小小调整,客户需要的是快速响应。目前正是由于这样的一个业务驱动下,才来研究这么一个东西。
版本机制,测试机制,试运行功能,曾经我们都有这些功能。现在我们就在做去脚本化,有的时候为了快,就全在脚本里面写了,后续维护的人就gg了。
O龙猫O
O龙猫O 博主

引用来自“_hadooper”的评论

恭喜开始入坑,脚本确实方便,但是神坑

引用来自“田舍先生”的评论

坑是哪里都有的,掉下去后,能爬上来。同样的坑,下回不再掉下去就好。

引用来自“_hadooper”的评论

脚本后期的维护会很蛋疼,同时因为没有工程化的东西,导致原先的业务脚本基本很少去变动
脚本,我对它一般定位在一些规则相对较为简单,变动相对较为频繁的功能上。引入版本管理以及测试机制。可以解决你所说的问题。毕竟业务规则的一点小小调整,客户需要的是快速响应。目前正是由于这样的一个业务驱动下,才来研究这么一个东西。
_hadooper
_hadooper

引用来自“_hadooper”的评论

恭喜开始入坑,脚本确实方便,但是神坑

引用来自“田舍先生”的评论

坑是哪里都有的,掉下去后,能爬上来。同样的坑,下回不再掉下去就好。
脚本后期的维护会很蛋疼,同时因为没有工程化的东西,导致原先的业务脚本基本很少去变动
O龙猫O
O龙猫O 博主

引用来自“linapex”的评论

终于看到使用groovy的同道中人~
👌
linapex
linapex
终于看到使用groovy的同道中人~
java脚本引擎的设计原理浅析

本人在阿里巴巴长期担任和负责规则引擎、流程引擎相关的技术开发,另外还负责开发和维护开源项目: https://github.com/alibaba/QLExpress QLExpress是一个脚本引擎工具,类似Groovy,JRuby...

baobao_pandora
2018/08/01
0
0
Groovy 能不能只允许代码中引入一部分java类库?

因为项目中想把groovy脚本放开给前台用户,让他们自定义一部分逻辑/规则,但又担心用户会输入 System.exit 之类的破坏性代码。所以想问 Groovy 能不能只允许代码中引入一部分java类库?或者是...

子木007
2013/03/28
844
6
spring 使用 groovy 的 utf-8 问题

spring 中使用动态 groovy bean 没有可设置 groovy 脚本文件的编码,是使用系统默认的编码。工作中,服务器上的系统默认编码不是 utf-8,郁闷,写的 groovy 是用 utf-8 编码。程序一发布到服...

小编辑
2010/02/27
650
0
《Groovy极简教程》第1章 Groovy简介

《Groovy极简教程》第1章 Groovy简介 Groovy: 绝妙的; 流行的; 最佳状态的。 A multi-faceted language for the Java platform. (JVM平台上的多面体语言。) 官网文档:http://www.groovy-lan...

程序员诗人
2017/04/16
0
0
openjweb快速开发平台中使用Groovy动态语言作为规则引擎解决方案

由于在工作流设计中需要为条件分支设定条件表达式,所以需要可以动态执行的语言脚本作为工作流的规则引擎.工作流条件分支纯粹使用sql脚本肯定是不行的,经过试验,groovy的脚本可以返回为java接...

迷途d书童
2012/03/09
622
0

没有更多内容

加载失败,请刷新页面

加载更多

使用zabbix自带的模板监控MySQL自带

一、安装zabbix server 略 二、安装zabbix agent 略 三、给主机套自带的模板 略 四、创建授权用户 mysql> grant all on *.* to 'zabbix'@'localhost' identified by 'musingtec2019';Quer......

雁南飞丶
9分钟前
4
0
notepad++快捷键

notepad++也情有独钟,最近发现了一个快捷键,就是选中单词,ctrl+shift+enter。不过现在想知道一个快捷键,假设有三行代码,选中后一般按TAB就可以三行全部缩进. Notepad++绝对是windows下进...

zhengzhixiang
31分钟前
5
0
区块链背景是什么?区块链的意义是什么?

一、前言 区块链技术的首次也是最著名的应用是比特币,一个在2009年1月初正式上线运行的去中心化数字货币应用,他的创始人叫中本聪,但目前大家并不知道此人的真实身份。 比特币不同于现代国...

daxiongdi
36分钟前
4
0
在Bash中循环浏览文件内容

如何使用Bash遍历文本文件的每一行? 使用此脚本: echo "Start!"for p in (peptides.txt)do echo "${p}"done 我在屏幕上得到以下输出: Start!./runPep.sh: line 3: syntax error......

技术盛宴
39分钟前
8
0
史上最强IP正则表达式

port ([0-9]|[1-9]\\d{1,3}|[1-5]\\d{4}|6[0-4]\\d{4}|65[0-4]\\d{2}|655[0-2]\\d|6553[0-5]) ipv4 ^((25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.){3}(25[0-5]|2[0-4]\\d|[01]?\\d\\d?)$ ipv4+mask......

蜗牛伊
42分钟前
10
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部