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



發佈了51 篇原創文章 · 獲贊 9 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章