这篇文章主要介绍“Java怎么利用反射实现动态运行一行或多行代码”,在日常操作中,相信很多人在Java怎么利用反射实现动态运行一行或多行代码问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Java怎么利用反射实现动态运行一行或多行代码”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!
Talk is cheap, show me the code!
先来看代码:
public class TestEval { public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException { long start = System.currentTimeMillis(); eval("int sum = 0; for (int i = 0; i < 1000; i++){sum += i;} System.out.println(sum);"); long end = System.currentTimeMillis(); System.out.println("运行耗时:"+(end-start)+"ms"); } public static void eval(String code) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException { //创建 eval.java 文件 File file = new File("src/Eval.java"); //创建缓冲输出流 BufferedWriter buffer = new BufferedWriter(new FileWriter(file)); //将 code 里面的内容写入 buffer 中 buffer.write("public class Eval{ " //必须是 public class,否则无法访问。 + "public void test(){ " + code+" }" +" }"); buffer.close(); File parentFile = new File(file.getParent()); //注意这里要补一个分隔符,不然会出错 File.pathSeparator //这可能是 src 和 src/ 表示意义的区别 String parentPath = parentFile.getAbsolutePath()+File.separatorChar; //文件分隔符,只能在前面加,因为它是和平台有关的, //而这里我需要使用正斜杠,在windows平台下,默认是反斜杠 parentPath = parentPath.replace('', '/'); //反斜杠要使用转义字符 //Eval.class的位置,我的电脑上是 file:///F:/JavaProject/eval/src/ //注意最后面的文件分隔符 //创建一个 URL 数组 String url = "file:///"+parentPath; System.out.println("测试使用,查看输出文件路径:"+url); //需要自己编译Java文件,产生 class 文件 //下面这段代码用于编译文件,这是比较简单的了。 //这个绝对路径是Java源文件的路径,使用javac编译获取 .class文件 Process p = Runtime.getRuntime().exec("javac -encoding utf-8 "+file.getAbsolutePath()); InputStream compileError = p.getErrorStream(); //下面javac 的编译信息,与在命令行中使用的输出结果一样,只是把错误信息放到了控制台进行输出,如果没有输出,代表编译成功。否则会有错误提示。 byte[] b = new byte[1000]; while (compileError.read(b) != -1) { System.out.println(new String(b)); } compileError.close(); URL[] urls = {new URL(url)}; //以默认的 ClassLoader 作为父 ClassLoader, 创建 URLClassLoader URLClassLoader classLoader = new URLClassLoader(urls); Class<?> clazz = classLoader.loadClass("Eval"); Object ob = clazz.newInstance(); Method mtd = clazz.getMethod("test", new Class<?>[] {}); //使用 null 会有警告 //执行方法! mtd.invoke(ob, new Object[] {}); //使用 null 会有警告 classLoader.close(); //关闭 URLClassLoader 对象 System.out.println("执行结束"); } }
代码不多,但是用到了疯狂Java上的不少知识,比如使用 URLClassLoader 加载
字节码文件,因为一开始我是想使用 Class.forName(),但是发现总是提示找不到class文件,这似乎是 它的限制。然后我就想到了使用 URLClassLoader 这样加载的话,就不会有这个限制了。疯狂 Java 上面的例子是加载一个 jar 包,我这里就是加载一个单个 字节码文件(.class 文件)。
简要说明以下步骤:
1.首先将eval() 函数里面的 代码端,利用字符串拼接放入一个方法中,在放入一个类中:
//创建 eval.java 文件 File file = new File("src/Eval.java"); //创建缓冲输出流 BufferedWriter buffer = new BufferedWriter(new FileWriter(file)); //将 code 里面的内容写入 buffer 中 buffer.write("public class Eval{ " //必须是 public class,否则无法访问。 + "public void test(){ " + code+" }" +" }"); buffer.close();
2.然后利用
Runtime.getRuntim.exec()调用 javac 进行编译,因为源文件 是无法使用的,这是一种比较简单的编译文件的方式,我还见到了更为复杂的方式,但是限于水平,就采用了这种最简单的方式了。
3.获取字节码文件后,就要加载这个类了,但是使用
Class.forName()这个方法,似乎不能加载任意类,必须是项目里面存在的类才行,这一点我还没明白,转念又想到了
ClassLoader似乎可以解决这个问题,就是用了
URLClassLoader,因为它可以从网络或者本地其他地方加载 字节码 文件,这一点非常方便。
4.加载字节码文件以后,就进入了反射的知识了。
//这里的url 是当前 字节码文件的路径,注意后面要带上文件分隔符 '/' URL[] urls = {new URL(url)}; //以默认的 ClassLoader 作为父 ClassLoader, 创建 URLClassLoader URLClassLoader classLoader = new URLClassLoader(urls); Class<?> clazz = classLoader.loadClass("Eval"); Object ob = clazz.newInstance(); Method mtd = clazz.getMethod("test", new Class<?>[] {}); //使用 null 会有警告 //执行方法! mtd.invoke(ob, new Object[] {}); //使用 null 会有警告 classLoader.close(); //关闭 URLClassLoader 对象
5.然后就可以运行了,但是不知到为什么,一开始运行特别慢,居然要 5000+ms,后来第二天中午我修改了以下以后,居然变快了。
下面是运行截图: