Groovy实现热部署
Posted 热爱编程的大忽悠
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Groovy实现热部署相关的知识,希望对你有一定的参考价值。
Groovy实现热部署
原文中对于Grovvy实现热更新原理没有进行讲解,并且案例存在问题,可能是因为本人版本与作者不同所致,所以本文会进行原理介绍,并纠正原文错误。
一、概述
Groovy是构建在JVM上的一个轻量级却强大的动态语言, 它结合了Python、Ruby和Smalltalk的许多强大的特性.
Groovy就是用Java写的 , Groovy语法与Java语法类似, Groovy 代码能够与 Java 代码很好地结合,也能用于扩展现有代码, 相对于Java, 它在编写代码的灵活性上有非常明显的提升,Groovy 可以使用其他 Java 语言编写的库.
Groovy通常是被用来扩展现有的代码,就是说,当你需要某个实现类动态生成的时候,就可以使用Groovy来完成,比如:
- 动态类实现从文件生成,改动后能立即检测到。
- 动态类实现从数据库中生成,改动后能立即检测到。
- 动态类作为Spring的bean被管理起来,改动后能立即检测到。
这次,我要讲的就是这三种方式。
二、准备工作
本篇的使用场景是:假设有一个规则接口,它的实现可以是本地的JAVA代码实现,也可以是groovy文件实现,也可以通过数据库存储的Groovy脚本实现,也可以是Spring管理的bean。然后这多种不同的规则实现,放到一个列表中,统一执行。
在xxl-job中,所有的任务都要实现IJobHandler接口,它的web Glue方式就是基于Groovy实现的。
2.1 规则接口IRule
IRule定义了这个规则接口。
IRule:
package cn.pomit.skill.groovy.rule;
public interface IRule
static final int NORMAL_TYPE = 0;
static final int GROOVY_FILE_TYPE = 1;
static final int GROOVY_DB_TYPE = 2;
static final int GROOVY_SPRING_TYPE = 3;
int getType();
void printInfo();
三、非Spring环境Groovy文件方式
3.1 Groovy文件
定义一个GroovyFileRule的Groovy文件,执行自己的规则。
GroovyFileRule:
package cn.pomit.skill.groovy.rule.file;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import cn.pomit.skill.groovy.rule.IRule;
class GroovyFileRule implements IRule
private Logger log = LoggerFactory.getLogger(this.getClass());
@Override
public int getType()
return GROOVY_FILE_TYPE;
@Override
public void printInfo()
log.info("这是一段来自Groovy文件的代码");
3.2 读取并生成实例
我这里定义了一个GroovyFactory,用来实现将Groovy文件生成IRule的实现.
GroovyFactory :
package cn.pomit.skill.groovy.rule;
import java.net.URL;
import groovy.lang.GroovyClassLoader;
import groovy.lang.GroovyCodeSource;
public class GroovyFactory
private static GroovyFactory groovyFactory = new GroovyFactory();
private GroovyClassLoader groovyClassLoader = new GroovyClassLoader();
public static GroovyFactory getInstance()
return groovyFactory;
/**
* 根据groovy文件路径生成IRule的实现
* @param packagePath
* @return
* @throws Exception
*/
public IRule getIRuleFromPackage(String filePath) throws Exception
Class<?> clazz = groovyClassLoader.parseClass(new GroovyCodeSource(new File(filePath)),false);
if (clazz != null)
Object instance = clazz.newInstance();
if (instance instanceof IRule)
return (IRule) instance;
throw new IllegalArgumentException("读取groovy文件异常");
3.3 使用这个实现
public class Main
public static void main(String[] args) throws Exception
dynamicDebug();
Thread.sleep(10000);
dynamicDebug();
private static void dynamicDebug() throws Exception
//直接读取Groovy文件生成IRule实现
IRule groovyFile = GroovyFactory.getInstance() .getIRuleFromPackage("C:\\\\Users\\\\zdh\\\\IdeaProjects\\\\leetcode\\\\src\\\\main\\\\java\\\\com\\\\GroovyFileRule.groovy");
groovyFile.printInfo();
在睡眠10s内,去修改对应的GroovyFileRule.groovy类输出的内容(记得ctrl+s保存文件),会发现下一次输出时,就会输出最新内容。
四、数据库Groovy脚本方式
4.1 Groovy脚本
定义一个GroovyDbRule 的脚本,执行自己的规则。GroovyDbRule 脚本存储在数据库中,
GroovyDbRule脚本如下:
package cn.pomit.skill.groovy.rule.file;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import cn.pomit.skill.groovy.rule.IRule;
class GroovyDbRule implements IRule
private Logger log = LoggerFactory.getLogger(this.getClass());
@Override
public int getType()
return GROOVY_DB_TYPE;
@Override
public void printInfo()
log.info("这是一段来自数据库的Groovy脚本");
建表语句及插入以上脚本:
CREATE TABLE `pomit_rule` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
`rule` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci,
`create_time` timestamp(0) DEFAULT CURRENT_TIMESTAMP,
`update_time` timestamp(0) DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP(0),
`visible` int(11) DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = latin1 COLLATE = latin1_swedish_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of pomit_rule
-- ----------------------------
INSERT INTO `pomit_rule` VALUES (5, 'GroovyDbRule', 'package cn.pomit.skill.groovy.rule.file;\\r\\n\\r\\nimport org.slf4j.Logger;\\r\\nimport org.slf4j.LoggerFactory;\\r\\n\\r\\nimport cn.pomit.skill.groovy.rule.IRule;\\r\\n\\r\\nclass GroovyDbRule implements IRule \\r\\n private Logger log = LoggerFactory.getLogger(this.getClass());\\r\\n \\r\\n @Override\\r\\n public int getType() \\r\\n return GROOVY_DB_TYPE;\\r\\n \\r\\n\\r\\n @Override\\r\\n public void printInfo() \\r\\n log.info(\\"这是一段来自数据库的Groovy脚本\\");\\r\\n printInfoHigh();\\r\\n \\r\\n \\r\\n public void printInfoHigh() \\r\\n log.info(\\"这是一段来自数据库的Groovy脚本的代码\\");\\r\\n \\r\\n\\r\\n', '2020-01-02 10:36:01', '2020-01-02 10:36:01', NULL);
4.2 读取并生成实例
在GroovyFactory,加入从字符串生成IRule实现的方法.
在上面3.2中的GroovyFactory 中,加入下面的代码:
/**
* 根据脚本内容生成IRule的实现
* @param code
* @return
* @throws Exception
*/
public IRule getIRuleFromCode(String code) throws Exception
Class<?> clazz = groovyClassLoader.parseClass(code);
if (clazz != null)
Object instance = clazz.newInstance();
if (instance != null)
if (instance instanceof IRule)
return (IRule) instance;
throw new IllegalArgumentException("读取groovy脚本异常");
和案例一没啥区别,也就是将源码存储到数据库了,不用从文件读取了。
五、Spring中使用Groovy的方式
5.1 Groovy文件
定义一个SpringGroovyRule 文件,执行自己的规则。这里拿它来测试Spring是如何将Groovy文件作为Bean来使用,不再手动读取Groovy文件。
SpringGroovyRule如下:
package cn.pomit.skill.groovy.rule.spring;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import cn.pomit.skill.groovy.rule.IRule;
import org.springframework.stereotype.Service;
public class SpringGroovyRule implements IRule
private Logger log = LoggerFactory.getLogger(this.getClass());
@Override
public int getType()
return GROOVY_SPRING_TYPE;
@Override
public void printInfo()
log.info("这是一段Spring的Groovy代码");
5.2 读取并生成实例
建立Spring的配置文件(SpringBoot也要这样玩,因为Groovy文件没有JAVA配置的方式生成Bean):spring-groovy.xml。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:lang="http://www.springframework.org/schema/lang"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/lang
http://www.springframework.org/schema/lang/spring-lang.xsd">
<lang:defaults refresh-check-delay="60000" />
<!-- groovy文件定义为bean -->
<lang:groovy id="springGroovyRule"
script-source="classpath:cn/pomit/skill/groovy/rule/spring/SpringGroovyRule.groovy">
</lang:groovy>
</beans>
5.3 使用这个实现
如果是在SpringBoot下,需要引入上面的xml文件:@ImportResource(locations="classpath:spring-groovy.xml")
注入这个bean:
@Resource(name = "springGroovyRule")
IRule springGroovyRule;
六 原理篇
我们来看看Groovy是如何利用classLoader完成热更新的:
- parseClass方法
public Class parseClass(GroovyCodeSource codeSource, boolean shouldCacheSource) throws CompilationFailedException
//shouldCacheSource参数用来开启缓存,在应用层面避免了同一个类名被多次加载的问题
//如果需要实现热更新需要传入参数false,关闭应用缓存
synchronized (sourceCache)
Class answer = sourceCache.get(codeSource.getName());
if (answer != null) return answer;
answer = doParseClass(codeSource);
if (shouldCacheSource) sourceCache.put(codeSource.getName(), answer);
return answer;
- doParseClass方法
private Class doParseClass(GroovyCodeSource codeSource)
...
Class answer=null;
//ClassCollector通过内部的createClass方法调用内部加载器的defineClass方法加载类
ClassCollector collector = createCollector(unit, su);
...
//将源码文件进行编译操作,变为对应的.class字节流--该方法内部调用ClassCollector的createClass方法加载类
unit.compile(goalPhase);
...
//返回加载得到的class对象
answer = collector.generatedClass;
return answer;
- createCollector方法
protected ClassCollector createCollector(CompilationUnit unit, SourceUnit su)
//classCollector利用InnerLoader来加载内,这里是热更新的关键
//因为每次都是new了一个新的类加载器
InnerLoader loader = new InnerLoader(GroovyClassLoader.this)
return new ClassCollector(loader, unit, su);
- createClass方法
//此时我们的源文件已经被编译为了class字节流
protected Class createClass(byte[] code, ClassNode classNode)
//拿到上面new出来的loader
GroovyClassLoader cl = getDefiningClassLoader();
//利用InnerLoader来加载我们指定的类
Class theClass = cl.defineClass(classNode.getName(), code, 0, code.length, unit.getAST().getCodeSource());
this.loadedClasses.add(theClass);
...
this.generatedClass=theClass;
return theClass;
以上是关于Groovy实现热部署的主要内容,如果未能解决你的问题,请参考以下文章