慎用动态编译

简介:

动态编译一直是Java的梦想,从Java 6版本它开始支持动态编译了,可以在运行期直接编译.java文件,执行.class,并且能够获得相关的输入输出,甚至还能监听相关的事件。不过,我们最期望的还是给定一段代码,直接编译,然后运行,也就是空中编译执行(on-the-fly),来看如下代码:

复制代码
 1 public class Client {  
 2      public static void main(String[] args) throws Exception {  
 3          //Java源代码  
 4          String sourceStr = "public class Hello{    public String sayHello (String name) {return \"Hello,\" + name + \"!\";}}";  
 5          //类名及文件名  
 6          String clsName = "Hello";  
 7          //方法名  
 8          String methodName = "sayHello";  
 9          //当前编译器  
10          JavaCompiler cmp = ToolProvider.getSystemJavaCompiler();  
11          //Java标准文件管理器  
12          StandardJavaFileManager fm = cmp.getStandardFileManager(null,null,null);  
13          //Java文件对象  
14          JavaFileObject jfo = new StringJavaObject(clsName,sourceStr);  
15          //编译参数,类似于javac <options>中的options  
16          List<String> optionsList = new ArrayList<String>();  
17          //编译文件的存放地方,注意:此处是为Eclipse工具特设的  
18          optionsList.addAll(Arrays.asList("-d","./bin"));  
19          //要编译的单元  
20          List<JavaFileObject> jfos = Arrays.asList(jfo);  
21          //设置编译环境  
22          JavaCompiler.CompilationTask task = cmp.getTask(null, fm, null, optionsList,null,jfos);  
23          //编译成功  
24          if(task.call()){  
25              //生成对象  
26              Object obj = Class.forName(clsName).newInstance();  
27              Class<? extends Object> cls = obj.getClass();  
28              //调用sayHello方法  
29              Method m = cls.getMethod(methodName, String.class);  
30              String str = (String) m.invoke(obj, "Dynamic Compilation");  
31              System.out.println(str);  
32         }  
33     }  
34 }  
35 //文本中的Java对象  
36 class StringJavaObject extends SimpleJavaFileObject{  
37      //源代码  
38      private String content = "";  
39      //遵循Java规范的类名及文件  
40      public StringJavaObject(String _javaFileName,String _content){  
41            super(_createStringJavaObjectUri(_javaFileName),Kind.SOURCE);  
42            content = _content;  
43      }  
44      //产生一个URL资源路径  
45      private static URI _createStringJavaObjectUri(String name){  
46         //注意此处没有设置包名  
47         return URI.create("String:///" + name + Kind.SOURCE.extension);  
48      }  
49      //文本文件代码  
50      @Override  
51      public CharSequence getCharContent(boolean ignoreEncodingErrors)  
52             throws IOException {  
53         return content;  
54     }  
55 } 
复制代码

上面的代码较多,这是一个动态编译的模板程序,读者可以拷贝到项目中使用,代码中的中文注释也较多,相信读者看得懂,不多解释,读者只要明白一件事:只要是在本地静态编译能够实现的任务,比如编译参数、输入输出、错误监控等,动态编译就都能实现。

Java的动态编译对源提供了多个渠道。比如,可以是字符串(例子中就是字符串),可以是文本文件,也可以是编译过的字节码文件(.class文件),甚至可以是存放在数据库中的明文代码或是字节码。汇总成一句话,只要是符合Java规范的就都可以在运行期动态加载,其实现方式就是实现JavaFileObject接口,重写getCharContent、openInputStream、openOutputStream,或者实现JDK已经提供的两个SimpleJavaFileObject、ForwardingJavaFileObject,具体代码可以参考上个例子。

动态编译虽然是很好的工具,让我们可以更加自如地控制编译过程,但是在我目前所接触的项目中还是使用得较少。原因很简单,静态编译已经能够帮我们处理大部分的工作,甚至是全部的工作,即使真的需要动态编译,也有很好的替代方案,比如JRuby、Groovy等无缝的脚本语言。

另外,我们在使用动态编译时,需要注意以下几点:

(1)在框架中谨慎使用

比如要在Struts中使用动态编译,动态实现一个类,它若继承自ActionSupport就希望它成为一个Action。能做到,但是debug很困难;再比如在Spring中,写一个动态类,要让它动态注入到Spring容器中,这是需要花费老大功夫的。

