值提取系列

值提取系列

字符串值提取工具-01-概览

字符串值提取工具-02-java 调用 js

字符串值提取工具-03-java 调用 groovy

字符串值提取工具-04-java 调用 java? Janino 编译工具

字符串值提取工具-05-java 调用 shell

字符串值提取工具-06-java 调用 python

字符串值提取工具-07-java 调用 go

代码地址

value-extraction 值提取核心

场景

我们希望通过 java 执行 java,如何实现呢?

入门例子

代码

  [java]
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 org.example; import javax.tools.*; import java.io.File; import java.lang.reflect.Method; import java.net.URI; import java.net.URL; import java.net.URLClassLoader; import java.util.Arrays; public class DynamicJavaExecutor { public static void main(String[] args) { // Java 代码字符串 String javaCode = "public class HelloWorld { " + " public static void main(String[] args) { " + " System.out.println(\"Hello, World!\"); " + " } " + "} "; // 编译 Java 代码 boolean success = compileJavaCode("HelloWorld", javaCode); if (success) { try { // 使用 URLClassLoader 加载编译后的类 File file = new File("./"); // 获取当前目录 URL url = file.toURI().toURL(); // 转换为 URL URLClassLoader classLoader = new URLClassLoader(new URL[]{url}); Class<?> clazz = classLoader.loadClass("HelloWorld"); // 调用类的 main 方法 Method mainMethod = clazz.getMethod("main", String[].class); String[] params = null; // 传递给 main 方法的参数 mainMethod.invoke(null, (Object) params); } catch (Exception e) { e.printStackTrace(); } } else { System.out.println("Compilation failed."); } } public static boolean compileJavaCode(String className, String javaCode) { // 创建自定义的 JavaFileObject JavaFileObject fileObject = new InMemoryJavaFileObject(className, javaCode); // 获取系统 Java 编译器 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null); // 设置输出目录 Iterable<String> options = Arrays.asList("-d", "./"); // 编译 Java 代码 JavaCompiler.CompilationTask task = compiler.getTask( null, fileManager, null, options, null, Arrays.asList(fileObject) ); // 进行编译 return task.call(); } // 内部类,用于在内存中表示 Java 源文件 static class InMemoryJavaFileObject extends SimpleJavaFileObject { private final String code; protected InMemoryJavaFileObject(String name, String code) { super(URI.create("string:///" + name.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE); this.code = code; } @Override public CharSequence getCharContent(boolean ignoreEncodingErrors) { return code; } } }

测试效果:

  [plaintext]
1
Hello, World!

Janino 例子

maven 引入

  [xml]
1
2
3
4
5
<dependency> <groupId>org.codehaus.janino</groupId> <artifactId>janino</artifactId> <version>3.1.9</version> </dependency>

代码

  [java]
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
package org.example; import org.codehaus.janino.SimpleCompiler; public class JaninoExample { public static void main(String[] args) throws Exception { // Java 代码字符串 String javaCode = "public class HelloWorld { " + " public void run() { " + " System.out.println(\"Hello, World!\"); " + " } " + "} "; // 创建编译器实例 SimpleCompiler compiler = new SimpleCompiler(); // 编译 Java 代码 compiler.cook(javaCode); // 获取编译后的类 Class<?> clazz = compiler.getClassLoader().loadClass("HelloWorld"); // 创建类的实例 Object instance = clazz.getDeclaredConstructor().newInstance(); // 调用 run 方法 clazz.getMethod("run").invoke(instance); } }

直接执行该方法

  [java]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.github.houbb.value.extraction.test; import org.codehaus.janino.ScriptEvaluator; public class JavaDemoTest { public static void main(String[] args) throws Exception { // Java 脚本字符串 String script = "System.out.println(\"Hello, World!\");"; // 创建脚本求值器实例 ScriptEvaluator scriptEvaluator = new ScriptEvaluator(); // 编译并执行脚本 scriptEvaluator.cook(script); scriptEvaluator.evaluate(null); } }

传入参数,直接执行

  [java]
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
package com.github.houbb.value.extraction.test; import org.codehaus.janino.ScriptEvaluator; import java.util.HashMap; import java.util.Map; public class JaninoExample { public static void main(String[] args) throws Exception { // 创建一个包含参数的 Map Map<String, Object> bindings = new HashMap<>(); bindings.put("a", 10); bindings.put("b", 20); // 定义要执行的脚本 String script = "System.out.println(\"Result: \" + (a + b));"; // 调用方法来执行脚本 executeScriptWithBindings(script, bindings); } public static void executeScriptWithBindings(String script, Map<String, Object> bindings) throws Exception { // 提取 Map 中的键(参数名)和值(参数值) String[] parameterNames = bindings.keySet().toArray(new String[0]); Class<?>[] parameterTypes = new Class<?>[parameterNames.length]; // 假设所有参数的类型都是 Object,可以根据需要修改类型推断逻辑 for (int i = 0; i < parameterNames.length; i++) { parameterTypes[i] = bindings.get(parameterNames[i]).getClass(); } // 创建 ScriptEvaluator 实例 ScriptEvaluator scriptEvaluator = new ScriptEvaluator(); // 设置脚本的参数名称和类型 scriptEvaluator.setParameters(parameterNames, parameterTypes); // 编译脚本 scriptEvaluator.cook(script); // 提取 Map 中的值作为参数 Object[] parameterValues = bindings.values().toArray(); // 执行脚本 scriptEvaluator.evaluate(parameterValues); } }

但是感觉这个很麻烦,而且有问题

另一种执行的方式

  [java]
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
package com.github.houbb.value.extraction.test.javas; import org.codehaus.commons.compiler.CompileException; import org.codehaus.janino.ScriptEvaluator; import java.lang.reflect.InvocationTargetException; import java.util.HashMap; import java.util.Map; public class JaninoScriptMapExample { public static void main(String[] args) throws CompileException, InvocationTargetException { // 示例脚本,使用 Map 参数 String script = "return map.get(\"greeting\") + \", \" + map.get(\"name\") + \"!\";"; // 创建 ScriptEvaluator 实例,指定返回类型、参数名和参数类型 ScriptEvaluator se = new ScriptEvaluator( script, // 脚本代码 Object.class, // 返回值类型 new String[]{"map"}, // 参数名列表 new Class<?>[]{Map.class} // 参数类型列表 ); // 准备传入的 Map 参数 Map<String, Object> params = new HashMap<>(); params.put("greeting", "Hello"); params.put("name", "Janino"); // 执行脚本,传入 Map 参数 Object result = se.evaluate(new Object[]{params}); // 输出结果 System.out.println(result); } }

chat

java 调用 java

这个错误通常是因为编译的类没有正确加载到类加载器的路径中。我们需要确保编译后的类文件被正确加载。

在此示例中,我们需要使用一个自定义的类加载器来加载编译后的类。以下是修正后的代码,确保类文件编译后正确加载和执行:

  [java]
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
84
85
86
87
import javax.tools.JavaCompiler; import javax.tools.ToolProvider; import javax.tools.JavaFileObject; import javax.tools.StandardJavaFileManager; import javax.tools.SimpleJavaFileObject; import java.net.URI; import java.util.Arrays; import java.util.Locale; import java.util.logging.Level; import java.util.logging.Logger; import java.lang.reflect.Method; import java.io.File; import java.net.URL; import java.net.URLClassLoader; public class DynamicJavaExecutor { public static void main(String[] args) { // Java 代码字符串 String javaCode = "public class HelloWorld { " + " public static void main(String[] args) { " + " System.out.println(\"Hello, World!\"); " + " } " + "} "; // 编译 Java 代码 boolean success = compileJavaCode("HelloWorld", javaCode); if (success) { try { // 使用 URLClassLoader 加载编译后的类 File file = new File("./"); // 获取当前目录 URL url = file.toURI().toURL(); // 转换为 URL URLClassLoader classLoader = new URLClassLoader(new URL[]{url}); Class<?> clazz = classLoader.loadClass("HelloWorld"); // 调用类的 main 方法 Method mainMethod = clazz.getMethod("main", String[].class); String[] params = null; // 传递给 main 方法的参数 mainMethod.invoke(null, (Object) params); } catch (Exception e) { e.printStackTrace(); } } else { System.out.println("Compilation failed."); } } public static boolean compileJavaCode(String className, String javaCode) { // 创建自定义的 JavaFileObject JavaFileObject fileObject = new InMemoryJavaFileObject(className, javaCode); // 获取系统 Java 编译器 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null); // 设置输出目录 Iterable<String> options = Arrays.asList("-d", "./"); // 编译 Java 代码 JavaCompiler.CompilationTask task = compiler.getTask( null, fileManager, null, options, null, Arrays.asList(fileObject) ); // 进行编译 return task.call(); } // 内部类,用于在内存中表示 Java 源文件 static class InMemoryJavaFileObject extends SimpleJavaFileObject { private final String code; protected InMemoryJavaFileObject(String name, String code) { super(URI.create("string:///" + name.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE); this.code = code; } @Override public CharSequence getCharContent(boolean ignoreEncodingErrors) { return code; } } }

关键点说明:

  1. 编译输出目录:在 compileJavaCode 方法中,使用 -d ./ 设置编译输出目录为当前目录。
  2. URLClassLoader:在加载编译后的类时,使用 URLClassLoader 从当前目录加载类文件。

通过这些修改,编译后的类文件将存储在当前目录,并且可以正确加载和执行。

你可以根据需要调整代码以适应不同的目录结构或文件位置。

如果你有更多问题或需求,请告诉我。

有没有比较成熟的工具?

在 Java 中,有一些成熟的工具和库可以帮助你动态编译和执行 Java 代码字符串。以下是两个常用的工具:

  1. Janino:一个小型但功能强大的 Java 编译器,可以在运行时编译和执行 Java 代码。
  2. JShell:自 Java 9 引入的交互式编程工具,允许在 REPL 环境中执行 Java 代码。

使用 Janino 动态编译和执行 Java 代码

Janino 是一个嵌入式的 Java 编译器,适合在运行时编译和执行代码。

  1. 添加 Janino 依赖

    如果你使用 Maven,添加以下依赖:

      [xml]
    1
    2
    3
    4
    5
    <dependency> <groupId>org.codehaus.janino</groupId> <artifactId>janino</artifactId> <version>3.1.9</version> </dependency>

    如果你使用 Gradle,添加以下依赖:

      [groovy]
    1
    implementation 'org.codehaus.janino:janino:3.1.9'
  2. 使用 Janino 编译和执行 Java 代码

      [java]
    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
    import org.codehaus.janino.SimpleCompiler; public class JaninoExample { public static void main(String[] args) throws Exception { // Java 代码字符串 String javaCode = "public class HelloWorld { " + " public void run() { " + " System.out.println(\"Hello, World!\"); " + " } " + "} "; // 创建编译器实例 SimpleCompiler compiler = new SimpleCompiler(); // 编译 Java 代码 compiler.cook(javaCode); // 获取编译后的类 Class<?> clazz = compiler.getClassLoader().loadClass("HelloWorld"); // 创建类的实例 Object instance = clazz.getDeclaredConstructor().newInstance(); // 调用 run 方法 clazz.getMethod("run").invoke(instance); } }

使用 JShell 动态执行 Java 代码

JShell 是 Java 9 引入的工具,可以在交互式环境中运行 Java 代码。

  1. 启动 JShell

    在命令行中,输入 jshell 以启动 JShell。

  2. 在 JShell 中执行代码

      [java]
    1
    2
    3
    4
    5
    6
    7
    jshell> int x = 10; x ==> 10 jshell> System.out.println("Hello, World!"); Hello, World! jshell> /exit

JShell 适合用于交互式开发和测试,但不太适合在生产代码中动态执行 Java 代码。

选择合适的工具

对于在运行时动态编译和执行 Java 代码,Janino 是一个非常成熟且易用的工具。

而 JShell 则更适合用于交互式开发和快速测试。

参考资料