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

从零搭建开发脚手架 Spring Boot 集成Groovy实现动态加载业务规则_laker的博客-CSDN博客文章目录
背景
什么是 Groovy?
使用体验
1、利用Spring Boot的`CommandLineRunner`注册SpringBean、GroovyBean
2、项目内不变的规则以Java实现继承RuleFilter
3、项目内经常变动的以Groovy来实现
4、在合适的位置使用RuleFilterProcessor
5、启动并验证
实现
总结
背景
前端时间体验了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 === 
1
2
2. 我修改GroovyRule中的runOrder(),把它改为0

不用重启服务

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

结果如下:

 === Groovy 实现的业务规则 order = 0 , msg = laker 666 === 
 === Java 实现的业务规则 order = 1 , msg = laker 666 === 
1
2
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源码,比较简单,建议大家都去看看。
————————————————
版权声明:本文为CSDN博主「lakernote」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/abu935009066/article/details/118899183

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值