(2)不要在要求高性能的项目使用

动态编译毕竟需要一个编译过程,与静态编译相比多了一个执行环节,因此在高性能项目中不要使用动态编译。不过,如果是在工具类项目中它则可以很好地发挥其优越性,比如在Eclipse工具中写一个插件,就可以很好地使用动态编译,不用重启即可实现运行、调试功能,非常方便。

(3)动态编译要考虑安全问题

如果你在Web界面上提供了一个功能,允许上传一个Java文件然后运行,那就等于说:“我的机器没有密码,大家都来看我的隐私吧”,这是非常典型的注入漏洞,只要上传一个恶意Java程序就可以让你所有的安全工作毁于一旦。

(4)记录动态编译过程

建议记录源文件、目标文件、编译过程、执行过程等日志,不仅仅是为了诊断,还是为了安全和审计,对Java项目来说,空中编译和运行是很不让人放心的,留下这些依据可以更好地优化程序。

 



本文转自SummerChill博客园博客,原文链接:http://www.cnblogs.com/DreamDrive/p/5417431.html,如需转载请自行联系原作者
相关文章
|
5月前
|
存储 缓存 Java
hyengine编译实现问题之复用 quickjs 原代码如何解决
hyengine编译实现问题之复用 quickjs 原代码如何解决
|
5月前
|
存储
hyengine 编译问题之创建对象异常如何解决
hyengine 编译问题之创建对象异常如何解决
|
7月前
|
存储 安全 Java
JAVA泛型大揭秘:如何在编译时守护你的代码安全?
【6月更文挑战第28天】Java泛型是JDK 5引入的特性,用于提升代码复用和类型安全性。通过类型参数如`&lt;T&gt;`,泛型在编译时确保数据类型的正确性。例如,泛型类`GenericList&lt;T&gt;`允许创建特定类型的列表,如`GenericList&lt;String&gt;`,阻止了不兼容类型元素的添加,防止运行时ClassCastException。泛型方法如`swap()`也确保了操作的类型安全,增强了代码的可读性和维护性。
54 0
|
8月前
|
设计模式 Java
Java源码中经常出现的for (;;) {}:理解无限循环
我们平常都会去阅读Java的源码,经常可以在源码中看到for (;;) {}的结构,本文将带你去理解无限循环。虽然无限循环可以在某些情况下很有用,例如在需要一直监听用户输入或执行某些后台任务的情况下,但它们也可能会导致程序变得不可响应或崩溃。因此,在使用无限循环时需要谨慎,确保有适当的退出条件或逻辑,以避免程序陷入死循环。
191 1
在多线程中某段代码只执行一次
使用Automic包中的AtomicBoolean结合CAS来实现。当只希望需要某段代码在多线程中只执行一次的时候,此时你会发现boolean这个变量类型是多么有用。
154 0
|
C语言 索引 编译器
|
前端开发 Java 编译器
JVM解释器与JIT编译器如何并存?
JVM解释器与JIT编译器如何并存?
148 0
|
算法 Java Python
pyhon:(垃圾回收机制)超详细——一次搞懂
python采用的是引用计数机制为主,标记-清除和分代收集两种机制为辅的策略
|
JavaScript 测试技术
干货 | Web自动化测试中显式等待与隐式等待该怎么用?
在实际工作中等待机制可以保证代码的稳定性,保证代码不会受网速、电脑性能等条件的约束。 等待就是当运行代码时,如果页面的渲染速度跟不上代码的运行速度,就需要人为的去限制代码执行的速度。 在做 Web 自动化时,一般要等待页面元素加载完成后,才能执行操作,否则会报找不到元素等各种错误,这样就要求在有些场景下加上等待。 最常见的有三种等待方式: - 隐式等待 - 显式等待 - 强制等待 后面会一一介
|
缓存 前端开发 Java
深入理解Java的动态编译(上)
笔者很久之前就有个想法:参考现有的主流ORM框架的设计,造一个ORM轮子,在基本不改变使用体验的前提下把框架依赖的大量的反射设计去掉,这些反射API构筑的组件使用「动态编译」加载的实例去替代,从而可以得到接近于直接使用原生JDBC的性能。于是带着这样的想法,深入学习Java的动态编译。编写本文的时候使用的是JDK11。
256 0
深入理解Java的动态编译(上)

热门文章

最新文章

下一篇
开通oss服务