`

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();
		}
	}
}

  • sdl.rar (256.7 KB)
  • 下载次数: 122
  • 大小: 17.6 KB
  • 大小: 27.7 KB
分享到:
评论
4 楼 陌路人丁 2018-11-07  
SmallFish 写道
同上,我也遇到了这个问题,目前还没解决。

大神你的解决了么?我也一样的问题,麻烦分享下答案!
3 楼 陌路人丁 2018-11-07  
冯程程 写道
如果java源码中引用了第三方类后,动态编译会报软件包不存在,请问你是怎么解决的呢?

请问大神现在解决了么?我也有同样疑问啊,很感谢了!
2 楼 SmallFish 2013-09-03  
同上,我也遇到了这个问题,目前还没解决。
1 楼 冯程程 2012-09-07  
如果java源码中引用了第三方类后,动态编译会报软件包不存在,请问你是怎么解决的呢?

相关推荐

    Class文件反编译工具

    这就引出了我们今天要讨论的主题——"Class文件反编译工具"。 反编译工具的主要作用是将Java的`Class`文件转换回可读的源代码(Java源代码),以便开发者理解或调试已编译的代码。这在某些情况下非常有用,比如当...

    java源码包---java 源码 大量 实例

     一个Java+ajax写的登录实例,附有JAVA源文件,JAVA新手朋友可以学习一下。 JAVA+JSP的聊天室 8个目标文件 简单 JavaScript万年历 显示出当前时间及年份,还可以选择年份及月份和日期 Java编写的HTML浏览器 一...

    class_java.zip

    当我们编写好一个Java源文件后,通过Java编译器(javac)将源码转换为字节码,存储在.class文件中。这个过程被称为编译阶段,它是Java程序生命周期中的第一步。 在"源码"这一标签下,我们可以期待看到各种Java类的...

    java——知识点归纳总结

    - **动态性**:Java是一种面向网络的语言,支持远程过程调用(RPC)和动态加载类等功能,使得应用可以动态扩展和更新。 #### 编译与执行 - **编译过程**:使用`javac`命令将Java源代码(*.java文件)编译成字节码...

    Java JNI调用IC卡读卡器

    2. 编译Java源文件:使用javac命令编译Java源文件,生成对应的`.class`文件,如`ReadCard.class`和`WriteCard.class`。 3. 生成JNI头文件:使用`javah`工具,根据编译后的`.class`文件生成C/C++的头文件,它包含了...

    java源码包2

     一个Java+ajax写的登录实例,附有JAVA源文件,JAVA新手朋友可以学习一下。 JAVA+JSP的聊天室 8个目标文件 简单 JavaScript万年历 显示出当前时间及年份,还可以选择年份及月份和日期 Java编写的HTML浏览器 ...

    java源码包3

     一个Java+ajax写的登录实例,附有JAVA源文件,JAVA新手朋友可以学习一下。 JAVA+JSP的聊天室 8个目标文件 简单 JavaScript万年历 显示出当前时间及年份,还可以选择年份及月份和日期 Java编写的HTML浏览器 ...

    3.java程序运行机制(csdn)————程序.pdf

    2. **编译**:使用Java编译器(javac)将`.java`源文件转换为`.class`字节码文件。这个过程是Java的“一次编写,到处运行”特性的重要部分,因为字节码是平台无关的,可以在任何支持Java的平台上运行。编译器会检查...

    Java教案——3章 个人学习资料

    1. **编译过程**:Java源文件(.java)通过Java编译器(javac)被编译成字节码(.class)。 2. **运行过程**:客户端收到网络传输的字节码后,通过JRE中的JVM将字节码转化为本地码(Native code),这个过程可能涉及...

    JAVA习题集+答案

    - **错误**:虽然Java源文件的命名可以较为灵活,但通常建议源文件的名称应与主类的名称保持一致,且扩展名为`.java`。 6. **在JDK命令行开发工具中,用编译程序javac.exe编译生成的文件是二进制可执行文件。** -...

    成百上千个Java 源码DEMO 4(1-4是独立压缩包)

    生成文件输入流,输入文件为c:/mycert.cer,获取一个处理X.509证书的证书工厂…… Java+ajax写的登录实例 1个目标文件 内容索引:Java源码,初学实例,ajax,登录 一个Java+ajax写的登录实例,附有JAVA源文件,JAVA新手...

    Head First Java, Second Edition【中英对照】.pdf

    例如,`% javac Party.java` 表示编译名为`Party.java`的Java源文件。 - **运行**:使用`java`命令运行编译后的类文件,如`% java Party`表示运行名为`Party`的Java应用程序。 - **类加载机制**:Java类在被使用之前...

    成百上千个Java 源码DEMO 3(1-4是独立压缩包)

    生成文件输入流,输入文件为c:/mycert.cer,获取一个处理X.509证书的证书工厂…… Java+ajax写的登录实例 1个目标文件 内容索引:Java源码,初学实例,ajax,登录 一个Java+ajax写的登录实例,附有JAVA源文件,JAVA新手...

    java深度历险.pdf

    本文将探讨如何在运行时动态编译Java源文件以及操纵字节码。 首先,让我们了解一下Java的编译和执行过程。通常,开发者使用集成开发环境(IDE)编写源代码(.java文件),IDE会调用Java编译器(javac)将源代码编译...

    java源码包4

     一个Java+ajax写的登录实例,附有JAVA源文件,JAVA新手朋友可以学习一下。 JAVA+JSP的聊天室 8个目标文件 简单 JavaScript万年历 显示出当前时间及年份,还可以选择年份及月份和日期 Java编写的HTML浏览器 ...

    java学习教材(java私塾跟我学系列--java篇)

    - **编写、编译并运行Java程序**:使用文本编辑器编写Java源代码后,可以通过命令行使用`javac`命令编译Java源文件,生成字节码文件(.class文件),再使用`java`命令运行编译后的程序。 - **使用API文档**:Java...

    JVM学习笔记(一)——类的加载机制

    - 动态编译Java源文件为.class文件。 总结来说,类的加载机制是JVM中至关重要的部分,它确保了类的正确加载、验证和初始化,使得程序能够正常运行。理解类加载机制有助于优化程序性能,解决类加载相关的错误,并...

    java安装包.zip

    Java安装包是一个专门为Java初学者准备的压缩文件,包含了运行和开发Java应用程序所需的基础环境——Java Development Kit(JDK)。JDK是Oracle公司提供的官方Java编程工具包,它包括Java运行环境(Java Runtime ...

Global site tag (gtag.js) - Google Analytics