当前位置: 首页 > news >正文

借老系统重构机会我写了个groovy规则引擎

公司老系统的重构计划早就有了,为了对Java硬编码的各种校验规则进行重构,特地参考了相关技术,最终选择了groovy进行了系统的学习,并编写了一个即插即用的轻量级规则引擎。

文章目录

    • 项目背景
    • 技术选型
    • groovy的性能
    • groovy脚本执行线程安全问题
    • 统一Java运行环境
    • dsl风格的规则声明
    • 弱类型的便利
    • 校验规则的维护
    • 基于事实推断的规则引擎实现

项目背景

笔者上班负责的是一个很老的某业务平台的申报系统。并发量随不高,但是申报分了很多的阶段,且每个阶段的申报项比较多,表单的字段也很多,校验规则也比较杂。项目前期有多个团队先后负责,代码风格不同,且业务规则的校验实现都是堆砌代码的方式,导致后期的代码维护比较麻烦。一个类文件通常是5千行代码以上,一个校验方法也至少几百行。

因为业务操作主要是数据的保存和申报,而校验的代码占到很大的比例,为此笔者的重构,很自然就想到把各种杂七杂八的校验代码给抽取出来,以规则脚本的形式进行更好的维护。

技术选型

抽取校验规则的技术选型,考虑了基于rete算法的drools、基于mveleasyRule以及jvm体系的groovy。因为只是把系统中的校验规则抽取出来,做成脚本的形式维护,传统的规则引擎也基本用不上,因为它们更多的使用场景是基于多实体的事实对象的分析、推断,且比较重量级,且有一定的学习成本。而groovy本身就是一门动态的脚本语言,很轻量级,可以在java环境无缝衔接的调用,语法非常的简洁易学,它的dsl特性还能编写出可读性更高的脚本声明形式。

groovy的性能

groovy非常的轻量级,编译速度很快,新版本对编译的内容做了缓存,而且gc方面也做了优化。笔者在这方面做了一些实验:通过spring boot的定时任务每隔一秒钟用30个线程同时跑groovy规则脚本,并对应用的jvm参数-XX:MetaspaceSize-XX:MaxMetaspaceSize都做了限制。用groovy老版本和新版本做了下对比,发现老版本执行很快就oom了:

在这里插入图片描述

而新版本稳定发挥,执行的性能用visualVM监控了下:

在这里插入图片描述

笔者还测试了下规则执行的耗时,第一次访问时需要耗时几百毫秒,后续就很快了,说明重复执行一个规则脚本,会缓存一些编译的类,提高性能:

在这里插入图片描述

groovy脚本执行线程安全问题

groovy脚本在java环境中的执行有多种方式,具体可参考这篇技术博客Integrating Groovy into Java Applications。我们采用GroovyShell的方式来执行规则脚本,因为规则是设计为dsl声明方式,而不是类和方法的执行方式,用bingding作为执行的上下文。binding本身是线程不安全的,在多个请求线程用同一个GroovyShell实例执行时,binding上绑定的变量是共享的,为此在实现上需要进行线程同步处理。而我们的做法是每次都创建一个新的GroovyShell来避免这个问题,这种方式没有性能问题,因为groovy本身就有对编译脚本相应的类缓存机制,且gc方面做了优化。

统一Java运行环境

在植入groovy脚本代码到当前的java系统时,可以设置加载groovy的类加载器为当前java系统的类加载器,这样就可以直接使用当前环境中的类和类库了。比如可以在groovy脚本中直接用获取spring bean的工具类:

在这里插入图片描述

而外部需要传进来参与规则执行的对象可以通过设置为binding的变量。

groovy脚本中抛出异常,和在java系统中抛出的异常类型信息一样,不会被包装,方便系统原有的异常处理机制统一处理:

在这里插入图片描述

dsl风格的规则声明

借助于groovy闭包和binding可以很轻松的实现dsl风格的声明:

在这里插入图片描述

这里的condition以及逻辑运算闭包还可以进一步优化成,不满足逻辑条件就不再执行,比如上面截图中的第一个condition满足条件,则后续的闭包不再解析执行,都可以自行控制实现。

弱类型的便利

