9.3.2 思路_9.3 实战:自己动手实现远程执行功能

9.3.2 思路

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

  • 如何编译提交到服务器的Java代码?
  • 如何执行编译之后的Java代码?
  • 如何收集Java代码的执行结果?

对于第一个问题,我们有两种方案可以选择。一种在服务器上编译,在JDK 6以后可以使用Compiler API,在JDK 6以前可以使用tools.jar包(在JAVA_HOME/lib目录下)中的com.sun.tools.Javac.Main类来编译Java文件,它们其实和直接使用Javac命令来编译是一样的。这种思路的缺点是引入了额外的依赖,而且把程序绑死在特定的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的符号引用,依赖前面学习到的知识,做到这一点并不困难。