阿里妹导读:在平台级的 Java 系统中,动态脚本技术是不可或缺的一环。本文分享了一种 Java 动态脚本实现方案,给出了其中的关键技术点,并就类重名问题、生命周期、安全问题等做出进一步讨论,欢迎同学们共同交流。
文末福利:Java 学习路线。
eval('console.log(2+3)')
Groovy 虽然也是运行在 JVM,但是语法和 Java 有一些差异,对于只会 Java 的同学来说有一定学习成本。
动态类型,缺乏约束。有时候太过于灵活自由也是缺点,尤其是对于平台说来。
需要额外引入 Groovy 的引擎 jar 包,大小 6.2M,属实不小,对于有代码强迫症的我来说这会是一个重要考虑因素。
学习成本低,在阿里最主要的语言就是 Java,会 Java 几乎是每个工程师必备的技能,因此上手难度几乎为零。
Java 可以规定接口约束,从而使得用户写的前后置脚本整齐划一,方便管理和治理。
可以实时编译和错误提示,方便用户及时订正问题。
--dynamic-script
------advance-discuss //深度讨论脚本动态化技术中的一些细节
------code-javac //使用代码执行编译加载运行任务
------command-javac //演示用命令行的方式动态编译和加载java类
------facade //提供单独的接口包,方便整个演示过程流畅进行
cd 项目根目录
mvn install
# 进入到Cat.java所在的目录
cd /Users/fusu/d/group/fusu-share/dynamic-script/command-javac/src/main/resources
# 使用命令行工具javac编译,linux/mac 上cp分隔符使用 : windown使用 ;
javac -cp .:/Users/fusu/d/group/fusu-share/dynamic-script/facade/target/facade-1.0.jar Cat.java
# 运行
java -cp .:/Users/fusu/d/group/fusu-share/dynamic-script/facade/target/facade-1.0.jar Cat
# 得到结果
# > I'm Cat Main
//项目所在路径
String projectPath = PathUtil.getAppHomePath();
Process process = null;
String cmd = String.format("javac -cp .:%s/facade/target/facade-1.0.jar -d %s/command-javac/src/main/resources %s/command-javac/src/main/resources/Cat.java", projectPath, projectPath, projectPath);
System.out.println(cmd);
process = Runtime.getRuntime().exec(cmd);
// 打印程序输出
readProcessOutput(process);
int exitVal = process.waitFor();
if (exitVal == 0) {
System.out.println("javac执行成功!" + exitVal);
} else {
System.out.println("javac执行失败" + exitVal);
return;
}
String classFilePath = String.format("%s/command-javac/src/main/resources/Cat.class", projectPath);
String urlFilePath = String.format("file:%s", classFilePath);
URL url = new URL(urlFilePath);
URLClassLoader classLoader = new URLClassLoader(new URL[]{url});
Class<?> catClass = classLoader.loadClass("Cat");
Object obj = catClass.newInstance();
if (obj instanceof Animal) {
Animal animal = (Animal) obj;
animal.hello("Kitty");
}
//会得到结果: Hello,Kitty! 我是Cat。
//类名
String className = "Cat";
//项目所在路径
String projectPath = PathUtil.getAppHomePath();
String facadeJarPath = String.format(".:%s/facade/target/facade-1.0.jar", projectPath);
//需要进行编译的代码
Iterable<? extends JavaFileObject> compilationUnits = new ArrayList<JavaFileObject>() {{
add(new JavaSourceFromString(className, getJavaCode()));
}};
//编译的选项,对应于命令行参数
List<String> options = new ArrayList<>();
options.add("-classpath");
options.add(facadeJarPath);
//使用系统的编译器
JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager standardJavaFileManager = javaCompiler.getStandardFileManager(null, null, null);
ScriptFileManager scriptFileManager = new ScriptFileManager(standardJavaFileManager);
//使用stringWriter来收集错误。
StringWriter errorStringWriter = new StringWriter();
//开始进行编译
boolean ok = javaCompiler.getTask(errorStringWriter, scriptFileManager, diagnostic -> {
if (diagnostic.getKind() == Diagnostic.Kind.ERROR) {
errorStringWriter.append(diagnostic.toString());
}
}, options, null, compilationUnits).call();
if (!ok) {
String errorMessage = errorStringWriter.toString();
//编译出错,直接抛错。
throw new RuntimeException("Compile Error:{}" + errorMessage);
}
//获取到编译后的二进制数据。
final Map<String, byte[]> allBuffers = scriptFileManager.getAllBuffers();
final byte[] catBytes = allBuffers.get(className);
//使用自定义的ClassLoader加载类
FsClassLoader fsClassLoader = new FsClassLoader(className, catBytes);
Class<?> catClass = fsClassLoader.findClass(className);
Object obj = catClass.newInstance();
if (obj instanceof Animal) {
Animal animal = (Animal) obj;
animal.hello("Moss");
}
//会得到结果: Hello,Moss! 我是Cat。
/*FsClassLoader.java*/
public FsClassLoader(ClassLoader parentClassLoader, String name, byte[] data) {
super(parentClassLoader);
this.fullyName = name;
this.data = data;
}
/*AdvanceDiscuss.java*/
//接口的类加载器
ClassLoader animalClassLoader = Animal.class.getClassLoader();
//设置当前的线程类加载器
Thread.currentThread().setContextClassLoader(animalClassLoader);
//...
//使用自定义的ClassLoader加载类
FsClassLoader fsClassLoader = new FsClassLoader(animalClassLoader, className, catBytes);
NoInstance:该类所有的实例都已经被 GC。
NoClassLoader:加载该类的 ClassLoader 实例已经被 GC。
NoReference:该类的 java.lang.Class 没有被引用 (XXX.class,使用了静态变量/方法)。
for (int i = 0; i < 1000000; i++) {
//编译加载并且执行
compileAndRun(i);
//10000个回收一下
if (i % 10000 == 0) {
System.gc();
}
}
//强制进行回收
System.gc();
System.out.println("休息10s");
Thread.currentThread().sleep(10 * 1000);
public static Set<String> getDependencies(InputStream is) throws Exception {
ClassFile cf = new ClassFile(new DataInputStream(is));
ConstPool constPool = cf.getConstPool();
HashSet<String> set = new HashSet<>();
for (int ix = 1, size = constPool.getSize(); ix < size; ix++) {
int descriptorIndex;
if (constPool.getTag(ix) == ConstPool.CONST_Class) {
set.add(constPool.getClassInfo(ix));
} else if (constPool.getTag(ix) == ConstPool.CONST_NameAndType) {
descriptorIndex = constPool.getNameAndTypeDescriptor(ix);
String desc = constPool.getUtf8Info(descriptorIndex);
for (int p = 0; p < desc.length(); p++) {
if (desc.charAt(p) == 'L') {
set.add(desc.substring(++p, p = desc.indexOf(';', p)).replace('/', '.'));
}
}
}
}
return set;
}
java.lang,
java.util,
com.alibaba.fastjson,
java.text,
[Ljava.lang (java.lang下的数组,例如 `String[]`)
[D (double[])
[F (float[])
[I (int[])
[J (long[])
[C (char[])
[B (byte[])
[Z (boolean[])
java.lang.Thread
java.lang.reflect
含 6 大学习阶段(Java 语言基础、数据库开发、Java web 开发、Java 开发框架及工具、面试技巧)、26 门免费课程、871 课时教学视频、3 等级自测考试。排名第一的编程语言,从入门到实战,助你全面掌握 Java 开发技能。
识别下方二维码,或点击”阅读原文“立即学习:
推荐阅读