原先在java代码中编写的各种校验规则,需要严格的静态类型检查还需要避免在运行时的空指针问题,而groovy天生就有类似于javascript的弱类型特性,且变量属性的访问,也不会有空指针问题,比如Integer类型的变量为null,使用<操作符;或者值为null的字符串变量调用equals

在弱语言中有一个特别需要注意的问题,变量可能存在全局污染,我们只要遵循这样的原则:在一个groovy脚本的头部声明变量时一定加上def,因为不用类型声明的变量会作为全局变量,可以在shell工具运行的多个脚本之间传递使用,因此要避免这一点。以下是笔者的练习:

在这里插入图片描述

校验规则的维护

对于简单的参数校验:非空、长度、正则等可以编写统一的groovy函数或者闭包,这样字段校验的脚本会变得非常简单,而对于复杂的关联性的校验只需要用java或者groovy脚本,在我们定义的condition中自由发挥即可。

在这里插入图片描述
运行结果:
在这里插入图片描述

另外通过这样的方式可以把规则成块的组织起来,包含了规则头部的声明块(变量的声明和初始化)、各个规则rule闭包声明的规则执行块。有了这样的结构后,可以把规则的定义从文件搬到数据库中,然后再通过web端的编辑权限进行修改维护(用支持groovy语法高亮的web编辑器),这样就可以做到不重启服务器的情况下,来动态更新业务规则,非常的省心。

基于事实推断的规则引擎实现

基于groovy强大的闭包语法特性,我们可以很轻松的实现笛卡尔积匹配方式的小型规则引擎,比如:

在这里插入图片描述

相关文章:

借老系统重构机会我写了个groovy规则引擎

公司老系统的重构计划早就有了&#xff0c;为了对Java硬编码的各种校验规则进行重构&#xff0c;特地参考了相关技术&#xff0c;最终选择了groovy进行了系统的学习&#xff0c;并编写了一个即插即用的轻量级规则引擎。 文章目录 项目背景技术选型groovy的性能groovy脚本执行线…...

C#利用ffmpeg借助NVIDIA GPU实现实时RTSP硬解码+硬编码录制MP4

目录 说明 效果 项目 代码 下载 说明 利用周杰的开源项目 Sdcb.FFmpeg 项目地址&#xff1a;https://github.com/sdcb/Sdcb.FFmpeg/ 代码实现参考&#xff1a;https://github.com/sdcb/ffmpeg-muxing-video-demo 效果 C#利用ffmpeg借助NVIDIA GPU实现实时RTSP硬解码硬…...

第4章 汇编语言和汇编软件

第4章 汇编语言和汇编软件 该章主要介绍了汇编语言和汇编语言编译器的安装和使用。 汇编语言程序 该小节主要介绍了为什么要有汇编语言和汇编语言程序的一些基础写法。 书中有提到CPU有不同的架构&#xff0c;汇编语言有不同的风格&#xff0c;那么不同的CPU架构和不同的汇…...

网络安全在2024好入行吗?

前言 024年的今天&#xff0c;慎重进入网安行业吧&#xff0c;目前来说信息安全方向的就业对于学历的容忍度比软件开发要大得多&#xff0c;还有很多高中被挖过来的大佬。 理由很简单&#xff0c;目前来说&#xff0c;信息安全的圈子人少&#xff0c;985、211院校很多都才建立…...

C++练习

要求 1. 函数命名清晰 使用描述性的命名&#xff0c;准确反映函数的功能。例如&#xff0c;使用 CalculateSum() 而不是 sum()。避免使用缩写或模糊不清的名字&#xff0c;确保变量和函数名有明确的含义。 2. 参数传递 根据需要选择按值传递、按引用传递或按指针传递。如果…...

3. GIS后端工程师岗位职责、技术要求和常见面试题

本系列文章目录&#xff1a; 1. GIS开发工程师岗位职责、技术要求和常见面试题 2. GIS数据工程师岗位职责、技术要求和常见面试题 3. GIS后端工程师岗位职责、技术要求和常见面试题 4. GIS前端工程师岗位职责、技术要求和常见面试题 5. GIS工程师岗位职责、技术要求和常见面试…...

Linux学习笔记(4)----Debian压力测试方法

