Java 如何实现动态脚本?
![图片](https://img2024.cnblogs.com/blog/572188/202402/572188-20240220095237625-1171041507.png)
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。
![图片](https://img2024.cnblogs.com/blog/572188/202402/572188-20240220095237595-703967458.png)
/*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);
![图片](https://img2024.cnblogs.com/blog/572188/202402/572188-20240220095237816-1388937004.gif)
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.Threadjava.lang.reflect
摘抄自网络,便于检索查找。