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对象实例。

// 获取编译器实例
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

得到JavaCompiler对象实例后,我们可以调用该工具的getTask(Writer out, JavaFileManager fileManager, DiagnosticListener<? super JavaFileObject> diagnosticListener, Iterable<String> options, Iterable<String> classes, Iterable<? extends JavaFileObject> compilationUnits) 方法获取一个编译任务对象。

CompilationTask compilationTask = compiler.getTask(null, fileManager, diagnostics, options, null, compilationUnits);

该方法的第一个参数为文件输出,这里我们可以不指定,我们采用javac命令的-d参数来指定class文件的生成目录。
第二个参数为文件管理器实例

// 获取标准文件管理器实例
StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);

该文件管理器实例的作用就是将我们需要动态编译的java源文件转换为getTask需要的编译单元。

// 获取要编译的编译单元
Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjectsFromFiles(sourceFileList);

第三个参数DiagnosticCollector<JavaFileObject> diagnostics是在编译出错时,存放编译错误信息。
第四个参数为编译命令选项,就是javac命令的可选项,这里我们主要使用了-d和-sourcepath这两个选项。

/**
* 编译选项,在编译java文件时,编译程序会自动的去寻找java文件引用的其他的java源文件或者class。 -sourcepath选项就是定义java源文件的查找目录, -classpath选项就是定义class文件的查找目录,-d就是编译文件的输出目录。
*/
Iterable<String> options = Arrays.asList("-d", targetDir, "-sourcepath", sourceDir);

第五个参数为类名称,具体作用没研究清楚。
第六个参数为上面提到的编译单元,就是我们需要编译的java源文件

当我们得到CompilationTask compilationTask编译任务后,我们就可以调用compilationTask.call()方法进行编译工作

// 运行编译任务
compilationTask.call()

下面代码的运行前提条件:
首先需要将附件中的sdl.rar文件解压到F:\亚信工作\SDL文件目录下,sdl目录结构见下图

由于编译这些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();
}
}
}

转自:http://www.myexception.cn/program/627493.html

Java_动态加载的更多相关文章

  1. Java_动态加载类(英文)

    It is possible to load and reload classes at runtime in Java, though it is not as straightforward as ...

  2. js动态加载css和js

    之前写了一个工具类点此链接里面含有这段代码,感觉用处挺多,特意提出来 var loadUtil = { /* * 方法说明:[动态加载js文件css文件] * 使用方法:loadUtil.loadjs ...

  3. geotrellis使用(二十三)动态加载时间序列数据

    目录 前言 实现方法 总结 一.前言        今天要介绍的绝对是华丽的干货.比如我们从互联网上下载到了一系列(每天或者月平均等)的MODIS数据,我们怎么能够对比同一区域不同时间的数据情况,采用 ...

  4. Ext JS 如何动态加载JavaScript创建窗体

    JavaScript不需要编译即可运行,这让JavaScript构建的应用程序可以变得很灵活.我们可以根据需要动态从服务器加载JavaScript脚本来创建和控制UI来与用户交互.下面结合Ext JS ...

  5. Ext动态加载Toolbar

    在使用Ext的GridPanel时候,有时候需要面板不用重新加载而去更新Store或者Toolbar,Store的方法有很多,例如官方api给我们提供的Store.load(),Store.reLoa ...

  6. Android动态加载框架汇总

    几种动态加载的比较 1.Tinker 用途:热修复 GitHub地址:https://github.com/Tencent/tinker/ 使用:http://www.jianshu.com/p/f6 ...

  7. 为不同分辨率单独做样式文件,在页面头部用js判断分辨率后动态加载定义好的样式文件

    为不同分辨率单独做样式文件,在页面头部用js判断分辨率后动态加载定义好的样式文件.样式文件命名格式如:forms[_屏幕宽度].css,样式文件中只需重新定义文本框和下拉框的宽度即可. 在包含的头文件 ...

  8. html中的图像动态加载问题

    首先要说明下文档加载完成是什么概念 一个页面http请求访问时,浏览器会将它的html文件内容请求到本地解析,从窗口打开时开始解析这个document,页面初始的html结构和里面的文字等内容加载完成 ...

  9. 非常郁闷的 .NET中程序集的动态加载

    记载这篇文章的原因是我自己遇到了动态加载程序集的问题,而困扰了一天之久. 最终看到了这篇博客:http://www.cnblogs.com/brucebi/archive/2013/05/22/Ass ...

随机推荐

  1. Swipe to back not working滑动后退功能消失?

    如果你发现滑动后退功能突然失效了,很可能是因为你隐藏了NavigationBar 或者定制了 leftBarButtonItem(s) 这会导致 NavigationController 的 inte ...

  2. Linux环境下使用C/C++编写CGI(httpd)

    step1下载: ftp://ftp.gnu.org/gnu/cgicc/ step2: tar xzf cgicc-X.X.X.tar.gz(用最新版本) cd cgicc-X.X.X ./conf ...

  3. 杂物 python (一)

    python 是一门语言,有各种不同的实现.CPython 即用c语言实现Python及其解释器.

  4. 如何安装sublime text2以及它的插件?

    下载Sublime Text2的安装包,安装,安装后打开的界面如图   下面我们来给他安装插件,首先安装packagecontrol,打开菜单栏中的View-->show console   在 ...

  5. phpcms v9中调用栏目及调用多个子栏目中的文章列表

    调用一个指定栏目列表:            {pc:content action="lists" catid="6" order="id DESC& ...

  6. Python3 基本数据类型注意事项

    Python3 基本数据类型 教程转自菜鸟教程:http://www.runoob.com/python3/python3-data-type.html Python中的变量不需要声明.每个变量在使用 ...

  7. 注解:【有连接表的】Hibernate单向1->1关联

    Person与Address关联:单向1->1,[有连接表的] (使用较少!) Person.java package org.crazyit.app.domain; import javax. ...

  8. 什么时候使用CountDownLatch

    正如每个Java文档所描述的那样,CountDownLatch是一个同步工具类,它允许一个或多个线程一直等待,直到其他线程的操作执行完后再执行.在Java并发中,countdownlatch的概念是一 ...

  9. How Kafka’s Storage Internals Work

    In this post I'm going to help you understand how Kafka stores its data. I've found understanding th ...

  10. ettercap局域网内DNS欺骗(隔壁的哥们轻一点 ...)

    转自:http://www.cnblogs.com/hkleak/p/5043063.html 笔记一:ettercap是什么? 我们在对WEB安全检测的时候都会用到Cain和netfuke这两款工具 ...