使用命令行终端压力测试需要两个实用工具&#xff1a;s-tui和stress sudo apt install s-tui stress 安装完成后&#xff0c;在终端中启动 s-tui实用工具&#xff1a; s-tui 执行后如下图&#xff1a; 你可以使用鼠标或键盘箭头键浏览菜单&#xff0c;然后点击“压力选项(Str…...

xml详解

一、XML是什么 XML&#xff08;可扩展标记语言&#xff09;是一种非常常用的数据存储和交换格式。 二、XML 的基本结构 声明 XML 文件通常以 XML 声明开始&#xff0c;例如&#xff1a;<?xml version"1.0" encoding"UTF-8"?>。它指定了 XML 的版…...

C140 杨辉三角

C140 杨辉三角 题目题解(94)讨论(102)排行面经 new 简单 通过率&#xff1a;29.57% 时间限制&#xff1a;1秒 空间限制&#xff1a;256M 知识点C工程师牛客 校招时部分企业笔试将禁止编程题跳出页面&#xff0c;为提前适应&#xff0c;练习时请使用在线自测&#xff0c;…...

C++字符串操作中的陷阱

休对故人思故国&#xff0c;且将新火试新茶。诗酒趁年华。 ——《望江南超然台作》【宋】苏轼 目录 正文&#xff1a; 首先我们要明白出现问题的原因: 1. 缓冲区溢出 2. 错误的字符串声明方式 3. 缺乏对NULL指针的检查 解决方案&#xff1a; 下期预告&#xff1a;C字符串…...

最值求解 | 管理类联考数学专项

日期内容2024.9.5新建2024.9.6曦曦求最值完结 实数求最值至少至多抽屉原理工程问题线性规划一次性绝对值求最值 参考&#xff1a; b站跟着曦曦老师玩转【最值】...

C++_继承详解

继承的概念 继承(inheritance)机制是面向对象程序设计使代码可以复用的重要的手段&#xff0c;它允许程序员在保持原有类特性的基础上进行扩展&#xff0c;增加功能。继承呈现了面向对象程序设计的层次结构&#xff0c;之前我们接触的复用都是函数复用&#xff0c;今天我们所讨…...

区块链开发解决方案有哪些

区块链开发解决方案概述 区块链开发解决方案旨在利用区块链技术构建和开发新型应用和系统&#xff0c;以解决各种业务问题和提升效率。区块链作为一种基于密码学的分布式账本技术&#xff0c;通过将交易和数据记录在不可篡改的区块中&#xff0c;并通过网络中的多个节点共同验…...

Express与SQLite集成教程:轻松实现数据库操作

Express使用SQLite的教程可以大致分为以下几个步骤。以下是一个详细的指南&#xff0c;帮助你在Express项目中集成SQLite数据库。 1. 安装必要的库 首先&#xff0c;你需要在你的Express项目中安装sqlite3库。打开终端或命令提示符&#xff0c;切换到你的项目目录&#xff0c…...

Transforms的常见用法

文章目录 一、封装函数与普通函数的用法区别二、Image.open()打开图片的格式三、ToTensor打开图片格式四、ToTensor使用五、Normalize归一化使用六、Resize的使用七、Compose - Resize 使用八、RandomCrop&#xff08;&#xff09; 随机裁剪用法 一、封装函数与普通函数的用法区…...

js 创建 React 项目

起因(目的): js 很久没写了。 react js 之前粗略看过, 最近又需要用到, 继续学习&#xff0c; 记录 积累。 1. 新建 React 项目 的几种方法。 官方建议使用 next 来创建 React 项目&#xff0c; 但是我觉得太复杂了。以后再看看. npx create-next-applatest # !!! 不建议使…...

WPF 中常用 `Transform` 类的介绍、使用示例和适用场景

WPF 中常用 Transform 类的介绍、使用示例和适用场景 使用场景解释代码示例示例代码解释 Transform 类描述使用示例适用场景TranslateTransform用于沿 X 轴或 Y 轴平移&#xff08;移动&#xff09;元素。xml <TranslateTransform X"50" Y"100" />移…...

ElasticSearch-DSL

查询所有 match_all 分页查询 from size深分页查询 Scroll指定字段排序 sort返回指定字段_sourcematch 短语查询 match_phrase多字段查询 multi_matchquery_string simple_query_string 关键词查询 Term 结构化搜索 前缀查询 prefix通配符查询 wildcard范围查询 range多 id 查…...

