将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");
			}
		}
	}
}
  • 9
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
动态编译执行Java代码是指在运行时通过编程动态地将Java代码编译成字节码并执行的过程。这种方式可以在不需要预先编译和打包的情况下,实现程序的动态扩展和灵活性。 在Java中,可以通过使用Java Compiler API来实现动态编译。首先,我们需要获取一个编译器实例,即JavaCompiler对象。然后,我们可以使用该对象的getTask方法创建一个编译任务,指定编译器所需的输入、输出和其他选项。我们需要提供一个JavaFileManager来管理编译任务的输入和输出,通常使用StandardJavaFileManager实现。 编译任务创建完成后,我们可以将需要编译Java源代码文件或代码字符串作为输入,然后调用compile方法进行编译编译完成后,可以通过JavaFileManager的getJavaFileObjects方法,获取编译生成的类文件的引用,进而获取类文件的字节码。 动态编译完成后,我们可以使用Java Reflection API加载并执行编译生成的类。通过Class.forName方法加载类,然后可以使用反射机制创建类的实例并调用类的方法。 动态编译执行Java代码在一些特定的场景中非常有用。例如,当需要在运行时根据特定条件来生成不同的代码逻辑时,动态编译可以方便地实现。此外,它还可以用于实现动态加载和卸载模块、插件和扩展等功能,提升系统的灵活性和可扩展性。 需要注意的是,动态编译执行Java代码可能存在一些潜在的安全风险,因为它可以执行未经过严格验证的代码。因此,在使用动态编译功能时应谨慎处理用户输入,以防止代码注入和其他安全问题的发生。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yelvens

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值