從零搭建開發(fā)腳手架Spring Boot 集成Groovy實現(xiàn)動態(tài)加載業(yè)務(wù)規(guī)則
本文轉(zhuǎn)載自微信公眾號「Java大廠面試官」,作者laker。轉(zhuǎn)載本文請聯(lián)系Java大廠面試官公眾號。
背景
前段時間體驗了Zuul的groovy Filter,其實現(xiàn)了動態(tài)熱加載Filter,可以在不重啟應(yīng)用的情況下新增、修改自己的業(yè)務(wù)規(guī)則,現(xiàn)在我也來仿照Zuul來山寨一個,用于我們?nèi)粘6嘧兊臉I(yè)務(wù)規(guī)則中。
需要依賴groovy-all
- <dependency>
- <groupId>org.codehaus.groovy</groupId>
- <artifactId>groovy-all</artifactId>
- <version>2.4.12</version> 版本自己去適配哈
- </dependency>
什么是 Groovy?
類似于Python,perl等靈活動態(tài)語言,不過它是運行在java平臺上,也就是Groovy最后代碼會被編譯成class字節(jié)碼文件,集成到web應(yīng)用或者java應(yīng)用中,groovy編寫的程序其實就是特殊點的Java程序,而且java類庫可以在groovy中直接使用。
Groovy 的另一個好處是,它的語法與 Java 語言的語法很相似。
使用體驗
先來體驗下實現(xiàn)后的成果
1、利用Spring Boot的CommandLineRunner注冊SpringBean、GroovyBean
- 初始化加載項目中RuleFilter的Spring Bean
- 直接使用@Autowired注解配合List即可獲取所有RuleFilter的子類
- 初始化Groovy動態(tài)掃描的監(jiān)控間隔,目錄配置
- 這里配置的是每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、項目內(nèi)不變的規(guī)則以Java實現(xiàn)繼承RuleFilter
這個就是普通的Java類,我們把不變的規(guī)則以這種方式實現(xiàn)。
- @Component
- public class JavaRule extends RuleFilter {
- /**
- * 具體規(guī)則執(zhí)行
- * @param msg
- */
- @Override
- public void run(String msg) {
- System.out.println(" === Java 實現(xiàn)的業(yè)務(wù)規(guī)則 order = 1 , msg = " + msg + " === ");
- }
- /**
- * 該規(guī)則是否被執(zhí)行
- * @return
- */
- @Override
- public boolean shouldRun() {
- return true;
- }
- /**
- * 該規(guī)則執(zhí)行的順序
- * @return
- */
- @Override
- public int runOrder() {
- return 1;
- }
- }
3、項目內(nèi)經(jīng)常變動的以Groovy來實現(xiàn)
groovy兼容Java語法,可以直接用java語法來寫。
- public class GroovyRule extends RuleFilter {
- @Override
- public void run(String msg) {
- System.out.println(" === Groovy 實現(xiàn)的業(yè)務(wù)規(guī)則 order = " + runOrder() + ", msg = " + msg + " === ");
- }
- @Override
- public boolean shouldRun() {
- return true;
- }
- @Override
- public int runOrder() {
- return 2;
- }
- }
“然后把這個xxx.java文件丟到我們監(jiān)控的文件夾即可
4、在合適的位置使用RuleFilterProcessor
這里我寫了個Controller用來測試動態(tài)加載規(guī)則。
- @RestController
- @RequestMapping("/groovy")
- public class GroovyController {
- @Autowired
- private RuleFilterProcessor ruleFilterProcessor;
- @GetMapping()
- @ApiOperation("測試groovy的動態(tài)加載")
- public void transaction(@RequestParam String msg) {
- ruleFilterProcessor.runRuleFilters(msg);
- }
- }
5、啟動并驗證
我分了幾個場景驗證如下:
1). 啟動程序
瀏覽器訪問:http://localhost:8080/groovy?msg=laker%20666
結(jié)果如下:
- === Java 實現(xiàn)的業(yè)務(wù)規(guī)則 order = 1 , msg = laker 666 ===
- === Groovy 實現(xiàn)的業(yè)務(wù)規(guī)則 order = 2 , msg = laker 666 ===
2.) 我修改GroovyRule中的runOrder(),把它改為0
“不用重啟服務(wù)
瀏覽器訪問:http://localhost:8080/groovy?msg=laker%20666
結(jié)果如下:
- === Groovy 實現(xiàn)的業(yè)務(wù)規(guī)則 order = 0 , msg = laker 666 ===
- === Java 實現(xiàn)的業(yè)務(wù)規(guī)則 order = 1 , msg = laker 666 ===
3). 我新增一個Groovy2Rule然后丟進上面指定的監(jiān)控文件夾
- public class Groovy2Rule extends RuleFilter {
- @Override
- public void run(String msg) {
- System.out.println(" === Groovy 實現(xiàn)的業(yè)務(wù)規(guī)則 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;
- }
- }
不用重啟服務(wù)
瀏覽器訪問:http://localhost:8080/groovy?msg=laker%20666
結(jié)果如下:
- === Groovy 實現(xiàn)的業(yè)務(wù)規(guī)則 order = 0 , msg = laker 666 ===
- === Java 實現(xiàn)的業(yè)務(wù)規(guī)則 order = 1 , msg = laker 666 ===
- === Groovy 實現(xiàn)的業(yè)務(wù)規(guī)則 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
“這里如果想調(diào)用Spring環(huán)境中的bean可以借助SpringContextUtil
實現(xiàn)
核心的模塊如下
- RuleFilter :規(guī)則過濾器抽象類,用于擴展實現(xiàn)業(yè)務(wù)規(guī)則,供Java和Groovy繼承。
- RuleFilterLoader :規(guī)則過濾器加載器,用于加載基于Spring的RuleFilter實現(xiàn)類和動態(tài)編譯指定文件基于Groovy的RuleFilter實現(xiàn)類。
存儲所有的規(guī)則過濾器并能動態(tài)加載改變的和新增的規(guī)則。
- RuleFilterFileManager : 一個獨立線程輪詢監(jiān)聽指定目錄文件的變化配合RuleFilterLoader ( 規(guī)則過濾器加載器)使用。
- RuleFilterProcessor: 業(yè)務(wù)規(guī)則處理器核心入口
“這四個核心模塊都是盜版Zuul的實現(xiàn)。
貼上部分核心代碼如下:
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);
- }
- });
- }
- }
- }
總結(jié)
可以看到使用起來是相當(dāng)?shù)姆奖悖瑑H依賴groovy-all,整體代碼結(jié)構(gòu)簡單。
性能和穩(wěn)定性未測試,但是這基本就是翻版的Zuul,Zuul都在使用了,應(yīng)該沒什么問題。
參考:
參考了Zuul源碼,比較簡單,建議大家都去看看。