从零搭建开发脚手架Spring Boot 集成Groovy实现动态加载业务规则

开发 前端
前段时间体验了Zuul的groovy Filter,其实现了动态热加载Filter,可以在不重启应用的情况下新增、修改自己的业务规则,现在我也来仿照Zuul来山寨一个,用于我们日常多变的业务规则中。

[[420920]]

本文转载自微信公众号「Java大厂面试官」,作者laker。转载本文请联系Java大厂面试官公众号。

背景

前段时间体验了Zuul的groovy Filter,其实现了动态热加载Filter,可以在不重启应用的情况下新增、修改自己的业务规则,现在我也来仿照Zuul来山寨一个,用于我们日常多变的业务规则中。

需要依赖groovy-all

  1. <dependency> 
  2.      <groupId>org.codehaus.groovy</groupId> 
  3.      <artifactId>groovy-all</artifactId> 
  4.      <version>2.4.12</version> 版本自己去适配哈 
  5.  </dependency> 

什么是 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目录下文件。
  1. @Component 
  2. public class GroovyRunner implements CommandLineRunner { 
  3.     @Autowired 
  4.     List<RuleFilter> ruleFilterList; 
  5.     @Override 
  6.     public void run(String... args) throws Exception { 
  7.         // 初始化加载项目中RuleFilter的Springbean 
  8.         RuleFilterLoader.getInstance().initSpringRuleFilter(ruleFilterList); 
  9.         try { 
  10.             // 每隔多少秒,扫描目录下的groovy文件 
  11.             RuleFilterFileManager.init(5, "D:\\laker\\lakernote\\groovy"); 
  12.         } catch (Exception e) { 
  13.             e.printStackTrace(); 
  14.             throw new RuntimeException(); 
  15.         } 
  16.     } 

2、项目内不变的规则以Java实现继承RuleFilter

这个就是普通的Java类,我们把不变的规则以这种方式实现。

  1. @Component 
  2. public class JavaRule extends RuleFilter { 
  3.     /** 
  4.      * 具体规则执行 
  5.      * @param msg 
  6.      */ 
  7.     @Override 
  8.     public void run(String msg) { 
  9.         System.out.println(" === Java 实现的业务规则 order = 1 , msg = " + msg + " === "); 
  10.     } 
  11.     /** 
  12.      * 该规则是否被执行 
  13.      * @return 
  14.      */ 
  15.     @Override 
  16.     public boolean shouldRun() { 
  17.         return true
  18.     } 
  19.  
  20.     /** 
  21.      * 该规则执行的顺序 
  22.      * @return 
  23.      */ 
  24.     @Override 
  25.     public int runOrder() { 
  26.         return 1; 
  27.     } 

3、项目内经常变动的以Groovy来实现

groovy兼容Java语法,可以直接用java语法来写。

  1. public class GroovyRule extends RuleFilter { 
  2.     @Override 
  3.     public void run(String msg) { 
  4.  
  5.         System.out.println(" === Groovy 实现的业务规则 order = " + runOrder() + ", msg = " + msg + " === "); 
  6.     } 
  7.     @Override 
  8.     public boolean shouldRun() { 
  9.         return true
  10.     } 
  11.     @Override 
  12.     public int runOrder() { 
  13.         return 2; 
  14.     } 

“然后把这个xxx.java文件丢到我们监控的文件夹即可

4、在合适的位置使用RuleFilterProcessor

这里我写了个Controller用来测试动态加载规则。

  1. @RestController 
  2. @RequestMapping("/groovy"
  3. public class GroovyController { 
  4.     @Autowired 
  5.     private RuleFilterProcessor ruleFilterProcessor; 
  6.  
  7.     @GetMapping() 
  8.     @ApiOperation("测试groovy的动态加载"
  9.     public void transaction(@RequestParam String msg) { 
  10.         ruleFilterProcessor.runRuleFilters(msg); 
  11.     } 

5、启动并验证

我分了几个场景验证如下:

1). 启动程序

浏览器访问:http://localhost:8080/groovy?msg=laker%20666

结果如下:

  1. === Java 实现的业务规则 order = 1 , msg = laker 666 ===  
  2. === Groovy 实现的业务规则 order = 2 , msg = laker 666 ===  

2.) 我修改GroovyRule中的runOrder(),把它改为0

“不用重启服务

浏览器访问:http://localhost:8080/groovy?msg=laker%20666

结果如下:

  1. === Groovy 实现的业务规则 order = 0 , msg = laker 666 ===  
  2. === Java 实现的业务规则 order = 1 , msg = laker 666 ===  

3). 我新增一个Groovy2Rule然后丢进上面指定的监控文件夹

  1. public class Groovy2Rule extends RuleFilter { 
  2.     @Override 
  3.     public void run(String msg) { 
  4.         System.out.println(" === Groovy 实现的业务规则 order = " + runOrder() + ", msg = " + msg + " === "); 
  5.         List<RuleFilter> ruleFilters = RuleFilterLoader.getInstance().getFilters(); 
  6.         for (RuleFilter ruleFilter : ruleFilters) { 
  7.             System.out.println(ruleFilter.getClass().getName()); 
  8.         } 
  9.     } 
  10.     @Override 
  11.     public boolean shouldRun() { 
  12.         return true
  13.     } 
  14.  
  15.     @Override 
  16.     public int runOrder() { 
  17.         return 3; 
  18.     } 

不用重启服务

浏览器访问:http://localhost:8080/groovy?msg=laker%20666

结果如下:

  1.  === Groovy 实现的业务规则 order = 0 , msg = laker 666 ===  
  2.  === Java 实现的业务规则 order = 1 , msg = laker 666 ===  
  3.  === Groovy 实现的业务规则 order = 3, msg = laker 666 ===  
  4. com.laker.map.moudle.groovy.javarule.GroovyRule 
  5. com.laker.map.moudle.groovy.javarule.JavaRule 
  6. com.laker.map.moudle.groovy.Groovy2Rule 

“这里如果想调用Spring环境中的bean可以借助SpringContextUtil

实现

核心的模块如下

  • RuleFilter :规则过滤器抽象类,用于扩展实现业务规则,供Java和Groovy继承。
  • RuleFilterLoader :规则过滤器加载器,用于加载基于Spring的RuleFilter实现类和动态编译指定文件基于Groovy的RuleFilter实现类。

存储所有的规则过滤器并能动态加载改变的和新增的规则。

  • RuleFilterFileManager : 一个独立线程轮询监听指定目录文件的变化配合RuleFilterLoader ( 规则过滤器加载器)使用。
  • RuleFilterProcessor: 业务规则处理器核心入口

“这四个核心模块都是盗版Zuul的实现。

贴上部分核心代码如下:

RuleFilter.java

  1. public abstract class RuleFilter implements IRule, Comparable<RuleFilter> { 
  2.  
  3.     abstract public int runOrder(); 
  4.  
  5.     @Override 
  6.     public int compareTo(RuleFilter ruleFilter) { 
  7.         return Integer.compare(this.runOrder(), ruleFilter.runOrder()); 
  8.     } 
  9.     ... 

RuleFilterLoader.java

  1. public class RuleFilterLoader { 
  2.     public boolean putFilter(File file) throws Exception { 
  3.         String sName = file.getAbsolutePath() + file.getName(); 
  4.         if (filterClassLastModified.get(sName) != null && (file.lastModified() != filterClassLastModified.get(sName))) { 
  5.             LOG.debug("reloading filter " + sName); 
  6.             filterRegistry.remove(sName); 
  7.         } 
  8.         RuleFilter filter = filterRegistry.get(sName); 
  9.         if (filter == null) { 
  10.             Class clazz = compile(file); 
  11.             if (!Modifier.isAbstract(clazz.getModifiers())) { 
  12.                 filter = (RuleFilter) clazz.newInstance(); 
  13.                 filterRegistry.put(file.getAbsolutePath() + file.getName(), filter); 
  14.                 ruleFilters.clear(); 
  15.                 filterClassLastModified.put(sName, file.lastModified()); 
  16.                 return true
  17.             } 
  18.         } 
  19.         return false
  20.     } 
  21.     public List<RuleFilter> getFilters() { 
  22.         if (CollUtil.isNotEmpty(ruleFilters)) { 
  23.             return ruleFilters; 
  24.         } 
  25.         ruleFilters.addAll(springRuleFilterList); 
  26.         ruleFilters.addAll(this.filterRegistry.values()); 
  27.         Collections.sort(ruleFilters); 
  28.         return ruleFilters; 
  29.     } 
  30.     private Class compile(File file) throws IOException { 
  31.         GroovyClassLoader loader = getGroovyClassLoader(); 
  32.         Class groovyClass = loader.parseClass(file); 
  33.         return groovyClass; 
  34.     } 
  35.     GroovyClassLoader getGroovyClassLoader() { 
  36.         return new GroovyClassLoader(); 
  37.     } 
  38.     ... 

RuleFilterFileManager.java

  1. public class RuleFilterFileManager { 
  2.     public static void init(int pollingIntervalSeconds, String... directories)  { 
  3.         if (INSTANCE == null) INSTANCE = new RuleFilterFileManager(); 
  4.  
  5.         INSTANCE.aDirectories = directories; 
  6.         INSTANCE.pollingIntervalSeconds = pollingIntervalSeconds; 
  7.         INSTANCE.manageFiles(); 
  8.         INSTANCE.startPoller(); 
  9.     } 
  10.     void startPoller() { 
  11.         poller = new Thread("GroovyRuleFilterFileManagerPoller") { 
  12.             @Override 
  13.             public void run() { 
  14.                 while (bRunning) { 
  15.                     try { 
  16.                         sleep(pollingIntervalSeconds * 1000); 
  17.                         manageFiles(); 
  18.                     } catch (Exception e) { 
  19.                         e.printStackTrace(); 
  20.                     } 
  21.                 } 
  22.             } 
  23.         }; 
  24.         poller.setDaemon(true); 
  25.         poller.start(); 
  26.     } 
  27.    void processGroovyFiles(List<File> aFiles) throws Exception, InstantiationException, IllegalAccessException { 
  28.  
  29.         for (File file : aFiles) { 
  30.             RuleFilterLoader.getInstance().putFilter(file); 
  31.         } 
  32.     } 
  33.     void manageFiles() throws Exception, IllegalAccessException, InstantiationException { 
  34.         List<File> aFiles = getFiles(); 
  35.         processGroovyFiles(aFiles); 
  36.     } 
  37.     ... 

RuleFilterProcessor.java

  1. @Component 
  2. public class RuleFilterProcessor { 
  3.     public void runRuleFilters(String msg) { 
  4.         List<RuleFilter> list = RuleFilterLoader.getInstance().getFilters(); 
  5.         if (list != null) { 
  6.             list.forEach(ruleFilter -> { 
  7.                 if (ruleFilter.shouldRun()) { 
  8.                     ruleFilter.run(msg); 
  9.                 } 
  10.             }); 
  11.         } 
  12.     } 

总结

可以看到使用起来是相当的方便,仅依赖groovy-all,整体代码结构简单。

性能和稳定性未测试,但是这基本就是翻版的Zuul,Zuul都在使用了,应该没什么问题。

参考:

参考了Zuul源码,比较简单,建议大家都去看看。

 

责任编辑:武晓燕 来源: Java大厂面试官
相关推荐

2021-04-28 16:10:48

开发脚手架 Spring

2021-03-09 17:11:09

数据库脚手架开发

2020-08-19 08:55:47

Redis缓存数据库

2021-07-13 18:42:38

Spring Boot脚手架开发

2021-04-13 14:47:53

认证授权Java

2021-05-13 17:02:38

MDC脚手架日志

2021-03-11 14:16:47

Spring Boo开发脚手架

2021-07-29 18:49:49

Spring开发脚手架

2021-04-20 19:24:16

脚手架 Java微信

2021-06-02 17:58:49

脚手架 幂等性前端

2021-02-19 22:43:50

开发脚手架Controller

2020-06-29 11:35:02

Spring BootJava脚手架

2016-08-10 14:59:41

前端Javascript工具

2022-04-24 11:33:47

代码管理工程

2023-11-21 17:36:04

OpenFeignSentinel

2014-08-15 09:36:06

2021-01-07 05:34:07

脚手架JDK缓存

2021-05-21 05:22:52

脚手架工具项目

2018-08-30 16:08:37

Node.js脚手架工具

2018-06-11 14:39:57

前端脚手架工具node.js
点赞
收藏

51CTO技术栈公众号