Java源代码动态编译类加载和代码执行(Java8)
#头条创作挑战赛#
Java 的一个重要特性是动态的类加载机制。通过在运行时动态地加载类,Java 程序可以实现很多强大的功能。下面通过一个具体的实例来说明 Java 程序中,如何动态地编译 Java 源代码、加载类和执行类中的代码。这里的代码示例适用的版本是 Java 8。
示例所实现的功能很简单,就是对表达式求值。输入的是类似 1 + 1 或 3 * (2 + 3) 这样的表达式,返回的是表达式的值。示例的做法是动态创建一个 Java 源文件,编译该文件生成 class 文件,加载 class 文件之后再执行。比如,需要求值的表达式是 1 + 1,那么所生成的 Java 源文件如下所示,其中 1 + 1 的部分是动态的。public class Calculator { public static Object calculate() { return 1 + 1; } }
我们只需要编译该源文件,加载编译之后的 class 文件,再通过反射 API 来调用其中的 calculate 方法就可以得到表达式求值的结果。编译
第一步是动态生成 Java 源代码并编译。生成 Java 源代码比较简单,直接用字符串连接就可以了。当然了,在生成逻辑比较复杂时,推荐的做法是使用字符串模板引擎,如 Handlebars。在下面的代码中,getJavaSource 方法生成 Java 源代码,compile 方法进行编译。
在进行编译的时候,使用的是 JDK 标准的 JavaCompiler 接口。从源代码字符串中创建了一个 JavaFileObject 对象作为编译时的源代码单元。编译时的选项 -d 指定了编译结果的输出路径,这里是一个临时文件夹。compile 方法的返回值是一个 Pair 对象,包含了 class 文件的路径,以及随机生成的 Java 包的名称。public class DynamicCompilation { private static final String CLASS_NAME = "Calculator"; public static Pair compile(String expr) throws IOException { String packageName = "z" + UUID.randomUUID().toString().replace("-", ""); Path outputPath = Files.createTempDirectory("expr"); JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null); compiler.getTask(null, fileManager, null, ImmutableList.of( "-d", outputPath.toAbsolutePath().toString() ), null, Collections.singletonList( new StringContentJavaFileObject(CLASS_NAME, getJavaSource(packageName, expr)))) .call(); return Pair.of(outputPath, packageName + "." + CLASS_NAME); } private static String getJavaSource(String packageName, String expr) { return "package " + packageName + "; " + "public class " + CLASS_NAME + " { public static Object calculate() { " + "return " + expr + "; }" + "}"; } }
上面的代码用到了一个帮助类 StringContentJavaFileObject,表示从字符串创建的 JavaFileObject 对象。public class StringContentJavaFileObject extends SimpleJavaFileObject { private final String content; public StringContentJavaFileObject(String name, String content) { super(URI.create("string:///" + name + Kind.SOURCE.extension), Kind.SOURCE); this.content = content; } @Override public CharSequence getCharContent(boolean ignoreEncodingErrors) { return content; } }
加载
编译完成之后的第二步是动态加载类。这一步并没有实现自定义的类加载器,而且使用内置的系统类加载器。系统类加载器通过 ClassLoader.getSystemClassLoader() 方法来获取。系统类加载器在 classpath 上查找类。这里用了一个比较 hack 的技巧来动态修改系统类加载器的 classpath。
在下面的代码中,ClasspathUpdater 的 addPath 方法可以把一个 Path 对象表示的路径,添加到系统类加载器的查找路径中。这是因为系统类加载器自身是 URLClassLoader 类型的加载器,其中的 addURL 方法可以添加新的查找路径。只不过 addURL 方法是 protected,这里通过反射 API 来进行调用。public class ClasspathUpdater { public static void addPath(Path path) { URLClassLoader classLoader = (URLClassLoader) ClassLoader.getSystemClassLoader(); try { Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class); method.setAccessible(true); method.invoke(classLoader, path.toUri().toURL()); } catch (Exception e) { throw new RuntimeException(e); } } }
上面介绍的 ClasspathUpdater 类中的使用技巧,只对 Java 8 生效。在 Java 9 引入模块系统时,对系统类加载器进行了修改。系统类加载器被替换成了应用类加载器。应用类加载器不再是 URLClassLoader 类型了,就不能使用这个技巧了。执行
最后一步就是执行动态加载的 Java 类。这一步比较简单,只需要用 Class.forName 方法来查找 Java 类,再找到对应的 Method 对象,直接调用即可。下面的代码给出了示例。public class Invoker { public static Object invoke(String className) { try { Method method = Class.forName(className).getDeclaredMethod("calculate"); return method.invoke(null); } catch (Exception e) { throw new RuntimeException(e); } } }完整的执行过程
最后把整个流程串起来。在下面的代码中,需要求值的表达式是 (1 + 1) * 3 / 5.0。首先调用 DynamicCompilation.compile 方法进行动态编译,得到 class 文件的路径和完整的类名。class 文件的路径通过 ClasspathUpdater.addPath 方法添加到 classpath 中。完整的类名则传递给 Invoker.invoke 方法来执行。最后输出的结果是表达式的值。public class Main { public static void main(String[] args) throws IOException { Pair result = DynamicCompilation.compile("(1 + 1) * 3 / 5.0"); ClasspathUpdater.addPath(result.getLeft()); System.out.println(Invoker.invoke(result.getRight())); } }
中老一家亲南欧江水电站中老员工共庆新春琅勃拉邦航拍国际在线报道(记者莫小玲)中国电建南欧江流域梯级水电项目位于老挝北部琅勃拉邦省和丰沙里省境内,是中企在海外首个全流域整体规划和投建的BOT项目,2021年9月全部建成投
明天破五节,破五吃三样,人旺财也旺,三样分别指什么?快乐的时光总是短暂的,不知不觉新年就要过完了,明天就是正月初五,也就是我们所说的破五,一般说来,在除夕到正月初五期间,我们有很多禁忌要讲究。比如说不能倒垃圾,不能说不吉利的话,不能
8。如何维持正常的肠道功能1营养均衡的膳食(一日三餐中包含蛋白质维生素矿物质脂肪酸膳食纤维等)2充足睡眠(可以维持正常的人体免疫力,睡眠不足免疫力低下会使细菌有乘虚而入的机会)3规律的作息(小孩身体发育尚未
比贫穷更可怕的,是孩子没有规矩现在的孩子,你给他说一会儿出去干啥啥要怎么怎么样,他都会问为什么,家长说这是规矩,他都会小声嘟囔规矩规矩又是规矩,一副讨厌的表情!其实,规矩它是一直存在!自然有自然的规矩如四季轮回
7。解决便秘的办法1合理服用乳果糖等纤维素药物(若服用量过大会出现腹泻样表现,要选择与孩子年龄相当的剂量)2合理服用益生菌益生元3正确使用开塞露(长期使用会产生心理依赖)4顽固性便秘需要就医查明原因
如何帮助重度精神分裂症妈妈从一个爱抱怨的人,变成学会思考的人首先,从时间上这件事不是一蹴而就的,所以需要的时间也是相对漫长的。做好打持久战的心理准备,日子才有希望过得轻松快乐。其次,要考虑到想要解决这个问题是极有难度的,方法的多样性和有效性
平安是福,提醒您和孩子安全过大年!健康幸福过新年(8)春节安全过大年春节是孩子们的开心时刻,家里都摆放着各类坚果糖果水果等,加上孩子们在一起打打闹闹,容易发生食物卡喉及异物吸入的情况。家长要注意防范,懂得如何处理。孩子好奇心强,安全意
肌斜颈先天性肌斜颈是一侧胸锁乳突肌纤维性挛缩,颈部和头面部向患侧偏斜畸形,很多是由于胎儿臀位产产伤及牵拉等因素导致胸锁乳突肌损伤出血,血肿机化挛缩而形成,此外还有遗传,子宫内外感染等因素
闪电日志洗澡是个技术活山幺闪电的幼儿期生活钟基本上保持一致,吃完饭喝完奶就是洗脸。睡觉醒来先伸懒腰,做个简短的磨爪运动,然后舔舐身体。妥妥一枚爱干净的崽!视频加载中不过布偶宝宝的毛是真的长。每次看闪电洗
春晚10大热搜,岳云鹏凭实力,张若昀靠颜值,撒贝宁苏有朋最尴尬随着全国各地鞭炮声的奏响,新的一年也已经来到。与往年一样,每家每户在团圆之时,总是守着电视收看春晚,虽然近几年春晚节目质量总是褒贬不一,但也丝毫不影响大家的观看热情。与往届春晚一样
贾玲2023年01月25日消息贾玲贾玲2023年01月25日消息贾玲没有参加2013年春晚?或许正走着一步大棋贾玲岳云鹏沈腾宋小宝四大喜剧团全接不了赵本山的班贾玲张小斐缺席2023年春晚贾玲怎么没有上今年春晚?