1. 程式人生 > >Java類檔案動態編譯並執行方法

Java類檔案動態編譯並執行方法

package com.example.demo.dimension;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.Stack;

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

/**
 * @ClassName: CompilerTest
 * @Description: JAVA原始檔動態編譯
 * @date: 2018年7月16日 上午9:45:32
 * 
 */
public class CompilerTest {
	public static void main(String[] args) throws Exception {
		String classPath = "G:/deleted/apktool/dynamicClass" ;
		System.out.println( calculate("1.0+1.0",classPath));
	}
	
	/**
	 * @Title: testHello   
	 * @Description: 測試動態編譯java檔案
	 * @param: @param classPath      
	 * @return: void      
	 * @throws
	 */
	public static void testHello(String classPath){
		String className = "Main" ;
		String source = "public class "+className+" { public static void main(String[] args) {System.out.println(\"Hello World!\");} }";
		boolean isSuccess = complie(source,className,classPath) ;
		System.out.println( "Complie successfully:"+isSuccess );
	}
	

	/**
	 * @Title: calculate   
	 * @Description: 測試動態編譯並且載入類並執行對應方法
	 * @param: @param expr
	 * @param: @param classPath
	 * @param: @return
	 * @param: @throws Exception      
	 * @return: double      
	 * @throws
	 */
	private static double calculate(String expr,String classPath) throws  Exception {
		String className = "Main";
		String methodName = "calculate";
		String source = "public class " + className + " { public static double " + methodName + "() { return " + expr
				+ "; } }";
		// 省略動態編譯Java原始碼的相關程式碼,參見上一節
		boolean result = complie(source,className, classPath);
		if (result) {
			loadClass( new File(classPath) );
			ClassLoader loader = CompilerTest.class.getClassLoader();
			try {
				Class<?> clazz = loader.loadClass(className);
				Method method = clazz.getMethod(methodName, new Class<?>[] {});
				Object value = method.invoke(null, new Object[] {});
				return (Double) value;
			} catch (Exception e) {
				throw new  Exception("內部錯誤。",e);
			}
		} else {
			throw new  Exception("錯誤的表示式。");
		}
	}
	
	/**
	 * @Title: complie   
	 * @Description: 動態編譯java類成成class檔案放入指定目錄
	 * @param: @param expr
	 * @param: @param classPath
	 * @param: @return
	 * @param: @throws Exception      
	 * @throws
	 */
	public static boolean complie(String source,String className,String classPath){
		JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
		StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
		StringSourceJavaObject sourceObject = null;
		try {
			Iterable<String> options = Arrays.asList("-d", classPath);
			sourceObject = new CompilerTest.StringSourceJavaObject(className, source);
			Iterable<? extends JavaFileObject> fileObjects = Arrays.asList(sourceObject);
			CompilationTask task = compiler.getTask(null, fileManager, null, options, null, fileObjects);
			boolean result = task.call();
			return result ;
		} catch (URISyntaxException e) {
			e.printStackTrace();
		}
		return false ;
	} 

	private 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;
		}
	}
	
	/**
	 * @Title: loadClass   
	 * @Description: 動態載入class檔案
	 * @param: @param clazzPath
	 * @param: @throws Exception      
	 * @return: void      
	 * @throws
	 */
	public static void loadClass(File clazzPath) throws Exception{
		// 設定class檔案所在根路徑
		// 例如/usr/java/classes下有一個test.App類,則/usr/java/classes即這個類的根路徑,而.class檔案的實際位置是/usr/java/classes/test/App.class
//		File clazzPath = new File(class檔案所在根路徑);
		 
		// 記錄載入.class檔案的數量
		int clazzCount = 0;
		//only handle the folder
		if( clazzPath.isFile() ){
			clazzPath = clazzPath.getParentFile() ;
		}
		
		if (clazzPath.exists() && clazzPath.isDirectory()) {
			// 獲取路徑長度
			int clazzPathLen = clazzPath.getAbsolutePath().length() + 1;
		 
			Stack<File> stack = new Stack<>();
			stack.push(clazzPath);
		 
			// 遍歷類路徑
			while (stack.isEmpty() == false) {
				File path = stack.pop();
				File[] classFiles = path.listFiles(new FileFilter() {
					public boolean accept(File pathname) {
						return pathname.isDirectory() || pathname.getName().endsWith(".class");
					}
				});
				for (File subFile : classFiles) {
					if (subFile.isDirectory()) {
						stack.push(subFile);
					} else {
						if (clazzCount++ == 0) {
							Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
							boolean accessible = method.isAccessible();
							try {
								if (accessible == false) {
									method.setAccessible(true);
								}
								// 設定類載入器
								URLClassLoader classLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
								// 將當前類路徑加入到類載入器中
								method.invoke(classLoader, clazzPath.toURI().toURL());
							} finally {
								method.setAccessible(accessible);
							}
						}
						// 檔名稱
						String className = subFile.getAbsolutePath();
						className = className.substring(clazzPathLen, className.length() - 6);
						className = className.replace(File.separatorChar, '.');
						// 載入Class類
						Class.forName(className);
						System.out.println( String.format( "讀取應用程式類檔案[class=%s]", className) );
					}
				}
			}
		}
	}
}

參考:

動態載入jar和class檔案    https://blog.csdn.net/mousebaby808/article/details/31788325

JAVA原始檔動態編譯       https://blog.csdn.net/m1993619/article/details/78734682