mirror of
https://gitee.com/dromara/hutool.git
synced 2025-04-05 17:37:59 +08:00
commit
f126c6ec34
@ -0,0 +1,105 @@
|
||||
package cn.hutool.core.compiler;
|
||||
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
|
||||
import javax.tools.FileObject;
|
||||
import javax.tools.ForwardingJavaFileManager;
|
||||
import javax.tools.JavaFileManager;
|
||||
import javax.tools.JavaFileObject;
|
||||
import javax.tools.JavaFileObject.Kind;
|
||||
import java.io.InputStream;
|
||||
import java.security.SecureClassLoader;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Java 字节码文件对象
|
||||
* 正常我们使用javac命令编译源码时会将class文件写入到磁盘中,但在运行时动态编译类不适合保存在磁盘中
|
||||
* 我们采取此对象来管理运行时动态编译类生成的字节码
|
||||
*
|
||||
* @author lzpeng
|
||||
* @see JavaSourceCompilerBak#compile()
|
||||
* @see com.sun.tools.javac.api.ClientCodeWrapper.WrappedJavaFileManager#getJavaFileForOutput(javax.tools.JavaFileManager.Location, java.lang.String, javax.tools.JavaFileObject.Kind, javax.tools.FileObject)
|
||||
*/
|
||||
final class JavaClassFileManager extends ForwardingJavaFileManager<JavaFileManager> {
|
||||
|
||||
/**
|
||||
* 存储java字节码文件对象映射
|
||||
*/
|
||||
private final Map<String, JavaFileObject> javaFileObjectMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* 加载动态编译生成类的父类加载器
|
||||
*/
|
||||
private final ClassLoader parent;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param parent 父类加载器
|
||||
* @param fileManager 字节码文件管理器
|
||||
* @see JavaSourceCompilerBak#compile()
|
||||
*/
|
||||
protected JavaClassFileManager(final ClassLoader parent, final JavaFileManager fileManager) {
|
||||
super(fileManager);
|
||||
if (parent == null) {
|
||||
this.parent = Thread.currentThread().getContextClassLoader();
|
||||
} else {
|
||||
this.parent = parent;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得动态编译生成的类的类加载器
|
||||
*
|
||||
* @param location 源码位置
|
||||
* @return 动态编译生成的类的类加载器
|
||||
* @see JavaSourceCompilerBak#compile()
|
||||
*/
|
||||
@Override
|
||||
public ClassLoader getClassLoader(final Location location) {
|
||||
return new SecureClassLoader(parent) {
|
||||
|
||||
/**
|
||||
* 查找类
|
||||
* @param name 类名
|
||||
* @return 类的class对象
|
||||
* @throws ClassNotFoundException 未找到类异常
|
||||
*/
|
||||
@Override
|
||||
protected Class<?> findClass(final String name) throws ClassNotFoundException {
|
||||
final JavaFileObject javaFileObject = javaFileObjectMap.get(name);
|
||||
if (javaFileObject != null) {
|
||||
try {
|
||||
final InputStream inputStream = javaFileObject.openInputStream();
|
||||
final byte[] bytes = IoUtil.readBytes(inputStream);
|
||||
final Class<?> c = defineClass(name, bytes, 0, bytes.length);
|
||||
return c;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
throw new ClassNotFoundException(name);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得Java字节码文件对象
|
||||
* 编译器编译源码时会将Java源码对象编译转为Java字节码对象
|
||||
*
|
||||
* @param location 源码位置
|
||||
* @param className 类名
|
||||
* @param kind 文件类型
|
||||
* @param sibling 将Java源码对象
|
||||
* @return Java字节码文件对象
|
||||
* @see com.sun.tools.javac.api.lientCodeWrapper.WrappedJavaFileManager#getJavaFileForOutput(javax.tools.JavaFileManager.Location, java.lang.String, javax.tools.JavaFileObject.Kind, javax.tools.FileObject)
|
||||
*/
|
||||
@Override
|
||||
public JavaFileObject getJavaFileForOutput(final Location location, final String className, final Kind kind, final FileObject sibling) {
|
||||
final JavaFileObject javaFileObject = new JavaClassFileObject(className, kind);
|
||||
javaFileObjectMap.put(className, javaFileObject);
|
||||
return javaFileObject;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
package cn.hutool.core.compiler;
|
||||
|
||||
|
||||
import javax.tools.SimpleJavaFileObject;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* Java 字节码文件对象
|
||||
*
|
||||
* @author lzpeng
|
||||
* @see JavaClassFileManager#getClassLoader(javax.tools.JavaFileManager.Location
|
||||
* @see JavaClassFileManager#getJavaFileForOutput(javax.tools.JavaFileManager.Location, java.lang.String, javax.tools.JavaFileObject.Kind, javax.tools.FileObject)
|
||||
* @see com.sun.tools.javac.jvm.ClassWriter.ClassWriter#writeClass(com.sun.tools.javac.code.Symbol.ClassSymbol)
|
||||
*/
|
||||
final class JavaClassFileObject extends SimpleJavaFileObject {
|
||||
|
||||
/**
|
||||
* 字节码输出流
|
||||
*/
|
||||
private final ByteArrayOutputStream byteArrayOutputStream;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param className 需要编译的类名
|
||||
* @param kind 需要编译的文件类型
|
||||
* @see JavaClassFileManager#getJavaFileForOutput(javax.tools.JavaFileManager.Location, java.lang.String, javax.tools.JavaFileObject.Kind, javax.tools.FileObject)
|
||||
*/
|
||||
protected JavaClassFileObject(final String className, final Kind kind) {
|
||||
super(URI.create("string:///" + className.replaceAll("\\.", "/") + kind.extension), kind);
|
||||
this.byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得字节码输入流
|
||||
* 编译器编辑源码后,我们将通过此输出流获得编译后的字节码,以便运行时加载类
|
||||
*
|
||||
* @return 字节码输入流
|
||||
* @see JavaClassFileManager#getClassLoader(javax.tools.JavaFileManager.Location)
|
||||
*/
|
||||
@Override
|
||||
public InputStream openInputStream() {
|
||||
final byte[] bytes = byteArrayOutputStream.toByteArray();
|
||||
return new ByteArrayInputStream(bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得字节码输出流
|
||||
* 编译器编辑源码时,会将编译结果输出到本输出流中
|
||||
*
|
||||
* @return 字节码输出流
|
||||
* @see com.sun.tools.javac.jvm.ClassWriter.ClassWriter#writeClass(com.sun.tools.javac.code.Symbol.ClassSymbol)
|
||||
*/
|
||||
@Override
|
||||
public OutputStream openOutputStream() {
|
||||
return this.byteArrayOutputStream;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,292 @@
|
||||
package cn.hutool.core.compiler;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.URLUtil;
|
||||
|
||||
import javax.tools.*;
|
||||
import javax.tools.JavaCompiler.CompilationTask;
|
||||
import javax.tools.JavaFileObject.Kind;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
/**
|
||||
* Java 源码编译器
|
||||
*
|
||||
* @author lzpeng
|
||||
*/
|
||||
public final class JavaSourceCompiler {
|
||||
|
||||
/**
|
||||
* java 编译器
|
||||
*/
|
||||
private static final JavaCompiler JAVA_COMPILER = ToolProvider.getSystemJavaCompiler();
|
||||
|
||||
/**
|
||||
* 待编译的文件 可以是 .java文件 压缩文件 文件夹 递归搜索文件夹内的zip包和jar包
|
||||
*/
|
||||
private final List<File> sourceFileList = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 编译时需要加入classpath中的文件 可以是 压缩文件 文件夹递归搜索文件夹内的zip包和jar包
|
||||
*/
|
||||
private final List<File> libraryFileList = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 源码映射 key: 类名 value: 类源码
|
||||
*/
|
||||
private final Map<String, String> sourceCodeMap = new LinkedHashMap<>();
|
||||
|
||||
/**
|
||||
* 编译类时使用的父类加载器
|
||||
*/
|
||||
private final ClassLoader parentClassLoader;
|
||||
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param parent 父类加载器
|
||||
*/
|
||||
private JavaSourceCompiler(ClassLoader parent) {
|
||||
this.parentClassLoader = parent;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 创建Java源码编译器
|
||||
*
|
||||
* @param parent 父类加载器
|
||||
* @return Java源码编译器
|
||||
*/
|
||||
public static JavaSourceCompiler create(ClassLoader parent) {
|
||||
return new JavaSourceCompiler(parent);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 向编译器中加入待编译的文件 支持 .java, 文件夹, 压缩文件 递归搜索文件夹内的压缩文件和jar包
|
||||
*
|
||||
* @param files 待编译的文件 支持 .java, 文件夹, 压缩文件 递归搜索文件夹内的压缩文件和jar包
|
||||
* @return Java源码编译器
|
||||
*/
|
||||
public JavaSourceCompiler addSource(final File... files) {
|
||||
if (ArrayUtil.isNotEmpty(files)) {
|
||||
this.sourceFileList.addAll(Arrays.asList(files));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 向编译器中加入待编译的源码Map
|
||||
*
|
||||
* @param sourceCodeMap 源码Map key: 类名 value 源码
|
||||
* @return Java源码编译器
|
||||
*/
|
||||
public JavaSourceCompiler addSource(final Map<String, String> sourceCodeMap) {
|
||||
if (MapUtil.isNotEmpty(sourceCodeMap)) {
|
||||
this.sourceCodeMap.putAll(sourceCodeMap);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加入编译Java源码时所需要的jar包
|
||||
*
|
||||
* @param files 编译Java源码时所需要的jar包
|
||||
* @return Java源码编译器
|
||||
*/
|
||||
public JavaSourceCompiler addLibrary(final File... files) {
|
||||
if (ArrayUtil.isNotEmpty(files)) {
|
||||
this.libraryFileList.addAll(Arrays.asList(files));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 向编译器中加入待编译的源码Map
|
||||
*
|
||||
* @param className 类名
|
||||
* @param sourceCode 源码
|
||||
* @return Java文件编译器
|
||||
*/
|
||||
public JavaSourceCompiler addSource(final String className, final String sourceCode) {
|
||||
if (className != null && sourceCode != null) {
|
||||
this.sourceCodeMap.put(className, sourceCode);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 编译所有文件并返回类加载器
|
||||
*
|
||||
* @return 类加载器
|
||||
*/
|
||||
public ClassLoader compile() {
|
||||
final ClassLoader parent;
|
||||
if (this.parentClassLoader == null) {
|
||||
parent = Thread.currentThread().getContextClassLoader();
|
||||
} else {
|
||||
parent = this.parentClassLoader;
|
||||
}
|
||||
// 获得classPath
|
||||
final List<File> classPath = getClassPath();
|
||||
final URL[] urLs = URLUtil.getURLs(classPath.toArray(new File[0]));
|
||||
final URLClassLoader ucl = URLClassLoader.newInstance(urLs, parent);
|
||||
if (sourceCodeMap.isEmpty() && sourceFileList.isEmpty()) {
|
||||
// 没有需要编译的源码
|
||||
return ucl;
|
||||
}
|
||||
// 没有需要编译的源码文件返回加载zip或jar包的类加载器
|
||||
final Iterable<JavaFileObject> javaFileObjectList = getJavaFileObject();
|
||||
// 创建编译器
|
||||
final JavaFileManager standardJavaFileManager = JAVA_COMPILER.getStandardFileManager(null, null, null);
|
||||
final JavaFileManager javaFileManager = new JavaClassFileManager(ucl, standardJavaFileManager);
|
||||
final DiagnosticCollector<? super JavaFileObject> diagnosticCollector = new DiagnosticCollector<>();
|
||||
final List<String> options = new ArrayList<>();
|
||||
if (!classPath.isEmpty()) {
|
||||
final List<String> cp = classPath.stream().map(File::getAbsolutePath).collect(Collectors.toList());
|
||||
options.add("-cp");
|
||||
options.addAll(cp);
|
||||
}
|
||||
// 编译文件
|
||||
final CompilationTask task = JAVA_COMPILER.getTask(null, javaFileManager, diagnosticCollector,
|
||||
options, null, javaFileObjectList);
|
||||
final Boolean result = task.call();
|
||||
if (Boolean.TRUE.equals(result)) {
|
||||
return javaFileManager.getClassLoader(StandardLocation.CLASS_OUTPUT);
|
||||
} else {
|
||||
// 编译失败,收集错误信息
|
||||
final List<?> diagnostics = diagnosticCollector.getDiagnostics();
|
||||
final String errorMsg = diagnostics.stream().map(String::valueOf)
|
||||
.collect(Collectors.joining(System.lineSeparator()));
|
||||
// CompileException
|
||||
throw new RuntimeException(errorMsg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得编译源码时需要的classpath
|
||||
*
|
||||
* @return 编译源码时需要的classpath
|
||||
*/
|
||||
private List<File> getClassPath() {
|
||||
List<File> classPathFileList = new ArrayList<>();
|
||||
for (File file : libraryFileList) {
|
||||
List<File> jarOrZipFile = FileUtil.loopFiles(file, this::isJarOrZipFile);
|
||||
classPathFileList.addAll(jarOrZipFile);
|
||||
if (file.isDirectory()) {
|
||||
classPathFileList.add(file);
|
||||
}
|
||||
}
|
||||
return classPathFileList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得待编译的Java文件对象
|
||||
*
|
||||
* @return 待编译的Java文件对象
|
||||
*/
|
||||
private Iterable<JavaFileObject> getJavaFileObject() {
|
||||
final Collection<JavaFileObject> collection = new ArrayList<>();
|
||||
for (File file : sourceFileList) {
|
||||
// .java 文件
|
||||
final List<File> javaFileList = FileUtil.loopFiles(file, this::isJavaFile);
|
||||
for (File javaFile : javaFileList) {
|
||||
collection.add(getJavaFileObjectByJavaFile(javaFile));
|
||||
}
|
||||
// 压缩包
|
||||
final List<File> jarOrZipFileList = FileUtil.loopFiles(file, this::isJarOrZipFile);
|
||||
for (File jarOrZipFile : jarOrZipFileList) {
|
||||
collection.addAll(getJavaFileObjectByZipOrJarFile(jarOrZipFile));
|
||||
}
|
||||
}
|
||||
// 源码Map
|
||||
collection.addAll(getJavaFileObjectByMap(this.sourceCodeMap));
|
||||
return collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过源码Map获得Java文件对象
|
||||
*
|
||||
* @param sourceCodeMap 源码Map
|
||||
* @return Java文件对象集合
|
||||
*/
|
||||
private Collection<JavaFileObject> getJavaFileObjectByMap(final Map<String, String> sourceCodeMap) {
|
||||
if (MapUtil.isNotEmpty(sourceCodeMap)) {
|
||||
return sourceCodeMap.entrySet().stream()
|
||||
.map(entry -> new JavaSourceFileObject(entry.getKey(), entry.getValue(), Kind.SOURCE))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过.java文件创建Java文件对象
|
||||
*
|
||||
* @param file .java文件
|
||||
* @return Java文件对象
|
||||
*/
|
||||
private JavaFileObject getJavaFileObjectByJavaFile(final File file) {
|
||||
return new JavaSourceFileObject(file.toURI(), Kind.SOURCE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过zip包或jar包创建Java文件对象
|
||||
*
|
||||
* @param file 压缩文件
|
||||
* @return Java文件对象
|
||||
*/
|
||||
private Collection<JavaFileObject> getJavaFileObjectByZipOrJarFile(final File file) {
|
||||
final Collection<JavaFileObject> collection = new ArrayList<>();
|
||||
try {
|
||||
final ZipFile zipFile = new ZipFile(file);
|
||||
final Enumeration<? extends ZipEntry> entries = zipFile.entries();
|
||||
while (entries.hasMoreElements()) {
|
||||
final ZipEntry zipEntry = entries.nextElement();
|
||||
final String name = zipEntry.getName();
|
||||
if (name.endsWith(".java")) {
|
||||
final InputStream inputStream = zipFile.getInputStream(zipEntry);
|
||||
final JavaSourceFileObject fileObject = new JavaSourceFileObject(name, inputStream, Kind.SOURCE);
|
||||
collection.add(fileObject);
|
||||
}
|
||||
}
|
||||
return collection;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 是否是jar 或 zip 文件
|
||||
*
|
||||
* @param file 文件
|
||||
* @return 是否是jar 或 zip 文件
|
||||
*/
|
||||
private boolean isJarOrZipFile(final File file) {
|
||||
final String fileName = file.getName();
|
||||
return fileName.endsWith(".jar") || fileName.endsWith(".zip");
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否是.java文件
|
||||
*
|
||||
* @param file 文件
|
||||
* @return 是否是.java文件
|
||||
*/
|
||||
private boolean isJavaFile(final File file) {
|
||||
final String fileName = file.getName();
|
||||
return fileName.endsWith(".java");
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
package cn.hutool.core.compiler;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import javax.tools.SimpleJavaFileObject;
|
||||
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
|
||||
/**
|
||||
* Java 源码文件对象
|
||||
*
|
||||
* @author lzpeng
|
||||
* @see JavaSourceCompilerBak#getJavaFileObjectByJavaFile(java.io.File)
|
||||
* @see JavaSourceCompilerBak#getJavaFileObjectByZipOrJarFile(java.io.File)
|
||||
* @see JavaSourceCompilerBak#getJavaFileObject(java.util.Map)
|
||||
* @see com.sun.tools.javac.api.ClientCodeWrapper.WrappedFileObject#getCharContent(boolean)
|
||||
*/
|
||||
final class JavaSourceFileObject extends SimpleJavaFileObject {
|
||||
|
||||
/**
|
||||
* 输入流
|
||||
*/
|
||||
private InputStream inputStream;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param uri 需要编译的文件uri
|
||||
* @param kind 需要编译的文件类型
|
||||
* @see JavaSourceCompilerBak#getJavaFileObjectByJavaFile(java.io.File)
|
||||
*/
|
||||
protected JavaSourceFileObject(URI uri, Kind kind) {
|
||||
super(uri, kind);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param name 需要编译的文件名
|
||||
* @param inputStream 输入流
|
||||
* @param kind 需要编译的文件类型
|
||||
* @see JavaSourceCompilerBak#getJavaFileObjectByZipOrJarFile(java.io.File)
|
||||
*/
|
||||
protected JavaSourceFileObject(final String name, final InputStream inputStream, final Kind kind) {
|
||||
super(URI.create("string:///" + name), kind);
|
||||
this.inputStream = inputStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param className 需要编译的类名
|
||||
* @param code 需要编译的类源码
|
||||
* @param kind 需要编译的文件类型
|
||||
* @see JavaSourceCompilerBak#getJavaFileObject(java.util.Map)
|
||||
*/
|
||||
protected JavaSourceFileObject(final String className, final String code, final Kind kind) {
|
||||
super(URI.create("string:///" + className.replaceAll("\\.", "/") + kind.extension), kind);
|
||||
this.inputStream = new ByteArrayInputStream(code.getBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得类源码的输入流
|
||||
*
|
||||
* @return 类源码的输入流
|
||||
* @throws IOException IO 异常
|
||||
*/
|
||||
@Override
|
||||
public InputStream openInputStream() throws IOException {
|
||||
if (inputStream == null) {
|
||||
inputStream = toUri().toURL().openStream();
|
||||
}
|
||||
return new BufferedInputStream(inputStream);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得类源码
|
||||
* 编译器编辑源码前,会通过此方法获取类的源码
|
||||
*
|
||||
* @param ignoreEncodingErrors 是否忽略编码错误
|
||||
* @return 需要编译的类的源码
|
||||
* @throws IOException IO异常
|
||||
* @see com.sun.tools.javac.api.ClientCodeWrapper.WrappedFileObject#getCharContent(boolean)
|
||||
*/
|
||||
@Override
|
||||
public CharSequence getCharContent(final boolean ignoreEncodingErrors) throws IOException {
|
||||
final InputStream in = openInputStream();
|
||||
final String code = IoUtil.read(in, Charset.defaultCharset());
|
||||
IoUtil.close(in);
|
||||
return code;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
/**
|
||||
* 运行时编译java源码,动态从字符串或外部文件加载类
|
||||
*
|
||||
* @author : Lzpeng
|
||||
*/
|
||||
package cn.hutool.core.compiler;
|
@ -0,0 +1,42 @@
|
||||
package cn.hutool.core.compiler;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.hutool.core.util.ZipUtil;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Java源码编译器测试
|
||||
*
|
||||
* @author lzpeng
|
||||
*/
|
||||
public class JavaSourceCompilerTest {
|
||||
|
||||
/**
|
||||
* 测试编译Java源码
|
||||
*/
|
||||
@Test
|
||||
public void testCompile() throws ClassNotFoundException {
|
||||
final File libFile = ZipUtil.zip(FileUtil.file("lib.jar"),
|
||||
new String[]{"a/A.class", "a/A$1.class", "a/A$InnerClass.class"},
|
||||
new InputStream[]{
|
||||
FileUtil.getInputStream("test-compile/a/A.class"),
|
||||
FileUtil.getInputStream("test-compile/a/A$1.class"),
|
||||
FileUtil.getInputStream("test-compile/a/A$InnerClass.class")
|
||||
});
|
||||
final ClassLoader classLoader = JavaSourceCompiler.create(null)
|
||||
.addSource(FileUtil.file("test-compile/b/B.java"))
|
||||
.addSource("c.C", FileUtil.readUtf8String("test-compile/c/C.java"))
|
||||
.addLibrary(libFile)
|
||||
.compile();
|
||||
final Class<?> clazz = classLoader.loadClass("c.C");
|
||||
Object obj = ReflectUtil.newInstance(clazz);
|
||||
Assert.assertTrue(String.valueOf(obj).startsWith("c.C@"));
|
||||
FileUtil.del(libFile);
|
||||
}
|
||||
|
||||
}
|
BIN
hutool-core/src/test/resources/test-compile/a/A$1.class
Normal file
BIN
hutool-core/src/test/resources/test-compile/a/A$1.class
Normal file
Binary file not shown.
BIN
hutool-core/src/test/resources/test-compile/a/A$InnerClass.class
Normal file
BIN
hutool-core/src/test/resources/test-compile/a/A$InnerClass.class
Normal file
Binary file not shown.
BIN
hutool-core/src/test/resources/test-compile/a/A.class
Normal file
BIN
hutool-core/src/test/resources/test-compile/a/A.class
Normal file
Binary file not shown.
24
hutool-core/src/test/resources/test-compile/a/A.java
Normal file
24
hutool-core/src/test/resources/test-compile/a/A.java
Normal file
@ -0,0 +1,24 @@
|
||||
package a;
|
||||
|
||||
import cn.hutool.core.lang.ConsoleTable;
|
||||
import cn.hutool.core.lang.caller.CallerUtil;
|
||||
|
||||
public class A {
|
||||
private class InnerClass {
|
||||
}
|
||||
|
||||
public A() {
|
||||
new InnerClass() {{
|
||||
int i = 0;
|
||||
Class<?> caller = CallerUtil.getCaller(i);
|
||||
final ConsoleTable t = new ConsoleTable();
|
||||
t.addHeader("类名", "类加载器");
|
||||
System.out.println("初始化 " + getClass() + " 的调用链为: ");
|
||||
while (caller != null) {
|
||||
t.addBody(caller.toString(), caller.getClassLoader().toString());
|
||||
caller = CallerUtil.getCaller(++i);
|
||||
}
|
||||
t.print();
|
||||
}};
|
||||
}
|
||||
}
|
BIN
hutool-core/src/test/resources/test-compile/b/B.class
Normal file
BIN
hutool-core/src/test/resources/test-compile/b/B.class
Normal file
Binary file not shown.
8
hutool-core/src/test/resources/test-compile/b/B.java
Normal file
8
hutool-core/src/test/resources/test-compile/b/B.java
Normal file
@ -0,0 +1,8 @@
|
||||
package b;
|
||||
import a.A;
|
||||
|
||||
public class B {
|
||||
public B() {
|
||||
new A();
|
||||
}
|
||||
}
|
BIN
hutool-core/src/test/resources/test-compile/c/C.class
Normal file
BIN
hutool-core/src/test/resources/test-compile/c/C.class
Normal file
Binary file not shown.
9
hutool-core/src/test/resources/test-compile/c/C.java
Normal file
9
hutool-core/src/test/resources/test-compile/c/C.java
Normal file
@ -0,0 +1,9 @@
|
||||
package c;
|
||||
|
||||
import b.B;
|
||||
|
||||
public class C {
|
||||
public C() {
|
||||
new B();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user