阿里妹导读:在平台级的 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.Threadjava.lang.reflect
含 6 大学习阶段(Java 语言基础、数据库开发、Java web 开发、Java 开发框架及工具、面试技巧)、26 门免费课程、871 课时教学视频、3 等级自测考试。排名第一的编程语言,从入门到实战,助你全面掌握 Java 开发技能。
识别下方二维码,或点击”阅读原文“立即学习:
推荐阅读