本文转载自微信公众号「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>
- 1.
- 2.
- 3.
- 4.
- 5.
什么是 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();
}
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
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;
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
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;
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
“然后把这个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);
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
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;
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
不用重启服务
浏览器访问: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
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
“这里如果想调用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());
}
...
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
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();
}
...
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
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);
}
...
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
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);
}
});
}
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
总结
可以看到使用起来是相当的方便,仅依赖groovy-all,整体代码结构简单。
性能和稳定性未测试,但是这基本就是翻版的Zuul,Zuul都在使用了,应该没什么问题。
参考:
参考了Zuul源码,比较简单,建议大家都去看看。