Java深度历险(一)——Java字节代码的操纵ClassNotFoundException的解决(Eclipse环境下)

        在一般情况下,开发人员都是在程序运行之前就编写完成了全部的Java源代码并且成功编译。对有些应用来说,Java源代码的内容在运行时刻才能确定。这个时候就需要动态编译源代码来生成Java字节代码,再由JVM来加载执行。典型的场景是很多算法竞赛的在线评测系统(如PKU JudgeOnline),允许用户上传Java代码,由系统在后台编译、运行并进行判定。在动态编译Java源文件时,使用的做法是直接在程序中调用Java编译器。

       JSR 199引入了Java编译器API。如果使用JDK 6 的话,可以通过此API来动态编译Java代码。比如下面的代码用来动态编译最简单的Hello World类。该Java类的代码是保存在一个字符串中的。

      

import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;

import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import javax.tools.JavaCompiler.CompilationTask;

/**
 * 在程序内部调用编译器,动态编译运行
 *
 */

public class CompilerTest {
	public static void main(String[] args) throws Exception {
		String source = "public class Main { public static void main(String[]"
		                +" args) {System.out.println(\"Hello World!\");} }";
		JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
		StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
		StringSourceJavaObject sourceObject = new CompilerTest.StringSourceJavaObject("Main", source);
		String flag = "-d";
		String outDir = System.getProperty("user.dir")+"/bin";
		//设置Main的class目录也在eclipse默认编译的bin目录下,不设置则Main在abcc下
		//Iterable<String> stringDir = Arrays.asList(flag,outDir); 
		Iterable<? extends JavaFileObject> fileObjects = Arrays.asList(sourceObject);
		CompilationTask task = compiler.getTask(null, fileManager, null,null, null, fileObjects);
		//CompilationTask task = compiler.getTask(null, fileManager, null,stringDir, null, fileObjects);
		boolean result = task.call();
		if (result) {
		     System.out.println("编译成功。");
		     //运行
		     Class<?> clazz = Class.forName("Main");
		     Method method = clazz.getMethod("main", new Class<?>[]{String[].class});
		     method.invoke(null, new Object[]{null});
		}
	}

	static class StringSourceJavaObject extends SimpleJavaFileObject {
		private String content = null;

		public StringSourceJavaObject(String name, String content)throws URISyntaxException {
			super(URI.create("string:///" + name.replace('.', '/')
					+ Kind.SOURCE.extension), Kind.SOURCE);
			this.content = content;
		}

		public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
			return content;
		}
	}
}

 
 

运行此类但抛出ClassNotFoundException ,Main找不到。在eclipse 导航中发现Main类(代码中的动态编译类)class文件生成在工程主目录下,而在eclipse中建的类(CompilerTest)的编译class的位置在bin下,用ComplierTest加载器在其目录bin下加载显然就找不到了。

解决办法: Java编译器API实际上是调用系统环境中的javac命令,在终端下输入javac命令,会发现javac带有n多的参数,其中有一个是-d 可以指定编译后的class文件存放目录。但在java编译器的API是如何实现的呢?   可以在JavaCompiler的getTask方法进行设置:

CompilationTask getTask(Writer out, JavaFileManager fileManager, 
		DiagnosticListener<? super JavaFileObject> diagnosticListener, Iterable<String> options,Iterable<String> classes,Iterable<? extends JavaFileObject> compilationUnits);   
其中的options就是指定了javac的参数。     在此将上面代码的注释去掉即可。   
关于其他的动态编译的用法如动态加载单独文件里的类等,可参考其他API。
看参考http://www.iteye.com/topic/608485



你可能感兴趣的:(Java深度历险(一)——Java字节代码的操纵ClassNotFoundException的解决(Eclipse环境下))