关于Java利用反射实现动态运行一行或多行代码

作者:CrazyDragon_King 时间:2021-11-27 23:12:55 

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{\n"            //必须是 public class,否则无法访问。
+ "public void test(){\n"
+ code+"\n}"
+"\n}");
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{\n"            //必须是 public class,否则无法访问。
+ "public void test(){\n"
+ code+"\n}"
+"\n}");
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,后来第二天中午我修改了以下以后,居然变快了。

Java还是很有趣的,继续深入学习,会发现更多有趣的知识。
下面是运行截图:

关于Java利用反射实现动态运行一行或多行代码

关于Java利用反射实现动态运行一行或多行代码

来源:https://blog.csdn.net/qq_40734247/article/details/101293619

标签:Java,反射,动态,运行
0
投稿

猜你喜欢

  • Groovy的规则脚本引擎实例解读

    2023-07-11 21:24:04
  • java 单例的五种实现方式及其性能分析

    2023-05-10 10:50:51
  • 详解Android中提示对话框(ProgressDialog和DatePickerDialog和TimePickerDialog&PopupWindow)

    2023-05-10 19:27:43
  • 关于Android 6.0权限的动态适配详解

    2021-09-16 07:56:34
  • C#预定义的基础类型转换

    2023-08-13 03:14:16
  • C#中读写INI配置文件的方法

    2021-10-29 19:57:37
  • C++实现softmax函数的面试经验

    2023-06-16 02:07:47
  • android使用handlerthread创建线程示例

    2023-07-05 17:29:57
  • C#中的WebRequest与WebResponse抽象类、DNS静态类、Ping类介绍

    2022-12-28 05:22:15
  • Android Button 自带阴影效果另一种解决办法

    2021-06-07 04:23:02
  • Android应用程序模型之应用程序,任务,进程,线程分析

    2021-10-09 02:17:45
  • Android编程之OpenGL绘图技巧总结

    2022-07-25 05:47:55
  • Java中的匿名对象定义与用法实例分析

    2023-04-11 20:03:05
  • Android接入阿里云热修复介绍

    2023-09-16 08:53:01
  • C#文件下载实例代码(适用于各个浏览器)

    2022-12-28 06:25:48
  • android Jsoup获取网站内容 android获取新闻标题实例

    2021-09-24 22:56:03
  • Android编程使用Fragment界面向下跳转并一级级返回的实现方法

    2021-08-20 19:56:51
  • Ubuntu搭建Java开发环境笔记

    2023-10-10 14:27:49
  • 一文带你了解Java中的函数式编程

    2022-08-02 07:32:29
  • Java面向对象程序设计多态性示例

    2021-09-24 11:57:57
  • asp之家 软件编程 m.aspxhome.com