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

Wesley13
• 阅读 1475

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

  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内嵌Groovy脚本引擎进行业务规则剥离(一)

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

希望此文能够抛砖引玉。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
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
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
5个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Karen110 Karen110
3年前
​一篇文章总结一下Python库中关于时间的常见操作
前言本次来总结一下关于Python时间的相关操作,有一个有趣的问题。如果你的业务用不到时间相关的操作,你的业务基本上会一直用不到。但是如果你的业务一旦用到了时间操作,你就会发现,淦,到处都是时间操作。。。所以思来想去,还是总结一下吧,本次会采用类型注解方式。time包importtime时间戳从1970年1月1日00:00:00标准时区诞生到现在
Wesley13 Wesley13
3年前
Java日期时间API系列31
  时间戳是指格林威治时间1970年01月01日00时00分00秒起至现在的总毫秒数,是所有时间的基础,其他时间可以通过时间戳转换得到。Java中本来已经有相关获取时间戳的方法,Java8后增加新的类Instant等专用于处理时间戳问题。 1获取时间戳的方法和性能对比1.1获取时间戳方法Java8以前
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Easter79 Easter79
3年前
Spring面试题总结
1、Spring是什么?Spring是一个轻量级的IOC和AOP容器框架。是为Java应用程序提供基础性服务的一套框架,目的是用于简化企业应用程序的开发,它使得开发者只需要关心业务需求。常见的配置方式有三种:基于XML的配置、基于注解的配置、基于java的配置。主要由以下几个模块组成:SpringCore:核心类库,提供IOC服务;
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Wesley13 Wesley13
3年前
Java内嵌Groovy脚本引擎进行业务规则剥离(四)
前三篇文章已从groovy脚本的基本使用到java解析,json对象结合作了一基础铺垫。这篇文章是本系列的最后一篇文章。在第二篇文章中,说到用groovy编写的规则脚本,然后采用文件字符串的形式加载到java中,解析并运行。在我的应用场景中,每一条规则就是一个业务指标项。每一个指标项能够单独维护,而不是把所有指标项统一放到一个文件中。那么,我需
规则引擎调研及初步使用 | 京东云技术团队
生产过程中,线上的业务规则内嵌在系统的各处代码中,每次策略的调整都需要更新线上系统,进行从需求设计编码测试上线这种长周期的流程,满足不了业务规则的快速变化以及低成本的更新试错迭代。因此需要有一种解决方案将商业决策逻辑和应用开发者的技术决策分离开,在系统运行时能去更新管理业务规则。