背景
前段时间体验了Zuul的groovy Filter,其实现了动态热加载Filter,可以在不重启应用的情况下新增、修改自己的业务规则,现在我也来仿照Zuul来山寨一个,用于我们日常多变的业务规则中。
需要依赖groovy-all
-
org.codehaus.groovy -
groovy-all -
2.4.12 版本自己去适配哈 -
什么是 Groovy?
类似于Python,perl等灵活动态语言,不过它是运行在java平台上,也就是Groovy最后代码会被编译成class字节码文件,集成到web应用或者java应用中,groovy编写的程序其实就是特殊点的Java程序,而且java类库可以在groovy中直接使用。
Groovy 的另一个好处是,它的语法与 Java 语言的语法很相似。
使用体验
先来体验下实现后的成果
1、利用Spring Boot的CommandLineRunner注册SpringBean、GroovyBean
- 初始化加载项目中RuleFilter的Spring Bean
- 直接使用@Autowired注解配合List即可获取所有RuleFilter的子类
- 初始化Groovy动态扫描的监控间隔,目录配置
- 这里配置的是每5秒检查D:\\laker\\lakernote\\groovy目录下,新增或者修改的文件用于编译加载
- 初始化也会加载D:\\laker\\lakernote\\groovy目录下文件。
- @Component
- public class GroovyRunner implements CommandLineRunner {
- @Autowired
- List
ruleFilterList; - @Override
- public void run(String... args) throws Exception {
- // 初始化加载项目中RuleFilter的Springbean
- RuleFilterLoader.getInstance().initSpringRuleFilter(ruleFilterList);
- try {
- // 每隔多少秒,扫描目录下的groovy文件
- RuleFilterFileManager.init(5, "D:\\laker\\lakernote\\groovy");
- } catch (Exception e) {
- e.printStackTrace();
- throw new RuntimeException();
- }
- }
- }
2、项目内不变的规则以Java实现继承RuleFilter
这个就是普通的Java类,我们把不变的规则以这种方式实现。
- @Component
- public class JavaRule extends RuleFilter {
-
- @Override
- public void run(String msg) {
- System.out.println(" === Java 实现的业务规则 order = 1 , msg = " + msg + " === ");
- }
-
- @Override
- public boolean shouldRun() {
- return true;
- }
-
-
- @Override
- public int runOrder() {
- return 1;
- }
- }
3、项目内经常变动的以Groovy来实现
groovy兼容Java语法,可以直接用java语法来写。
- public class GroovyRule extends RuleFilter {
- @Override
- public void run(String msg) {
-
- System.out.println(" === Groovy 实现的业务规则 order = " + runOrder() + ", msg = " + msg + " === ");
- }
- @Override
- public boolean shouldRun() {
- return true;
- }
- @Override
- public int runOrder() {
- return 2;
- }
- }
“然后把这个xxx.java文件丢到我们监控的文件夹即可
4、在合适的位置使用RuleFilterProcessor
这里我写了个Controller用来测试动态加载规则。
- @RestController
- @RequestMapping("/groovy")
- public class GroovyController {
- @Autowired
- private RuleFilterProcessor ruleFilterProcessor;
-
- @GetMapping()
- @ApiOperation("测试groovy的动态加载")
- public void transaction(@RequestParam String msg) {
- ruleFilterProcessor.runRuleFilters(msg);
- }
- }
5、启动并验证
我分了几个场景验证如下:
1). 启动程序
浏览器访问:http://localhost:8080/groovy?msg=laker%20666
结果如下:
- === Java 实现的业务规则 order = 1 , msg = laker 666 ===
- === Groovy 实现的业务规则 order = 2 , msg = laker 666 ===
2.) 我修改GroovyRule中的runOrder(),把它改为0
“不用重启服务
浏览器访问:http://localhost:8080/groovy?msg=laker%20666
结果如下:
- === Groovy 实现的业务规则 order = 0 , msg = laker 666 ===
- === Java 实现的业务规则 order = 1 , msg = laker 666 ===
3). 我新增一个Groovy2Rule然后丢进上面指定的监控文件夹
- public class Groovy2Rule extends RuleFilter {
- @Override
- public void run(String msg) {
- System.out.println(" === Groovy 实现的业务规则 order = " + runOrder() + ", msg = " + msg + " === ");
- List
ruleFilters = RuleFilterLoader.getInstance().getFilters(); - for (RuleFilter ruleFilter : ruleFilters) {
- System.out.println(ruleFilter.getClass().getName());
- }
- }
- @Override
- public boolean shouldRun() {
- return true;
- }
-
- @Override
- public int runOrder() {
- return 3;
- }
- }
不用重启服务
浏览器访问:http://localhost:8080/groovy?msg=laker%20666
结果如下:
- === Groovy 实现的业务规则 order = 0 , msg = laker 666 ===
- === Java 实现的业务规则 order = 1 , msg = laker 666 ===
- === Groovy 实现的业务规则 order = 3, msg = laker 666 ===
- com.laker.map.moudle.groovy.javarule.GroovyRule
- com.laker.map.moudle.groovy.javarule.JavaRule
- com.laker.map.moudle.groovy.Groovy2Rule
“这里如果想调用Spring环境中的bean可以借助SpringContextUtil
实现
核心的模块如下
- RuleFilter :规则过滤器抽象类,用于扩展实现业务规则,供Java和Groovy继承。
- RuleFilterLoader :规则过滤器加载器,用于加载基于Spring的RuleFilter实现类和动态编译指定文件基于Groovy的RuleFilter实现类。
存储所有的规则过滤器并能动态加载改变的和新增的规则。
- RuleFilterFileManager : 一个独立线程轮询监听指定目录文件的变化配合RuleFilterLoader ( 规则过滤器加载器)使用。
- RuleFilterProcessor: 业务规则处理器核心入口
“这四个核心模块都是盗版Zuul的实现。
贴上部分核心代码如下:
RuleFilter.java
- public abstract class RuleFilter implements IRule, Comparable
{ -
- abstract public int runOrder();
-
- @Override
- public int compareTo(RuleFilter ruleFilter) {
- return Integer.compare(this.runOrder(), ruleFilter.runOrder());
- }
- ...
- }
RuleFilterLoader.java
- public class RuleFilterLoader {
- public boolean putFilter(File file) throws Exception {
- String sName = file.getAbsolutePath() + file.getName();
- if (filterClassLastModified.get(sName) != null && (file.lastModified() != filterClassLastModified.get(sName))) {
- LOG.debug("reloading filter " + sName);
- filterRegistry.remove(sName);
- }
- RuleFilter filter = filterRegistry.get(sName);
- if (filter == null) {
- Class clazz = compile(file);
- if (!Modifier.isAbstract(clazz.getModifiers())) {
- filter = (RuleFilter) clazz.newInstance();
- filterRegistry.put(file.getAbsolutePath() + file.getName(), filter);
- ruleFilters.clear();
- filterClassLastModified.put(sName, file.lastModified());
- return true;
- }
- }
- return false;
- }
- public List
getFilters() { - if (CollUtil.isNotEmpty(ruleFilters)) {
- return ruleFilters;
- }
- ruleFilters.addAll(springRuleFilterList);
- ruleFilters.addAll(this.filterRegistry.values());
- Collections.sort(ruleFilters);
- return ruleFilters;
- }
- private Class compile(File file) throws IOException {
- GroovyClassLoader loader = getGroovyClassLoader();
- Class groovyClass = loader.parseClass(file);
- return groovyClass;
- }
- GroovyClassLoader getGroovyClassLoader() {
- return new GroovyClassLoader();
- }
- ...
- }
RuleFilterFileManager.java
- public class RuleFilterFileManager {
- public static void init(int pollingIntervalSeconds, String... directories) {
- if (INSTANCE == null) INSTANCE = new RuleFilterFileManager();
-
- INSTANCE.aDirectories = directories;
- INSTANCE.pollingIntervalSeconds = pollingIntervalSeconds;
- INSTANCE.manageFiles();
- INSTANCE.startPoller();
- }
- void startPoller() {
- poller = new Thread("GroovyRuleFilterFileManagerPoller") {
- @Override
- public void run() {
- while (bRunning) {
- try {
- sleep(pollingIntervalSeconds * 1000);
- manageFiles();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
- };
- poller.setDaemon(true);
- poller.start();
- }
- void processGroovyFiles(List
aFiles) throws Exception, InstantiationException, IllegalAccessException { -
- for (File file : aFiles) {
- RuleFilterLoader.getInstance().putFilter(file);
- }
- }
- void manageFiles() throws Exception, IllegalAccessException, InstantiationException {
- List
aFiles = getFiles(); - processGroovyFiles(aFiles);
- }
- ...
- }
RuleFilterProcessor.java
- @Component
- public class RuleFilterProcessor {
- public void runRuleFilters(String msg) {
- List
list = RuleFilterLoader.getInstance().getFilters(); - if (list != null) {
- list.forEach(ruleFilter -> {
- if (ruleFilter.shouldRun()) {
- ruleFilter.run(msg);
- }
- });
- }
- }
- }
总结
可以看到使用起来是相当的方便,仅依赖groovy-all,整体代码结构简单。
性能和稳定性未测试,但是这基本就是翻版的Zuul,Zuul都在使用了,应该没什么问题。
参考:
参考了Zuul源码,比较简单,建议大家都去看看。