動態編譯運行java程序
動態編譯java有兩種方式,一種是使用Runtime類執行cmd命令編譯java,詳見:https://blog.csdn.net/rico_zhou/article/details/79873344
另一種是java自身提供的方法,下面我們來實踐,如何通過JavaCompiler實現java代碼動態編譯。
首先獲取編譯器,注意tools.jar在jdk中不在jre中,詳見:https://blog.csdn.net/rico_zhou/article/details/80725086
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
獲取標準文件管理器實例
// 獲取標準文件管理器實例
StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
獲取要編譯的編譯單元
// 獲取要編譯的編譯單元
Iterable<? extends JavaFileObject> compilationUnits = fileManager
.getJavaFileObjectsFromFiles(sourceFileList);
錯誤信息管理器
// 錯誤信息
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
編譯選項
// 編譯選項,在編譯java文件時,編譯程序會自動的去尋找java文件引用的其他的java源文件或者class。
// -sourcepath選項就是定義java源文件的查找目錄, -classpath選項就是定義class文件的查找目錄。
Iterable<String> options = Arrays.asList(CMDConstant.CMD_COMMAND_JAVACOMPILE_1, encoding,CMDConstant.CMD_COMMAND_JAVACOMPILE_2, jars,CMDConstant.CMD_COMMAND_JAVACOMPILE_3, targetDir,CMDConstant.CMD_COMMAND_JAVACOMPILE_4, sourceDir);
編譯任務
CompilationTask compilationTask = compiler.getTask(null, fileManager, diagnostics, options, null,compilationUnits);
執行編譯
compilationTask.call();
完整代碼,源碼見github
package dynamicrunjava;
import java.io.File;
import java.io.FileFilter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.swing.JOptionPane;
import javax.swing.JTextArea;
import javax.swing.SwingWorker;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import constant.CMDConstant;
import constant.CodingConstant;
import constant.CommonSymbolicConstant;
import constant.ConfigConstant;
import constant.FileExtensionConstant;
import constant.OtherConstant;
import constant.SpiderConstant;
public class CompilerUtils {
// 編譯,java文件目錄,jar包路徑,搜索資源路徑,編譯存放路徑,指定編譯器路徑
public static boolean compilerJAVACodeFile(String filePath, String jarPath, String sourceDir, String targetDir,
String javaCompilerPath) {
// 注意路徑不同
// 調用封裝好的編譯方法,需要提前準備好參數
// 參數分別是:// 編碼格式,jar包,需要編譯的目錄,關聯查找目錄,編譯後存放目錄,錯誤信息
// 編碼格式
String encoding = CodingConstant.CODING_UTF_8;
// jar包路徑拼接字符串
String jars = CommonSymbolicConstant.EMPTY_STRING;
// 開始挨個獲取
// 獲取jar包拼接字符串
try {
jars = getJarFiles(jarPath);
} catch (Exception e) {
e.printStackTrace();
jars = CommonSymbolicConstant.EMPTY_STRING;
}
// 錯誤信息
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
// 開始編譯
try {
if (!compiler(encoding, jars, filePath, filePath, targetDir, diagnostics, javaCompilerPath)) {
return false;
}
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
// 查找jar
public static String getJarFiles(String jarPath) throws Exception {
String jars = CommonSymbolicConstant.EMPTY_STRING;
File sourceFile = new File(jarPath);
if (sourceFile.exists()) {
if (sourceFile.isDirectory()) {
// 得到該目錄下以.jar結尾的文件或者目錄
File[] childrenFiles = sourceFile.listFiles();
jars = filterJarFile(childrenFiles);
}
}
return jars;
}
// 過濾文件
private static String filterJarFile(File[] childrenFiles) {
String jars = CommonSymbolicConstant.EMPTY_STRING;
for (File pathname : childrenFiles) {
if (!pathname.isDirectory()) {
String name = pathname.getName();
if (name.endsWith(FileExtensionConstant.FILE_EXTENSION_POINT_CODEFILE_JAR) ? true : false) {
jars = jars + pathname.getPath() + CommonSymbolicConstant.SEMICOLON;
}
}
}
return jars;
}
// 編碼格式,jar包,需要編譯的目錄,關聯查找目錄,編譯後存放目錄,錯誤信息
public static boolean compiler(String encoding, String jars, String filePath, String sourceDir, String targetDir,
DiagnosticCollector<JavaFileObject> diagnostics, String javaCompilerPath) throws Exception {
// 獲取編譯器實例
// JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
// 此處無法加載如果沒有安裝jdk,另爲了安全起見需要使用單獨的jre
// 解決方法一,通過手動加載tools.jar獲取相關類方法獲取編譯器再編譯
// 解決方法二,通過Runtime方法執行cmd命令設置Javahome
JavaCompiler compiler = null;
try {
// 獲取指定的編譯器(需要tools.jar)
File file = new File(javaCompilerPath == null ? "" : javaCompilerPath);
if (!file.exists()) {
// 獲取系統編譯器需要把jdk中的tool.jar複製到jre中,因爲自帶的jre沒有
// compiler = ToolProvider.getSystemJavaCompiler();
// 獲取系統變量
String javahome = System.getenv(ConfigConstant.CONFIG_ENV_JAVA_HOME);
file = new File(javahome + File.separator + ConfigConstant.CONFIG_DEFAULT_TOOLS_PATH);
if (!file.exists()) {
return false;
}
}
compiler = getJavaCompilerByLocation(file);
} catch (Exception e1) {
e1.printStackTrace();
return false;
}
// 獲取標準文件管理器實例
StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
try {
// 得到filePath目錄下的所有java源文件
File sourceFile = new File(filePath);
List<File> sourceFileList = new ArrayList<File>();
// 根據文件判斷是否是java文件,並存放List
getSourceFiles(sourceFile, sourceFileList);
// 沒有java文件,直接返回
if (sourceFileList.size() == 0) {
return false;
}
// 獲取要編譯的編譯單元
Iterable<? extends JavaFileObject> compilationUnits = fileManager
.getJavaFileObjectsFromFiles(sourceFileList);
// 編譯選項,在編譯java文件時,編譯程序會自動的去尋找java文件引用的其他的java源文件或者class。
// -sourcepath選項就是定義java源文件的查找目錄, -classpath選項就是定義class文件的查找目錄。
Iterable<String> options = Arrays.asList(CMDConstant.CMD_COMMAND_JAVACOMPILE_1, encoding,
CMDConstant.CMD_COMMAND_JAVACOMPILE_2, jars, CMDConstant.CMD_COMMAND_JAVACOMPILE_3, targetDir,
CMDConstant.CMD_COMMAND_JAVACOMPILE_4, sourceDir);
CompilationTask compilationTask = compiler.getTask(null, fileManager, diagnostics, options, null,
compilationUnits);
// 運行編譯任務
return compilationTask.call();
} finally {
fileManager.close();
}
}
// 獲取非系統編譯器
public static JavaCompiler getJavaCompilerByLocation(File f1) throws Exception {
// tool.jar我放在了本項目根目錄下,可自行更改
String p = f1.getAbsolutePath();
URL[] urls = new URL[] { new URL(OtherConstant.OTHER_URL_1 + p) };
URLClassLoader myClassLoader1 = new URLClassLoader(urls, Thread.currentThread().getContextClassLoader());
Class<?> myClass1 = myClassLoader1.loadClass(OtherConstant.OTHER_JAVACTOOL_CLASS);
JavaCompiler compiler = myClass1.asSubclass(JavaCompiler.class).asSubclass(JavaCompiler.class).newInstance();
return compiler;
}
// 判斷不爲空
public static boolean isnull(String str) {
if (null == str) {
return false;
} else if (CommonSymbolicConstant.EMPTY_STRING.equals(str)) {
return false;
} else if (str.equals(CommonSymbolicConstant.NULL)) {
return false;
} else {
return true;
}
}
// 遞歸查找java文件
private static void getSourceFiles(File sourceFile, List<File> sourceFileList) throws Exception {
if (sourceFile.exists() && sourceFileList != null) {
if (sourceFile.isDirectory()) {
// 得到該目錄下以.java結尾的文件或者目錄
File[] childrenFiles = sourceFile.listFiles(new FileFilter() {
public boolean accept(File pathname) {
if (pathname.isDirectory()) {
return true;
} else {
String name = pathname.getName();
if (name.endsWith(FileExtensionConstant.FILE_EXTENSION_POINT_CODEFILE_JAVA) ? true
: false) {
return true;
}
return false;
}
}
});
// 遞歸調用
for (File childFile : childrenFiles) {
getSourceFiles(childFile, sourceFileList);
}
} else {// 若file對象爲文件
sourceFileList.add(sourceFile);
}
}
}
}
如何動態執行java代碼,見:
https://blog.csdn.net/rico_zhou/article/details/79873344 和 https://blog.csdn.net/rico_zhou/article/details/81301284
https://blog.csdn.net/rico_zhou/article/details/81284048
github源碼:https://github.com/ricozhou/dynamiccompilerjava