JDK下使用javax.tools.JavaCompiler
进行动态代码的生成,编译,执行。
在本文中,我们将研究如何将Java代码动态加载到正在运行的jvm中。 该代码可能是全新的,或者我们可能想更改程序中某些现有代码的功能。
(在开始之前,您可能想知道为什么到底有人会这样做。显而易见的示例是规则引擎之类的东西。规则引擎希望为用户提供添加或更改规则的能力,而不必重新启动规则。您可以通过将DSL脚本作为规则注入,由规则引擎调用此方法,这种方法的真正问题是必须对DSL脚本进行解释,使其运行起来非常缓慢。然后可以像程序中的任何其他代码一样编译和运行该程序,效率将提高几个数量级。
我们将要使用的库是Chronicle开源库Java-Runtime-Compiler 。
从下面的代码中您将看到,该库的使用极其简单-实际上,它实际上只需要几行。 创建一个CachedCompiler,然后调用loadFromJava。 (有关实际最简单的用例,请参见此处的文档。)
下面列出的程序执行以下操作:
- 创建一个线程,该线程每秒调用一次Strategy。
- 加载将两个数字相加的策略
- 等待3秒
- 加载从另一个数中减去一个数的策略
这是完整的代码清单:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
package test; import net.openhft.compiler.CachedCompiler; /** * Loads the addingStrategy and then after 3s replaces it with the * subtractingStrategy. */ public class DynamicJavaClassLoading { private final static String className = "test.MyClass"; private final static String addingStrategy = "package test;\n" + "import test.DynamicJavaClassLoading.Strategy;\n" + "public class MyClass implements Strategy {\n" + " public int compute(int a, int b) {\n" + " return a+b;\n" + " }\n" + "}\n"; private final static String subtractingStrategy = "package test;\n" + "import test.DynamicJavaClassLoading.Strategy;\n" + "public class MyClass implements Strategy {\n" + " public int compute(int a, int b) {\n" + " return a-b;\n" + " }\n" + "}\n"; public static void main(String[] args) throws Exception { StrategyProxy strategy = new StrategyProxy(); //Thread calling the strategy once a second Thread t = new Thread(() -> { while (true) { System.out.println(strategy.compute(10,20)); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); t.start(); { ClassLoader cl = new ClassLoader() { }; CachedCompiler cc = new CachedCompiler(null, null); Class aClass = cc.loadFromJava(cl, className, addingStrategy); Strategy runner = (Strategy) aClass.newInstance(); strategy.setStratgey(runner); } Thread.sleep(3000); { ClassLoader cl = new ClassLoader() { }; CachedCompiler cc = new CachedCompiler(null, null); Class aClass = cc.loadFromJava(cl, className, subtractingStrategy); Strategy runner = (Strategy) aClass.newInstance(); strategy.setStratgey(runner); } } public interface Strategy{ int compute(int a, int b); } public static class StrategyProxy implements Strategy{ private volatile Strategy underlying; public void setStratgey(Strategy underlying){ this.underlying = underlying; } public int compute(int a, int b){ Strategy underlying = this.underlying; return underlying == null ? Integer.MIN_VALUE : underlying.compute(a, b); } } } |
这是输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
The strategy has not been loaded yet. underlying in the StrategyProxy is null so Integer.MIN_VALUE is returned -2 1 4 7 4 8 3 6 4 8 The adding strategy has been loaded 10+20=30 30 30 30 After 3s the subtracting strategy is loaded. It replaces the adding strategy. 10-20=-10 -10 -10 -10 -10 -10 |
请注意,在每次加载策略时,在代码中我们都创建了一个新的ClassLoader和一个CachedCompiler。 这样做的原因是,ClassLoader一次只能加载一个特定类的一个实例。
如果仅使用该库来加载新代码,则可以这样做,而无需创建ClassLoader(即使用默认的ClassLoader)和CachedCompiler。
1 |
Class aClass = CompilerUtils.CACHED_COMPILER.loadFromJava(className, javaCode); |
Android下使用由于无法使用javax.tools.JavaCompiler
,因此使用 linkedin/dexmaker 进行动态代码的生成,编译,执行。
膜拜大佬