关于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还是很有趣的,继续深入学习,会发现更多有趣的知识。
下面是运行截图:
来源:https://blog.csdn.net/qq_40734247/article/details/101293619


猜你喜欢
- 前言Java中URL传中文时乱码的问题相信不少朋友都遇到过,最近就遇到一个问题,就是在Action当中把一条中文信息绑定在URL的后面,Ac
- 本文介绍的仿IOS对话框的实现,先来看一下效果图具体代码如下:public class AlertDialog { private Cont
- 工厂方法模式动机创建一个对象往往需要复杂的过程,所以不适合包含在一个复合工厂中,当有新的产品时,需要修改这个复合的工厂,不利于扩展。而且,有
- 最近开发过程中遇到了一个问题,之前没有太注意,这里记录一下。我用的SpringBoot版本是2.0.5,在跟前端联调的时候,有个请求因为用户
- “深入浅出,人人都是程序员”开发过android手机的同学都知道在eclipse中可以直接查找到SHA1值,但是使用intellij ide
- 项目中有需要多次统计 某些集合中 的某个属性值,所以考虑封装一个方法,让其其定义实现计算方式。 话不多说,看代码:1、封装的自定义集合工具类
- 本文实例讲述了android从资源文件中读取文件流并显示的方法。分享给大家供大家参考。具体如下:在android中,假如有的文本文件,比如T
- 背景:新需求需要引入新jar包,引入后发现本地启动没有报错,发到测试环境提示某个bean无法创建,nested exception is j
- 实现多表联合查询还是在david.mybatis.model包下面新建一个Website类,用来持久化数据之用,重写下相应toString(
- 利用属性动画实现优酷菜单,供大家参考,具体内容如下布局文件<RelativeLayout xmlns:android="ht
- 前言我们都知道,kill在linux系统中是用于杀死进程。kill pid [..]kill命令可将指定的信号发送给相应的进程或工作。 ki
- 1、JavaBean介绍 * JavaBean的定义:JavaBeans是Java中一种特殊的类,可以将多个对象封装到一个对象(bean)
- list stream: reduce的使用stream 中的 reduce 的主要作用就是stream中元素进行组合,组合的方式可以是加减
- 目录前言一、Apache poi1.1 首先添加依赖1.2 导出excel1.2.1 HSSF方式导出(.xls方式)1.2.2 XSSF方
- 具体代码如下所示:private string GetWeekName(DayOfWeek week) { &nb
- 情况一:配置文件,无法被导出或者生效修改前:修改后:究其原因,这是由于Maven的约定大于配置,导致我们写的配置文件,无法被导出或者生效的问
- 简介备忘录设计模式(Memento Design Pattern)也叫作快照(Snapshot)模式,主要用于实现防丢失、撤销、恢复等功能。
- 本文以实例形式详细讲述了Java的反射机制,是Java程序设计中重要的技巧。分享给大家供大家参考。具体分析如下:首先,Reflection是
- 今天碰到一个很坑的问题,折腾了五六个小时,网上也收不到答案,国外有哥们碰到了,但是看到有解决方法的回复,废话不多说了。现象:运行maven
- springboot配置文件中属性变量引用@@这种属性应用方式是field_name=@field_value@。两个@符号是springb