引言
或许大部分人工作至今都没有使用过 Java 的动态编译功能,当然我也是在机缘巧合之下才有机会去研究使用。
这就不得不说到我刚来我们部门的故事了,当时我接收了一个项目,主要是做部门各个业务与外部三方的对接,在接手后我遇到了一些问题:
1、项目就是一个大杂烩,包含了各个业务的代码。经常来个需求但已经无法找到对应的负责人(要么离职要么已经不负责这块业务),最后就要让我修改,可我也不是很了解相关业务。我恨呐!
2、各个业务方每次改动都需要找我发版以及做分支管理,需要耗费精力来处理与我负责业务无关的事情。我烦呐!
为了解决这些问题我就开动了我聪明的脑瓜子,为何不将这项目里的代码分割成一块块小的代码块?然后只要对这些代码块做好管理就可以了,这样就解决了这些代码归属的问题。
但还存在一个问题就是每次来需求都需要改动并发版,这对于一个需要的稳定的组件系统的设计初衷来说肯定是背道而驰的。这个时候我就想到了动态编译,它或许能解决!
1、什么是动态编译
在 Java 中,动态编译是指在运行时动态地编译 Java 源代码,生成字节码,并加载到 JVM 中执行。动态编译可以用于实现动态代码生成、动态加载、插件化等功能。
1.1、动态编译的相关概念
-
JavaFileManager 对象:用于管理编译过程中的文件。
- JavaFileManager 是一个接口,提供了对 Java 文件的管理功能,包括创建、查找、读写等操作。JavaFileManager 有多种实现方式,例如 StandardJavaFileManager、ForwardingJavaFileManager 等。
-
DiagnosticListener 对象:用于收集编译时的诊断信息。
- DiagnosticListener 是一个接口,用于接收编译时的诊断信息,例如错误、警告等。
-
JavaFileObject 对象:表示要编译的 Java 源代码。
- JavaFileObject 是一个抽象类,用于表示 Java 源代码或字节码。JavaFileObject 有多种实现方式,例如 SimpleJavaFileObject、JavaFileObjectWrapper 等。
1.2、如何简单的实现动态编译
- 创建一个 JavaCompiler 对象,该对象用于编译 Java 源代码。
- 创建一个 DiagnosticCollector 对象,该对象用于收集编译时的诊断信息。
- 创建一个 JavaFileManager 对象,该对象用于管理编译过程中的文件。
- 创建一个 JavaFileObject 对象,该对象用于表示要编译的 Java 源代码。
- 调用 JavaCompiler 对象的 getTask 方法,传入 JavaFileManager 对象和 DiagnosticCollector 对象,获取一个 CompilationTask 对象。
- 调用 CompilationTask 对象的 call 方法,编译 Java 源代码。
- 获取 DiagnosticCollector 对象的诊断信息,并处理编译结果。
下面是一个简单的示例,演示如何使用动态编译:
public class DynamicCompiler {
public static void main(String[] args) throws Exception {
// 创建 JavaCompiler 对象
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
// 创建 DiagnosticCollector 对象,用于收集编译时的诊断信息
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
// 创建 JavaFileManager 对象,用于管理编译过程中的文件
StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null);
// 创建 JavaFileObject 对象,用于表示要编译的 Java 源代码
String code = "public class HelloWorld { public static void main(String[] args) { System.out.println("Hello World!"); } }";
JavaFileObject source = new JavaSourceFromString("HelloWorld", code);
// 获取 CompilationTask 对象
Iterable<? extends JavaFileObject> compilationUnits = Arrays.asList(source);
CompilationTask task = compiler.getTask(null, fileManager, diagnostics, null, null, compilationUnits);
// 编译 Java 源代码
boolean success = task.call();
// 获取诊断信息
List<Diagnostic<? extends JavaFileObject>> messages = diagnostics.getDiagnostics();
for (Diagnostic<? extends JavaFileObject> message : messages) {
System.out.println(message.getMessage(null));
}
// 处理编译结果
if (success) {
System.out.println("Compilation was successful.");
} else {
System.out.println("Compilation failed.");
}
fileManager.close();
}
}
class JavaSourceFromString extends SimpleJavaFileObject {
final String code;
JavaSourceFromString(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;
}
}
运行结果:
Hello World!
Compilation was successful.
2、如何结合 springboot 项目使用
上面展示了如何简单使用 Java 的动态编译功能,但是在日常项目开发中,会面对更多的场景。结合前言中我所遇到的问题,我简单的给大家介绍下我在项目中是如何使用 Java 的动态编译功能来解