Bootstrap

将Java字符串形式的源代码动态编译,生成class文件并执行

直奔主题:Java中有下面这么一个字符串源码,如何判别这段源码没有错误,以及正确的执行结果?

String javaSrc = "import java.util.Random;\r\n" + "\r\n"
				+ "public class TestClass {\r\n" + "\r\n"
				+ "	public static void main(String[] args) {\r\n"
				+ "		TestClass class1 = new TestClass();\r\n"
				+ "		class1.sayHello(\"this is main method\");\r\n"
				+ "		Random random = new Random();\r\n"
				+ "		int a = random.nextInt(1024);\r\n"
				+ "		int b = random.nextInt(1024);\r\n"
				+ "		System.out.printf(\r\n"
				+ "				Thread.currentThread().getName() + \": \" + \"%d + %d = %d\\n\", a,\r\n"
				+ "				b, class1.add(a, b));\r\n"
				+ "		System.out.println();\r\n" + "	}\r\n" + "\r\n"
				+ "	public void sayHello(String msg) {\r\n"
				+ "		System.out.printf(\r\n"
				+ "				Thread.currentThread().getName() + \": \" + \"Hello %s!\\n\", msg);\r\n"
				+ "	}\r\n" + "\r\n" + "	public int add(int a, int b) {\r\n"
				+ "		return a + b;\r\n" + "	}\r\n" + "}\r\n" + "";

利用反射,通过类名去执行加载好的class文件:

package demo2;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;

