java开发150个建议(12)


回到顶部
建议17:慎用动态编译
动态编译一直是java的梦想,从Java6开始支持动态编译了,可以再运行期直接编译.java文件,执行.class,并且获得相关的输入输出,甚至还能监听相关的事件 。不过,我们最期望的还是定一段代码,直接编译,然后运行,也就是空中编译执行(on-the-fly),看如下代码:
1 import java.io.IOException;2 import java.lang.reflect.Method;3 import java.net.URI;4 import java.util.ArrayList;5 import java.util.Arrays;6 import java.util.List;7 8 import javax.tools.JavaCompiler;9 import javax.tools.JavaFileObject;10 import javax.tools.SimpleJavaFileObject;11 import javax.tools.StandardJavaFileManager;12 import javax.tools.ToolProvider;13 14 public class Client17 {15public static void main(String[] args) throws Exception {16// Java源代码17String sourceStr = "public class Hello { public String sayHello (String name) {return \"Hello,\"+name+\"!\";}}";18// 类名及文件名19String clsName = "Hello";20// 方法名21String methodName = "sayHello";22// 当前编译器23JavaCompiler cmp = ToolProvider.getSystemJavaCompiler();24// Java标准文件管理器25StandardJavaFileManager fm = cmp.getStandardFileManager(null, null,26null);27// Java文件对象28JavaFileObject jfo = new StringJavaObject(clsName, sourceStr);29// 编译参数,类似于javac 中的options30List optionsList = new ArrayList();31// 编译文件的存放地方,注意:此处是为Eclipse工具特设的32optionsList.addAll(Arrays.asList("-d", "./bin"));33// 要编译的单元34List jfos = Arrays.asList(jfo);35// 设置编译环境36JavaCompiler.CompilationTask task = cmp.getTask(null, fm, null,37optionsList, null, jfos);38// 编译成功39if (task.call()) {40// 生成对象41Object obj = Class.forName(clsName).newInstance();42Class cls = obj.getClass();43// 调用sayHello方法44Method m = cls.getMethod(methodName, String.class);45String str = (String) m.invoke(obj, "Dynamic Compilation");46System.out.println(str);47}48 49}50 }51 52 class StringJavaObject extends SimpleJavaFileObject {53// 源代码54private String content = "";55 56// 遵循Java规范的类名及文件57public StringJavaObject(String _javaFileName, String _content) {58super(_createStringJavaObjectUri(_javaFileName), Kind.SOURCE);59content = _content;60}61 62// 产生一个URL资源路径63private static URI _createStringJavaObjectUri(String name) {64// 注意,此处没有设置包名65return URI.create("String:///" + name + Kind.SOURCE.extension);66}67 68// 文本文件代码69@Override70public CharSequence getCharContent(boolean ignoreEncodingErrors)71throws IOException {72return content;73}74 }
上面代码较多,可以作为一个动态编译的模板程序 。只要是在本地静态编译能够实现的任务,比如编译参数,输入输出,错误监控等,动态编译都能实现 。
【java开发150个建议】Java的动态编译对源提供了多个渠道 。比如,可以是字符串,文本文件,字节码文件,还有存放在数据库中的明文代码或者字节码 。汇总一句话,只要符合Java规范的就可以在运行期动态加载,其实现方式就是实现接口,重写、、,或者实现JDK已经提供的两个、ject,具体代码可以参考上个例子 。
动态编译虽然是很好的工具,让我们可以更加自如的控制编译过程,但是在我们目前所接触的项目中还是使用较少 。原因很简单,静态编译已经能够帮我们处理大部分的工作,甚至是全部的工作,即使真的需要动态编译,也有很好的替代方案,比如Jruby、等无缝的脚本语言 。另外,我们在使用动态编译时,需要注意以下几点:
在框架中谨慎使用:比如要在中使用动态编译,动态实现一个类,它若继承自就希望它成为一个 。能做到,但是debug很困难;再比如在中,写一个动态类,要让它注入到容器中,这是需要花费老大功夫的 。不要在要求性能高的项目中使用:如果你在web界面上提供了一个功能,允许上传一个java文件然后运行,那就等于说:"我的机器没有密码,大家都可以看看",这是非常典型的注入漏洞,只要上传一个恶意Java程序就可以让你所有的安全工作毁于一旦 。记录动态编译过程:建议记录源文件,目标文件,编译过程,执行过程等日志,不仅仅是为了诊断,还是为了安全和审计,对Java项目来说,空中编译和运行时很不让人放心的,留下这些依据可以很好地优化程序 。