9.3.2 思路

在程序实现的过程中,我们需要解决以下3个问题:

如何编译提交到服务器的Java代码?

如何执行编译之后的Java代码?

如何收集Java代码的执行结果?

对于第一个问题,我们有两种思路可以选择,一种是使用tools.jar包(在Sun JDK/lib目录下)中的com.sun.tools.javac.Main类来编译Java文件,这其实和使用Javac命令编译是一样的。这种思路的缺点是引入了额外的JAR包,而且把程序“绑死”在Sun的JDK上了,要部署到其他公司的JDK中还得把tools.jar带上(虽然JRockit和J9虚拟机也有这个JAR包,但它总不是标准所规定必须存在的)。另外一种思路是直接在客户端编译好,把字节码而不是Java代码传到服务端,这听起来好像有点投机取巧,一般来说确实不应该假定客户端一定具有编译代码的能力,但是既然程序员会写Java代码去给服务端排查问题,那么很难想象他的机器上会连编译Java程序的环境都没有。

对于第二个问题,简单地一想:要执行编译后的Java代码,让类加载器加载这个类生成一个Class对象,然后反射调用一下某个方法就可以了(因为不实现任何接口,我们可以借用一下Java中人人皆知的“main()”方法)。但我们还应该考虑得更周全些:一段程序往往不是编写、运行一次就能达到效果,同一个类可能要反复地修改、提交、执行。另外,提交上去的类要能访问服务端的其他类库才行。还有,既然提交的是临时代码,那提交的Java类在执行完后就应当能卸载和回收。

最后的一个问题,我们想把程序往标准输出(System.out)和标准错误输出(System.err)中打印的信息收集起来,但标准输出设备是整个虚拟机进程全局共享的资源,如果使用System.setOut()/System.setErr()方法把输出流重定向到自己定义的PrintStream对象上固然可以收集输出信息,但也会对原有程序产生影响:会把其他线程向标准输出中打印的信息也收集了。虽然这些并不是不能解决的问题,不过为了达到完全不影响原程序的目的,我们可以采用另外一种办法,即直接在执行的类中把对System.out的符号引用替换为我们准备的PrintStream的符号引用,依赖前面学习的知识,做到这一点并不困难。