public class Test {
	public static void main(String[] args) {
		String className = "TestClass";
		String javaSrc = "import java.util.Random;\r\n" + "\r\n"
				+ "public class TestClass {\r\n" + "\r\n"
				+ "	public static void main(String[] args) {\r\n"
				+ "		TestClass class1 = new TestClass();\r\n"
				+ "		class1.sayHello(\"this is main method\");\r\n"
				+ "		Random random = new Random();\r\n"
				+ "		int a = random.nextInt(1024);\r\n"
				+ "		int b = random.nextInt(1024);\r\n"
				+ "		System.out.printf(\r\n"
				+ "				Thread.currentThread().getName() + \": \" + \"%d + %d = %d\\n\", a,\r\n"
				+ "				b, class1.add(a, b));\r\n"
				+ "		System.out.println();\r\n" + "	}\r\n" + "\r\n"
				+ "	public void sayHello(String msg) {\r\n"
				+ "		System.out.printf(\r\n"
				+ "				Thread.currentThread().getName() + \": \" + \"Hello %s!\\n\", msg);\r\n"
				+ "	}\r\n" + "\r\n" + "	public int add(int a, int b) {\r\n"
				+ "		return a + b;\r\n" + "	}\r\n" + "}\r\n" + "";
		try {
			Test.testInvoke(className, javaSrc);
		} catch (ClassNotFoundException | IllegalAccessException
				| InstantiationException | NoSuchMethodException
				| InvocationTargetException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public static void testInvoke(String className, String source)
			throws ClassNotFoundException, IllegalAccessException,
			InstantiationException, NoSuchMethodException,
			InvocationTargetException {

		final String SUFFIX = ".java";// 类名后面要跟的后缀

		// 对source进行编译生成class文件存放在Map中,这里用bytecode接收
		Map<String, byte[]> bytecode = DynamicLoader.compile(className + SUFFIX,
				source);

		// 加载class文件到虚拟机中,然后通过反射执行
		@SuppressWarnings("resource")
		DynamicLoader.MemoryClassLoader classLoader = new DynamicLoader.MemoryClassLoader(
				bytecode);
		Class<?> clazz = classLoader.loadClass("TestClass");
		Object object = clazz.newInstance();

		// 得到sayHello方法
		Method sayHelloMethod = clazz.getMethod("sayHello", String.class);
		sayHelloMethod.invoke(object, "This is the method called by reflect");

		// 得到add方法
		Method addMethod = clazz.getMethod("add", int.class, int.class);
		Object returnValue = addMethod.invoke(object, 1024, 1024);
		System.out.println(Thread.currentThread().getName() + ": "
				+ "1024 + 1024 = " + returnValue);

		// 因为在main方法中,调用了add和sayHello方法,所以直接调用main方法就可以执行两个方法
		Method mainMethod = clazz.getDeclaredMethod("main", String[].class);
		mainMethod.invoke(null, (Object) new String[] {});
	}

}

调用Java编译接口动态加载,以类名为键值,将生成的class文件放到map中:

package demo2;

import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

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

public class DynamicLoader {

	/**
	 * 通过类名和其代码(Java代码字符串),编译得到字节码,返回类名及其对应类的字节码,封装于Map中,值得注意的是,
	 * 平常类中就编译出来的字节码只有一个类,但是考虑到内部类的情况, 会出现很多个类名及其字节码,所以用Map封装方便。
	 * 
	 * @param javaName 类名
	 * @param javaSrc  Java源码
	 * @return map
	 */
	public static Map<String, byte[]> compile(String javaName, String javaSrc) {
		// 调用java编译器接口
		JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
		StandardJavaFileManager stdManager = compiler
				.getStandardFileManager(null, null, null);

		try (MemoryJavaFileManager manager = new MemoryJavaFileManager(
				stdManager)) {

			@SuppressWarnings("static-access")
			JavaFileObject javaFileObject = manager.makeStringSource(javaName,
					javaSrc);
			JavaCompiler.CompilationTask task = compiler.getTask(null, manager,
					null, null, null, Arrays.asList(javaFileObject));
			if (task.call()) {
				return manager.getClassBytes();
			}

		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * 先根据类名在内存中查找是否已存在该类,若不存在则调用 URLClassLoader的 defineClass方法加载该类
	 * URLClassLoader的具体作用就是将class文件加载到jvm虚拟机中去
	 * 
	 * @author Administrator
	 *
	 */
	public static class MemoryClassLoader extends URLClassLoader {
		Map<String, byte[]> classBytes = new HashMap<String, byte[]>();

		public MemoryClassLoader(Map<String, byte[]> classBytes) {
			super(new URL[0], MemoryClassLoader.class.getClassLoader());
			this.classBytes.putAll(classBytes);
		}

		@Override
		protected Class<?> findClass(String name)
				throws ClassNotFoundException {
			byte[] buf = classBytes.get(name);
			if (buf == null) {
				return super.findClass(name);
			}
			classBytes.remove(name);
			return defineClass(name, buf, 0, buf.length);
		}
	}
}

package demo2;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Reader;
import java.io.StringReader;
import java.net.URI;
import java.nio.CharBuffer;
import java.util.HashMap;
import java.util.Map;

import javax.tools.FileObject;
import javax.tools.ForwardingJavaFileManager;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;

/**
 * 将编译好的.class文件保存到内存当中,这里的内存也就是map映射当中
 */
@SuppressWarnings("rawtypes")
public final class MemoryJavaFileManager extends ForwardingJavaFileManager {

	private final static String EXT = ".java";// Java源文件的扩展名
	private Map<String, byte[]> classBytes;// 用于存放.class文件的内存

	@SuppressWarnings("unchecked")
	public MemoryJavaFileManager(JavaFileManager fileManager) {
		super(fileManager);
		classBytes = new HashMap<String, byte[]>();
	}

	public Map<String, byte[]> getClassBytes() {
		return classBytes;
	}

	@Override
	public void close() throws IOException {
		classBytes = new HashMap<String, byte[]>();
	}

	@Override
	public void flush() throws IOException {
	}

	/**
	 * 一个文件对象,用来表示从string中获取到的source,一下类容是按照jkd给出的例子写的
	 */
	private static class StringInputBuffer extends SimpleJavaFileObject {
		// The source code of this "file".
		final String code;

		/**
		 * Constructs a new JavaSourceFromString.
		 * 
		 * @param name 此文件对象表示的编译单元的name
		 * @param code 此文件对象表示的编译单元source的code
		 */
		StringInputBuffer(String name, String code) {
			super(toURI(name), Kind.SOURCE);
			this.code = code;
		}

		@Override
		public CharBuffer getCharContent(boolean ignoreEncodingErrors) {
			return CharBuffer.wrap(code);
		}

		@SuppressWarnings("unused")
		public Reader openReader() {
			return new StringReader(code);
		}
	}

	/**
	 * 将Java字节码存储到classBytes映射中的文件对象
	 */
	private class ClassOutputBuffer extends SimpleJavaFileObject {
		private String name;

		/**
		 * @param name className
		 */
		ClassOutputBuffer(String name) {
			super(toURI(name), Kind.CLASS);
			this.name = name;
		}

		@Override
		public OutputStream openOutputStream() {
			return new FilterOutputStream(new ByteArrayOutputStream()) {
				@Override
				public void close() throws IOException {
					out.close();
					ByteArrayOutputStream bos = (ByteArrayOutputStream) out;

					// 这里需要修改
					classBytes.put(name, bos.toByteArray());
				}
			};
		}
	}

	@Override
	public JavaFileObject getJavaFileForOutput(
			JavaFileManager.Location location, String className,
			JavaFileObject.Kind kind, FileObject sibling) throws IOException {
		if (kind == JavaFileObject.Kind.CLASS) {
			return new ClassOutputBuffer(className);
		} else {
			return super.getJavaFileForOutput(location, className, kind,
					sibling);
		}
	}

	static JavaFileObject makeStringSource(String name, String code) {
		return new StringInputBuffer(name, code);
	}

	static URI toURI(String name) {
		File file = new File(name);
		if (file.exists()) {// 如果文件存在,返回他的URI
			return file.toURI();
		} else {
			try {
				final StringBuilder newUri = new StringBuilder();
				newUri.append("mfm:///");
				newUri.append(name.replace('.', '/'));
				if (name.endsWith(EXT)) {
					newUri.replace(newUri.length() - EXT.length(),
							newUri.length(), EXT);
				}
				return URI.create(newUri.toString());
			} catch (Exception exp) {
				return URI.create("mfm:///com/sun/script/java/java_source");
			}
		}
	}
}
;