本文转载自微信公众号「Java大厂面试官」,从零成作者laker。搭建转载本文请联系Java大厂面试官公众号。脚手架St集
前段时间体验了Zuul的现动groovy Filter,其实现了动态热加载Filter,态加可以在不重启应用的载业则情况下新增、修改自己的从零成业务规则,现在我也来仿照Zuul来山寨一个,搭建用于我们日常多变的脚手架St集业务规则中。
需要依赖groovy-all
<dependency> <groupId>org.codehaus.groovy</groupId> <artifactId>groovy-all</artifactId> <version>2.4.12</version> 版本自己去适配哈 </dependency>类似于Python,perl等灵活动态语言,态加不过它是载业则运行在java平台上,也就是从零成Groovy最后代码会被编译成class字节码文件,集成到web应用或者java应用中,搭建groovy编写的脚手架St集程序其实就是特殊点的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<RuleFilter> 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 { /** * 具体规则执行 * @param msg */ @Override public void run(String msg) { System.out.println(" === Java 实现的业务规则 order = 1 , msg = " + msg + " === "); } /** * 该规则是否被执行 * @return */ @Override public boolean shouldRun() { return true; } /** * 该规则执行的顺序 * @return */ @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<RuleFilter> 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<RuleFilter> { 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<RuleFilter> 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<File> aFiles) throws Exception, InstantiationException, IllegalAccessException { for (File file : aFiles) { RuleFilterLoader.getInstance().putFilter(file); } } void manageFiles() throws Exception, IllegalAccessException, InstantiationException { List<File> aFiles = getFiles(); processGroovyFiles(aFiles); } ... }RuleFilterProcessor.java
@Component public class RuleFilterProcessor { public void runRuleFilters(String msg) { List<RuleFilter> list = RuleFilterLoader.getInstance().getFilters(); if (list != null) { list.forEach(ruleFilter -> { if (ruleFilter.shouldRun()) { ruleFilter.run(msg); } }); } } }可以看到使用起来是相当的方便,仅依赖groovy-all,整体代码结构简单。
性能和稳定性未测试,但是这基本就是翻版的Zuul,Zuul都在使用了,应该没什么问题。
参考:
参考了Zuul源码,比较简单,建议大家都去看看。