從零搭建開發腳手架Spring Boot 集成Groovy實現動態加載業務規則
本文轉載自微信公眾號「Java大廠面試官」,作者laker。轉載本文請聯系Java大廠面試官公眾號。
背景
前段時間體驗了Zuul的groovy Filter,其實現了動態熱加載Filter,可以在不重啟應用的情況下新增、修改自己的業務規則,現在我也來仿照Zuul來山寨一個,用于我們日常多變的業務規則中。
需要依賴groovy-all
- <dependency>
- <groupId>org.codehaus.groovy</groupId>
- <artifactId>groovy-all</artifactId>
- <version>2.4.12</version> 版本自己去適配哈
- </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目錄下文件。
- @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源碼,比較簡單,建議大家都去看看。





