Learn ComputeShader 07 Post Processing

这次我们将使用计算机着色器对图像进行后处理。 要进行后处理需要将渲染图像从cpu传递给gpu&#xff0c;并在gpu对图像进行处理然后传回cpu。 首先创建一个后处理基类BasePP 首先声明需要用到的属性。 using System.Collections; using System.Collections.Generic; using …...

初始QT!

作业&#xff1a;了解QT文件夹初始代码的意义 QT core gui #QT工程所需得类库 core是核心库 gui图形化界面相关库类 greaterThan(QT_MAJOR_VERSION, 4): QT widgets #版本超过4.0会加上widgetsCONFIG c11 #该编辑器支持c11后的版本 # The following define makes you…...

阿里云ACP云计算备考笔记 (5)——弹性伸缩

目录 第一章 概述 第二章 弹性伸缩简介 1、弹性伸缩 2、垂直伸缩 3、优势 4、应用场景 ① 无规律的业务量波动 ② 有规律的业务量波动 ③ 无明显业务量波动 ④ 混合型业务 ⑤ 消息通知 ⑥ 生命周期挂钩 ⑦ 自定义方式 ⑧ 滚的升级 5、使用限制 第三章 主要定义 …...

前端倒计时误差!

提示:记录工作中遇到的需求及解决办法 文章目录 前言一、误差从何而来?二、五大解决方案1. 动态校准法(基础版)2. Web Worker 计时3. 服务器时间同步4. Performance API 高精度计时5. 页面可见性API优化三、生产环境最佳实践四、终极解决方案架构前言 前几天听说公司某个项…...

ESP32读取DHT11温湿度数据

芯片&#xff1a;ESP32 环境&#xff1a;Arduino 一、安装DHT11传感器库 红框的库&#xff0c;别安装错了 二、代码 注意&#xff0c;DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...

Python爬虫(一):爬虫伪装

一、网站防爬机制概述 在当今互联网环境中&#xff0c;具有一定规模或盈利性质的网站几乎都实施了各种防爬措施。这些措施主要分为两大类&#xff1a; 身份验证机制&#xff1a;直接将未经授权的爬虫阻挡在外反爬技术体系&#xff1a;通过各种技术手段增加爬虫获取数据的难度…...

鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/

使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题&#xff1a;docker pull 失败 网络不同&#xff0c;需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...

AI编程--插件对比分析:CodeRider、GitHub Copilot及其他

AI编程插件对比分析&#xff1a;CodeRider、GitHub Copilot及其他 随着人工智能技术的快速发展&#xff0c;AI编程插件已成为提升开发者生产力的重要工具。CodeRider和GitHub Copilot作为市场上的领先者&#xff0c;分别以其独特的特性和生态系统吸引了大量开发者。本文将从功…...

Android Bitmap治理全解析:从加载优化到泄漏防控的全生命周期管理

引言 Bitmap&#xff08;位图&#xff09;是Android应用内存占用的“头号杀手”。一张1080P&#xff08;1920x1080&#xff09;的图片以ARGB_8888格式加载时&#xff0c;内存占用高达8MB&#xff08;192010804字节&#xff09;。据统计&#xff0c;超过60%的应用OOM崩溃与Bitm…...

【Java学习笔记】BigInteger 和 BigDecimal 类

BigInteger 和 BigDecimal 类 二者共有的常见方法 方法功能add加subtract减multiply乘divide除 注意点&#xff1a;传参类型必须是类对象 一、BigInteger 1. 作用&#xff1a;适合保存比较大的整型数 2. 使用说明 创建BigInteger对象 传入字符串 3. 代码示例 import j…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

脑机新手指南(七):OpenBCI_GUI:从环境搭建到数据可视化(上)

一、OpenBCI_GUI 项目概述 &#xff08;一&#xff09;项目背景与目标 OpenBCI 是一个开源的脑电信号采集硬件平台&#xff0c;其配套的 OpenBCI_GUI 则是专为该硬件设计的图形化界面工具。对于研究人员、开发者和学生而言&#xff0c;首次接触 OpenBCI 设备时&#xff0c;往…...