动态编译运行java程序

动态编译java有两种方式,一种是使用Runtime类执行cmd命令编译java,详见:

另一种是java自身提供的方法,下面我们来实践,如何通过JavaCompiler实现java代码动态编译。

首先获取编译器,注意tools.jar在jdk中不在jre中,详见:

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代码,见:

 和 


github源码:https://github.com/ricozhou/dynamiccompilerjava