目录
1.创建自定义类加载器
public class DynamicClassLoader extends ClassLoader {
/**
* 已编译的class字节码
*/
private final Map<String, ByteJavaFileObject> byteCodes = new HashMap<>();
public DynamicClassLoader(ClassLoader classLoader) {
super(classLoader);
}
@Override
protected Class<?> findClass(String name) {
Class<?> result = null;
byte[] bytes = null;
//从内存中获取
ByteJavaFileObject fileObject = byteCodes.get(name);
if (fileObject == null) {
//查找class文件 编译完成后我这会将class文件保存下来,所以这里会查找指定路径的文件。
File file = new File(String.format(Constants.DYNAMIC_CLASS_PATH, name.replace(".", "/")));
if (file.exists()) {
bytes = FileUtil.readBytes(file);
}
} else {
bytes = fileObject.getByteCode();
}
if (bytes != null) {
result = super.defineClass(name, bytes, 0, bytes.length);
}
return result;
}
public void addCompiledSource(ByteJavaFileObject byteCode) {
byteCodes.put(byteCode.getClassName(), byteCode);
}
public Map<String, Class<?>> getClasses() {
Map<String, Class<?>> classes = new HashMap<>(byteCodes.size());
for (ByteJavaFileObject byteCode : byteCodes.values()) {
classes.put(byteCode.getClassName(), findClass(byteCode.getClassName()));
}
return classes;
}
public Class<?> getClasses(String className) {
return findClass(className);
}
public Map<String, byte[]> getByteCodes() {
Map<String, byte[]> result = new HashMap<>(byteCodes.size());
for (Map.Entry<String, ByteJavaFileObject> entry : byteCodes.entrySet()) {
result.put(entry.getKey(), entry.getValue().getByteCode());
}
return result;
}
}
2.创建自定义JAVA文件对象
public class CustomJavaFileObject implements JavaFileObject {
/**
* 文件名称
*/
private final String binaryName;
/**
* JAR URI
*/
private final URI uri;
private final String name;
public CustomJavaFileObject(String binaryName, URI uri) {
this.uri = uri;
this.binaryName = binaryName;
name = uri.getPath() == null ? uri.getSchemeSpecificPart() : uri.getPath();
}
public URI toUri() {
return uri;
}
public InputStream openInputStream() throws IOException {
return uri.toURL().openStream();
}
public OutputStream openOutputStream() {
throw new UnsupportedOperationException();
}
public String getName() {
return name;
}
public Reader openReader(boolean ignoreEncodingErrors) {
throw new UnsupportedOperationException();
}
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
throw new UnsupportedOperationException();
}
public Writer openWriter() throws IOException {
throw new UnsupportedOperationException();
}
public long getLastModified() {
return 0;
}
public boolean delete() {
throw new UnsupportedOperationException();
}
public JavaFileObject.Kind getKind() {
return JavaFileObject.Kind.CLASS;
}
public boolean isNameCompatible(String simpleName, JavaFileObject.Kind kind) {
String baseName = simpleName + kind.extension;
return kind.equals(getKind())
&& (baseName.equals(getName())
|| getName().endsWith("/" + baseName));
}
public NestingKind getNestingKind() {
throw new UnsupportedOperationException();
}
public Modifier getAccessLevel() {
throw new UnsupportedOperationException();
}
public String binaryName() {
return binaryName;
}
public String toString() {
return this.getClass().getName() + "[" + this.toUri() + "]";
}
}
3.查找指定包中的所有类
public class PackageInternalsFinder {
/**
* 自定义类加载器
*/
private final DynamicClassLoader classLoader;
/**
* 文件后缀
*/
private static final String CLASS_FILE_EXTENSION = ".class";
public PackageInternalsFinder(DynamicClassLoader classLoader) {
this.classLoader = classLoader;
}
/**
* 查找指定包下所有的java文件对象
* @param packageName 包路径
* @return java文件对象
* @throws IOException
*/
public List<JavaFileObject> find(String packageName) throws IOException {
String javaPackageName = packageName.replaceAll("\\.", "/");
List<JavaFileObject> result = new ArrayList<>();
Enumeration<URL> urlEnumeration = classLoader.getResources(javaPackageName);
while (urlEnumeration.hasMoreElements()) {
//类路径上每个jar都有一个URL
URL packageFolderURL = urlEnumeration.nextElement();
//获取jar包里面的类
result.addAll(listUnder(packageName, packageFolderURL));
}
return result;
}
private Collection<JavaFileObject> listUnder(String packageName, URL packageFolderURL) {
File directory = new File(decode(packageFolderURL.getFile()));
//是一个文件夹
if (directory.isDirectory()) {
return processDir(packageName, directory);
}
//是一个jar包
else {
return processJar(packageFolderURL);
}
}
/**
* 获取jar包里面的类
* @param packageFolderURL jar url
* @return java文件对象集合
*/
private List<JavaFileObject> processJar(URL packageFolderURL) {
List<JavaFileObject> result = new ArrayList<>();
try {
String jarUri = packageFolderURL.toExternalForm().substring(0, packageFolderURL.toExternalForm().lastIndexOf("!/"));
//创建获取jar包资源对象
JarURLConnection jarConn = (JarURLConnection) packageFolderURL.openConnection();
//jar包根目录名称
String rootEntryName = jarConn.getEntryName();
int rootEnd = rootEntryName.length() + 1;
//获取jar文件
Enumeration<JarEntry> entryEnum = jarConn.getJarFile().entries();
//遍历文件
while (entryEnum.hasMoreElements()) {
JarEntry jarEntry = entryEnum.nextElement();
//资源名称
String name = jarEntry.getName();
//当前目录下的class文件
if (name.startsWith(rootEntryName) && name.indexOf('/', rootEnd) == -1 && name.endsWith(CLASS_FILE_EXTENSION)) {
URI uri = URI.create(jarUri + "!/" + name);
String binaryName = name.replaceAll("/", ".");
binaryName = binaryName.replaceAll(CLASS_FILE_EXTENSION + "$", "");
result.add(new CustomJavaFileObject(binaryName, uri));
}
}
} catch (Exception e) {
throw new RuntimeException("Wasn't able to open " + packageFolderURL + " as a jar file", e);
}
return result;
}
/**
* 获取文件夹里面的类
* @param packageName 包名
* @param directory 文件夹
* @return java文件对象集合
*/
private List<JavaFileObject> processDir(String packageName, File directory) {
List<JavaFileObject> result = new ArrayList<JavaFileObject>();
//当前目录的子级
File[] childFiles = directory.listFiles();
if (childFiles != null) {
for (File childFile : childFiles) {
if (childFile.isFile()) {
//是class文件
if (childFile.getName().endsWith(CLASS_FILE_EXTENSION)) {
String binaryName = packageName + "." + childFile.getName();
binaryName = binaryName.replaceAll(CLASS_FILE_EXTENSION + "$", "");
result.add(new CustomJavaFileObject(binaryName, childFile.toURI()));
}
}
}
}
return result;
}
private String decode(String filePath) {
try {
return URLDecoder.decode(filePath, "utf-8");
} catch (Exception e) {
}
return filePath;
}
}
4.创建class字节码文件对象
public class ByteJavaFileObject extends SimpleJavaFileObject {
private static final char DIR_SEPARATOR = '/';
private static final char PKG_SEPARATOR = '.';
private ByteArrayOutputStream byteArrayOutputStream;
public ByteJavaFileObject(String className) {
super(URI.create("byte:///" + className.replace(PKG_SEPARATOR, DIR_SEPARATOR) + Kind.CLASS.extension), Kind.CLASS);
}
public String getClassName() {
String className = getName();
className = className.replace(DIR_SEPARATOR, PKG_SEPARATOR);
className = className.substring(1, className.indexOf(Kind.CLASS.extension));
return className;
}
@Override
public OutputStream openOutputStream() {
if (byteArrayOutputStream == null) {
byteArrayOutputStream = new ByteArrayOutputStream();
}
return byteArrayOutputStream;
}
public byte[] getByteCode() {
return byteArrayOutputStream.toByteArray();
}
}
5.创建自定义文件管理器
public class DynamicJavaFileManager extends ForwardingJavaFileManager<JavaFileManager> {
/**
* 运行环境中的资源路径
*/
private static final String[] superLocationNames = {StandardLocation.PLATFORM_CLASS_PATH.name(), "SYSTEM_MODULES"};
/**
* 用于查找给定包中的内部类
*/
private final PackageInternalsFinder finder;
/**
* 自定义类加载器
*/
private final DynamicClassLoader classLoader;
/**
* 初始化
* @param fileManager 文件管理器
* @param classLoader 类加载器
*/
public DynamicJavaFileManager(JavaFileManager fileManager, DynamicClassLoader classLoader) {
super(fileManager);
this.classLoader = classLoader;
finder = new PackageInternalsFinder(classLoader);
}
/**
* 获取一个用于输出的java文件对象
* 当你编译一个Java源文件时,编译器会生成一个或多个输出文件(例如.class文件)
* 方法用于获取这些输出文件的JavaFileObject,这样编译器就可以将编译的结果写入这些文件
* @param location a location
* @param className 类名
* @param kind 文件类型
* {@link JavaFileObject.Kind#SOURCE 源文件后缀}
* {@link JavaFileObject.Kind#CLASS class文件后缀}
* @param sibling 保存提示的文件对象
* @return java文件对象
*/
@Override
public JavaFileObject getJavaFileForOutput(JavaFileManager.Location location, String className,
JavaFileObject.Kind kind, FileObject sibling) {
ByteJavaFileObject byteJavaFileObject = new ByteJavaFileObject(className);
//保存编译后的class字节码到内存中
classLoader.addCompiledSource(byteJavaFileObject);
return byteJavaFileObject;
}
/**
* 获取自定义类加载器
* @param location a location
* @return 自定义类加载器
*/
@Override
public ClassLoader getClassLoader(JavaFileManager.Location location) {
return classLoader;
}
/**
* 推断二进制名称表示输出文件名称
* 输出文件的名称通常是源文件名加上.class后缀。然而,有时候你可能希望使用不同的名称来保存输出文件
* @param location a location
* @param file java文件对象
* @return 输出文件名称
*/
@Override
public String inferBinaryName(Location location, JavaFileObject file) {
//类型是自定义文件对象,输出默认名称
if (file instanceof CustomJavaFileObject) {
return ((CustomJavaFileObject) file).binaryName();
} else {
return super.inferBinaryName(location, file);
}
}
/**
* 列出指定位置的所有Java文件对象
* @param location a location
* @param packageName 包名
* @param kinds 文件类型
* @param recurse 是否包含子包
* @return Java文件对象集合
* @throws IOException
*/
@Override
public Iterable<JavaFileObject> list(Location location, String packageName, Set<JavaFileObject.Kind> kinds,
boolean recurse) throws IOException {
if (location instanceof StandardLocation) {
String locationName = ((StandardLocation) location).name();
for (String name : superLocationNames) {
if (name.equals(locationName)) {
return super.list(location, packageName, kinds, recurse);
}
}
}
// 从指定的ClassLoader合并JavaFileObjects
if (location == StandardLocation.CLASS_PATH && kinds.contains(JavaFileObject.Kind.CLASS)) {
return new DynamicJavaFileManager.IterableJoin<>(super.list(location, packageName, kinds, recurse),
finder.find(packageName));
}
return super.list(location, packageName, kinds, recurse);
}
static class IterableJoin<T> implements Iterable<T> {
private final Iterable<T> first, next;
public IterableJoin(Iterable<T> first, Iterable<T> next) {
this.first = first;
this.next = next;
}
@Override
public Iterator<T> iterator() {
return new DynamicJavaFileManager.IteratorJoin<T>(first.iterator(), next.iterator());
}
}
static class IteratorJoin<T> implements Iterator<T> {
private final Iterator<T> first, next;
public IteratorJoin(Iterator<T> first, Iterator<T> next) {
this.first = first;
this.next = next;
}
@Override
public boolean hasNext() {
return first.hasNext() || next.hasNext();
}
@Override
public T next() {
if (first.hasNext()) {
return first.next();
}
return next.next();
}
@Override
public void remove() {
throw new UnsupportedOperationException("remove");
}
}
}
6.创建自定义源文件对象
public class StringJavaFileObject extends SimpleJavaFileObject {
/**
* 代码片段
*/
private String contents;
public StringJavaFileObject(String className, String content) {
super(URI.create("string:///" + className.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE);
this.contents = content;
}
@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
return contents;
}
}
7.编译方法,准备编译
public class DynamicCompiler {
/**
* java编译器
*/
private final JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
/**
* 文件管理器
*/
private final StandardJavaFileManager standardFileManager;
/**
* 编译参数
*/
private final List<String> options = new ArrayList<String>();
/**
* 自定义类加载器
*/
private final DynamicClassLoader dynamicClassLoader;
/**
* 需要被编译的源文件
*/
private final Collection<JavaFileObject> compilationUnits = new ArrayList<>();
/**
* 编译错误信息
*/
private final List<Diagnostic<? extends JavaFileObject>> errors = new ArrayList<>();
/**
* 编译警告信息
*/
private final List<Diagnostic<? extends JavaFileObject>> warnings = new ArrayList<>();
public DynamicCompiler(ClassLoader classLoader) {
if (javaCompiler == null) {
throw new RuntimeException("动态编译器初始化错误");
}
standardFileManager = javaCompiler.getStandardFileManager(null, null, null);
options.add("-Xlint:unchecked");
dynamicClassLoader = new DynamicClassLoader(classLoader);
}
/**
* 添加源文件
*
* @param className 全路径类名
* @param sourceCode 源代码
*/
public void addSource(String className, String sourceCode) {
compilationUnits.add(new StringJavaFileObject(className, sourceCode));
}
/**
* 开始编译
* @return 自定义类加载器
*/
public DynamicClassLoader build() {
//清空异常信息
errors.clear();
warnings.clear();
//创建文件管理器
DynamicJavaFileManager dynamicJavaFileManager = new DynamicJavaFileManager(standardFileManager, dynamicClassLoader);
//创建编译监听器
DiagnosticCollector<JavaFileObject> diagnosticListener = new DiagnosticCollector<>();
//创建编译任务
JavaCompiler.CompilationTask task = javaCompiler.getTask(null, dynamicJavaFileManager, diagnosticListener, options, null, compilationUnits);
try {
if (!compilationUnits.isEmpty()) {
//开始编译
boolean result = task.call();
//编译失败或者存在异常信息
if (!result || diagnosticListener.getDiagnostics().size() > 0) {
for (Diagnostic<? extends JavaFileObject> diagnostic : diagnosticListener.getDiagnostics()) {
switch (diagnostic.getKind()) {
case NOTE:
case WARNING:
case MANDATORY_WARNING:
//添加编译警告信息
warnings.add(diagnostic);
break;
case ERROR:
case OTHER:
default:
//添加编译错误信息
errors.add(diagnostic);
break;
}
}
if (!warnings.isEmpty()) {
log.warn("动态编译警告:{}", diagnosticToString(warnings));
}
if (!errors.isEmpty()) {
log.warn("动态编译失败:{}", diagnosticToString(warnings));
}
}
}
return dynamicClassLoader;
} catch (Throwable e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
} finally {
compilationUnits.clear();
}
}
/**
* 编译异常信息转字符串
*
* @param diagnostics 编译异常信息
* @return 异常信息字符串
*/
private List<String> diagnosticToString(List<Diagnostic<? extends JavaFileObject>> diagnostics) {
List<String> diagnosticMessages = new ArrayList<>();
for (Diagnostic<? extends JavaFileObject> diagnostic : diagnostics) {
diagnosticMessages.add(
"line: " + diagnostic.getLineNumber() + ", message: " + diagnostic.getMessage(Locale.US));
}
return diagnosticMessages;
}
}
8.开始编译并执行
String code = "package com.example public class Example1 {\n" +
" public void execute() {\n" +
" System.out.println(\"动态编译代码片段成功执行\");\n" +
" }\n" +
"}";
DynamicCompiler dynamicCompiler = new DynamicCompiler(ClassLoader.getSystemClassLoader());
dynamicCompiler.addSource("com.example.Example1", code);
DynamicClassLoader classLoader = dynamicCompiler.build();
//执行方法
classLoader.getClasses().get("com.example.Example1")
Example1 example = (Example1) aClass.newInstance();
example.execute();
如果部署到服务器上之后编译执行报 ClassNotFoundException找不到类,请点击查看JAVA动态编译执行代码片段部署后报ClassNotFoundException找不到类
尝试解决