1.简介
我们学习java的时候我们常常手动使用 javac -d . 类名.java来编译java文件, 在jdk1.6 以后 ,jdk提供了可以在java程序中编译,运行java类。借此我们可以实现动态编译,动态加载,以提高程序灵活性
2.JavaCompiler的一个简单例子
我们首先直接用我们的java代码编译一个程序
// 获取java编译器
javax.tools.JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
//
InputStream first = null; // 程序的输入 null 用 system.in
OutputStream second = null; // 程序的输出 null 用 system.out
OutputStream third = null; // 程序的错误输出 .,null 用 system.err
// 程序编译参数 注意 我们编译目录是我们的项目目录
String[] strings = {"-d", ".", "test/Hello.java"};
// 0 表示成功, 其他表示出现了错误
int i = javaCompiler.run(first, second, third, strings);
if (i == 0 ) {
System.out.println("成功");
}else {
System.out.println("错误");
}
结果
深入使用JavaCompiler
1. 不使用监听器
这个例子是编译指定文件(File ) ,并把结果保存到目录
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
// 我们不使用监听器 , Locale使用Locale.getDefault() Charset.getDefault()
// 作用就是把File 转换wei JavaFileObject
StandardJavaFileManager standardFileManager = compiler.getStandardFileManager(null, null, null);
Iterable<? extends JavaFileObject> iterable = standardFileManager.getJavaFileObjects(new File("C:\\Users\\Administrator\\Desktop\\Hello.java"));
// 编译参数 把结果文件生成与原文件同一个目录
List<String> options = Arrays.asList("-d", "C:\\Users\\Administrator\\Desktop\\");
// 注解处理器的 类
List<String> classes = null;
// 创建一个编译任务
JavaCompiler.CompilationTask task = compiler.getTask(null, standardFileManager, null, options, classes, iterable);
//JavaCompiler.CompilationTask 实现了 Callable 接口
Boolean result = task.call();
System.out.println(result ? "成功" : "失败");
结果 : 成功
2.使用监听器
这个监听器的作用就是,当我们编译出现问题的时候调用 ,比如我们的原代码故意写错
package test;
public class Hello {
1
}
我们的监听器代码
static DiagnosticListener<JavaFileObject> diagnosticListener() {
return (diagnostic) -> {
//diagnostic 对象可以获取更加详细的信息,我简单把他输出一下
System.out.println(diagnostic);
};
}
结果 就像我们编译错误一样
3.动态编译和加载,调用
我们的我们要加载的代码
/**
* 我们要加载的类
*
*/
//我们把他放在C:\\CalledClass.java 下 ,并且没有使用包名
public class CalledClass implements Runnable{
public void run() {
System.out.println("CalledClass");
}
}
我们主类
package liusheng.main.hello.dynamic;
import javax.tools.*;
import java.io.*;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Set;
import java.util.concurrent.ConcurrentSkipListSet;
public class DynamicTest {
private final Writer out;
private final JavaFileManager fileManager;
private final DiagnosticListener<? super JavaFileObject> diagnosticListener;
private final Iterable<String> options;
private final Iterable<String> classes;
private final Iterable<? extends JavaFileObject> compilationUnits;
private final static JavaCompiler COMPILER = ToolProvider.getSystemJavaCompiler();
public DynamicTest(Writer out, JavaFileManager fileManager, DiagnosticListener<? super JavaFileObject> diagnosticListener, Iterable<String> options, Iterable<String> classes, Iterable<? extends JavaFileObject> compilationUnits) {
this.out = out;
this.fileManager = fileManager;
this.diagnosticListener = diagnosticListener;
this.options = options;
this.classes = classes;
this.compilationUnits = compilationUnits;
}
public boolean compile() {
JavaCompiler.CompilationTask task = COMPILER.getTask(out, fileManager, diagnosticListener, options, classes, compilationUnits);
// 同步调用
return task.call();
}
public static void main(String[] args) throws IOException {
//我们文件路径
String path = "C:\\CalledClass.java";
String content = readContext(path);
// 自定义 JavaFileManager 方便自定义输入和输出
MyJavaFileManager myJavaFileManager = new MyJavaFileManager(COMPILER.getStandardFileManager(null, null, StandardCharsets.UTF_8));
DynamicTest dynamicTest = new DynamicTest(null
, myJavaFileManager
, System.out::println, null, null, Arrays.asList(new StringJavaFileObject(content, "CalledClass")));
boolean b = dynamicTest.compile();
myJavaFileManager.getByteArrayJavaFileObjects().forEach(b1->{
try {
ByteArrayOutputStream o= (ByteArrayOutputStream) b1.openOutputStream();
// 获取字节码
byte[] classByteArray = o.toByteArray();
// 加载对象 ,这里是有问题的,我们应该 是在ClassLoader 中编译,并加载
ClassLoader loader = new ClassLoader() {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
return defineClass(name, classByteArray, 0, classByteArray.length);
}
};
Class<?> clazz = loader.loadClass("CalledClass");
if (Runnable.class.isAssignableFrom(clazz)){
Runnable instance = (Runnable) clazz.newInstance();
instance.run();
}
} catch (IOException e) {
e.printStackTrace();
} catch (ReflectiveOperationException e) {
e.printStackTrace();
}
});
System.out.println(b ? "成功" : "失败");
}
private static String readContext(String path) throws IOException {
byte[] bytes = Files.readAllBytes(Paths.get(path));
return new String(bytes);
}
//输入对象
static class StringJavaFileObject extends SimpleJavaFileObject {
private String content;
/**
* Construct a SimpleJavaFileObject of the given kind and with the
* given URI.
*/
public StringJavaFileObject(String content, String className) {
super(URI.create("string:///" + className.replace(".", "/") + ".java"), Kind.SOURCE);
this.content = content;
}
@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
return content;
}
}
// 输出对象
static class ByteArrayJavaFileObject extends SimpleJavaFileObject {
/**
* Construct a SimpleJavaFileObject of the given kind and with the
* given URI.
*/
private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
public ByteArrayJavaFileObject(String name) {
super(URI.create("bytes:///" + name.replace(".", "/") + ".class"), Kind.CLASS);
}
@Override
public OutputStream openOutputStream() throws IOException {
return outputStream;
}
}
static class MyJavaFileManager extends ForwardingJavaFileManager<JavaFileManager> {
// 就是一个装饰着模式 ForwardingJavaFileManager
/**
* Creates a new instance of ForwardingJavaFileManager.
*
* @param fileManager delegate to this file manager
*/
public MyJavaFileManager(JavaFileManager fileManager) {
super(fileManager);
}
private final Set<ByteArrayJavaFileObject> byteArrayJavaFileObjects = new ConcurrentSkipListSet();
public Set<ByteArrayJavaFileObject> getByteArrayJavaFileObjects() {
return byteArrayJavaFileObjects;
}
// 有字节码的输出的时候 我们自定义一个JavaFileObject 来接受输出了
@Override
public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
if (JavaFileObject.Kind.CLASS == kind) {
ByteArrayJavaFileObject byteArrayJavaFileObject = new ByteArrayJavaFileObject(className);
byteArrayJavaFileObjects.add(byteArrayJavaFileObject);
return byteArrayJavaFileObject;
} else {
return super.getJavaFileForOutput(location, className, kind, sibling);
}
}
}
}
结果 :
3.总结
以上就是我使用JavaCompiler 的过程,我们就动态生成源码并加载(动态代理) 用的比较多。 以为就是我个人分享,有什么错误希望大佬指正。