java编程思想-动态编译
一般,我们用反射是可以在运行时洞悉一个类的信息(无论公私有),但是我们也可以在运行时创建一个新的类(原先不存在),然后加载该类,并调用该类中的函数。
1.创建一个类
这里我简便的用IO流将一个已经完成的类(与当前工程不再一个目录下),拷贝到当前工程目录下,当然也可以在运行时直接用字符串拼接处一个类,写入文件。
@SuppressWarnings("resource") public static void copyClass() throws IOException{ File file = new File("src/com/annotation/AnnotationDemo.java"); FileChannel in = new FileInputStream("/Users/admin/Desktop/Hello.java").getChannel(); FileChannel out = new FileOutputStream("src/com/annotation/Hello.java").getChannel(); ByteBuffer buff = ByteBuffer.allocate(1024); while(in.read(buff) != -1){ buff.flip(); out.write(buff); buff.clear(); } out.close(); in.close(); }
2.加载该类
由于我们创建好的类(文件)是.java,且该文件相当于是一种非正常途径建立的,所以并没有被编译过,所以要进行编译产生.class文件。
但是这里不能使用:
RunTime run = RunTime.getRunTime();
run.exec(command); //command = javac classname.java
因为他不会被执行
所以这里要使用专门的类编译工具,由JDK直接提供。
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null); Iterable units = fileMgr.getJavaFileObjects("src/com/annotation/Hello.java"); CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units); t.call(); fileMgr.close();
还有一点要注意的是,编译后的类直接在.java文件的同级目录下(当然也可以设置成将编译后产生的.class文件让入相应的位置),一般我们用eclipes的话 .class文件放在bin文件下,.java文件放在.src文件下的,所以我在这里做一个文件挪动的工作:
Runtime run = Runtime.getRuntime(); run.exec("mv 目标.class当前路径 "+ "该.class应该被存放的路径");
3.反射调用
Class c = Class.forName("com.annotation.Hello"); Object o = c.newInstance(); Method m = c.getMethod("hello"); m.invoke(o); System.out.println(c.getName());
因为类已经加载好了,所以我们就可以使用它的,当然是用反射的方式来调用,这种情况下,与一般的反射并没有区别了。
4.输出结果
helloworld
com.annotation.Hello
5.总结
其实这个例子意义并不大,只是感觉这应该是个比较偏门的东西,就拿出来分享一下,java并非在运行时不能创建类,相反的,java在运行时可以创建一个类,其实本质就相当于创建一个文件,但是重要的是如何要这个类被编译,这里只能使用javax提供的显示的类编译工具,一旦编译完成,产生.class文件后,之后的操作就一样了。
我想到的用途:
实现一个在线编译功能。(这样的话就变成调用另一个类的main方法。)
热部署(不停机替换文件)
缺点:
其实静态编译就已经能够完成绝大部分功能,并没有很大的需求使用动态编译
动态编译在框架中使用需要比较谨慎,比如在spring中动态编译一个类并把他加入到bean容器中是非常麻烦的
有其他更好的无缝脚本来支持动态编译,比如Groovy.
毕竟是一个编译过程,还是比较费时的
会产生注入漏洞,万一源代码来源有恶意BUG,编译之后可能产生安全隐患。