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















 
 
 



 
 
 
 