Java类动态加载(一)——java源文件动态编译为class文件
最近在做java动态加载这方面的工作,起初也遇到了很多困难。网上关于这方便的东西很零散,为了便于日后回过头来再看,于是我将这几天的心得体会总结如下。
什么情况下会需要用java程序动态的编译java源文件,动态的加载java类文件呢?如果很少遇到这样的需求的兄弟们可能不会清楚动态的编译、动态的加载用在一个什么样的场景。下面我将我遇到的场景描述下。
Sdl说明:
为了更好的说明需求,先解释下,我这里的sdl文件是干什么用的。
sdl文件里面主要是定义了一些远程调用接口的相关信息,根据这些信息我们可以自己手动生成java版本的远程调用接口。具体有些什么东西呢?比如说,接口的名称、所在包路径、接口参数、接口用的java bean等等。我们得到这些sdl文件后,可以利用sdl2java.exe或者sdl2java.sh工具将文件编译成java源文件,利用这些源文件就可以进行远程接口调用。
需求说明:
用户只提供一个sdl文件,需要程序能够根据这个sdl文件,提取出所有远程调用接口,让用户在前端输入参数,然后进行远程调用。
实现方案:
用户上传一个sdl文件到工程临时目录,然后程序自动的调用sdl2java.exe或者sdl2java.sh命令将sdl文件动态编译成一系列的java源文件,然后程序动态的将这些java文件编译成class文件,最后再动态加载到项目中。
整个实现方案有三个难点:
用java程序调用sdl2java.exe或者sdl2java.sh命令解析sdl文件,生成一系列的java源文件动态编译上述java源文件为class类文件动态加载class类文件
本文讲解的实现方案的前提:
本文主要讲解第二个难点如何实现,因此假设程序已经实现了将sdl文件转换成了一系列的java文件,并存放到服务器中根目录的temp\sdl\src目录中
动态将java文件编译为class文件解决方案:
将temp\sdl\src目录中的java源文件编译成class文件,并存放到temp\sdl\classes目录中。
java中早就提供了用java方式去动态编译java源文件的接口,有关java动态编译的API都在javax.tools包中。本文主要使用jdk1.6以上版本提供的JavaCompiler工具来动态编译java源文件。
我们可以通过ToolProvider类的静态方法getSystemJavaCompiler得到JavaCompiler对象实例。

由于编译这些java文件需要用到两个与sdl相关的jar包,因此在运行下面的代码前,需要将sdl.rar中lib目录下的jar包导入到工程里面来。
源代码如下,直接运行里面的main方法即可。运行后,如果输出:编译成功,则程序正常运行,可以通过查看sdl\classes目录中是否有class文件检查运行结果。
package util;import java.io.File;import java.io.FileFilter;import java.io.FilenameFilter;import java.util.ArrayList;import java.util.Arrays;import java.util.List;import javax.tools.Diagnostic;import javax.tools.DiagnosticCollector;import javax.tools.JavaCompiler;import javax.tools.JavaFileObject;import javax.tools.StandardJavaFileManager;import javax.tools.ToolProvider;import javax.tools.JavaCompiler.CompilationTask;import org.apache.commons.lang.StringUtils;/** * @author zhengtian * * @date 2012-4-17 下午07:24:24 */@SuppressWarnings("all")public class DynamicCompilerUtil {/** * 编译java文件 * * @param filePath * 文件或者目录(若为目录,自动递归编译) * @param sourceDir * java源文件存放目录 * @param targetDir * 编译后class类文件存放目录 * @param diagnostics * 存放编译过程中的错误信息 * @return * @throws Exception */public static boolean compiler(String filePath, String sourceDir, String targetDir, DiagnosticCollector<JavaFileObject> diagnostics)throws Exception {// 获取编译器实例JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();// 获取标准文件管理器实例StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);try {if (StringUtils.isEmpty(filePath) && StringUtils.isEmpty(sourceDir) && StringUtils.isEmpty(targetDir)) {return false;}// 得到filePath目录下的所有java源文件File sourceFile = new File(filePath);List<File> sourceFileList = new ArrayList<File>();getSourceFiles(sourceFile, sourceFileList);// 没有java文件,直接返回if (sourceFileList.size() == 0) {System.out.println(filePath + "目录下查找不到任何java文件");return false;}// 获取要编译的编译单元Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjectsFromFiles(sourceFileList);/** * 编译选项,在编译java文件时,编译程序会自动的去寻找java文件引用的其他的java源文件或者class。 -sourcepath选项就是定义java源文件的查找目录, -classpath选项就是定义class文件的查找目录。 */Iterable<String> options = Arrays.asList("-d", targetDir, "-sourcepath", sourceDir);CompilationTask compilationTask = compiler.getTask(null, fileManager, diagnostics, options, null, compilationUnits);// 运行编译任务return compilationTask.call();} finally {fileManager.close();}}/** * 查找该目录下的所有的java文件 * * @param sourceFile * @param sourceFileList * @throws Exception */private static void getSourceFiles(File sourceFile, List<File> sourceFileList) throws Exception {if (sourceFile.exists() && sourceFileList != null) {// 文件或者目录必须存在if (sourceFile.isDirectory()) {// 若file对象为目录// 得到该目录下以.java结尾的文件或者目录File[] childrenFiles = sourceFile.listFiles(new FileFilter() {public boolean accept(File pathname) {if (pathname.isDirectory()) {return true;} else {String name = pathname.getName();return name.endsWith(".java") ? true : false;}}});// 递归调用for (File childFile : childrenFiles) {getSourceFiles(childFile, sourceFileList);}} else {// 若file对象为文件sourceFileList.add(sourceFile);}}}public static void main(String[] args) {try {// 编译F:\\亚信工作\\SDL文件\\sdl\\src目录下的所有java文件String filePath = "F:\\亚信工作\\SDL文件\\sdl\\src";String sourceDir = "F:\\亚信工作\\SDL文件\\sdl\\src";String targetDir = "F:\\亚信工作\\SDL文件\\sdl\\classes";DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();boolean compilerResult = compiler(filePath, sourceDir, targetDir, diagnostics);if (compilerResult) {System.out.println("编译成功");} else {System.out.println("编译失败");for (Diagnostic diagnostic : diagnostics.getDiagnostics()) {// System.out.format("%s[line %d column %d]-->%s%n", diagnostic.getKind(), diagnostic.getLineNumber(),// diagnostic.getColumnNumber(),// diagnostic.getMessage(null));System.out.println(diagnostic.getMessage(null));}}} catch (Exception e) {e.printStackTrace();}}}