从我们众所周知的 Java 到 Groovy,Jython, Scala 这些新青年们,这些语言的成功,除了语言的丰富特性外,还离不开强大的 JVM。


正在因为站在 JVM 的肩上,新青年们才看的更远。


虽说 JVM 之上可能跑这些小青年,但原则还是有的。毕竟 JVM 的第一个字母可是 J。


这里说的原则,或者说新青年们能跑在 JVM 这个舞台上的,是因为他们在使用各自语言语法编写之后,生成的内容都是 class 文件。 没错,就是那个和 Java

的 class 一样的 class。每个语言使用自己的编译器,按照规范生成 class 文件。


你是XX 请来对比这些语言的么?


当然不是。


我们主要看看这些操作 class 的阶段与方式,是谁在什么时候动了你的「class」。


还记得初学 Java 时,满怀信心的在命令行敲下 javac HelloWorld.java 的情形么? 运行程序时看着输出的「Hello World」,感觉这门语言已经上传完成,

全部进入你的大脑,可以改变世界了。


这里的javac 生产class, 他可不是class的搬运工。


常用 IDE 之后,写完代码,随着保存或者运行Main Class, 这个生成class的过程被隐藏了起来,连是谁编译的都不清楚。

甚至使用 Maven、Gradle 之后,每次都是在IDE 里双击666,拿着打好的包直接运行了。编译的过程,甚至打包出来的产物,都变成了一个可忽视的步骤。



但话说回来, class 在 JVM 中扮演着一个至关重要的角色, JVM 加载 「class」 之后,才生成了对应的 「Class」(注意C大写了) 对象,进而生成各个你们面向的「对象」。


所以有不少的工具、框架,在是 class 上做文章,通过操作class 的内容,来实现一些特定的功能。比如

Hibernate、OpenJPA 都是通过编译或者运行时增强,来修改 Domain Entity class。


大名鼎鼎的 Spring, 除了 Cglib这一类的动态代理功能实现AOP外,也提供了 LTW (Load Time Weave) 的功能,在加载 class的时候进行功能增强。


而对一个 class 进行 transform,入口在 Java 的 Instrumentation。  我们知道,这是 Java agent 实现的核心。无论是在运行前以javaagent 的方式

指定,还是在运行时通过 Attach 机制再连接上去,都会拿到 Instrumentation的一个实例。 有了它,可以进行一个addTransformer 的操作,

「没错,不是添加「变形金钢」」,是添加类的转换器,通过transformer,把类给改了。


这里的 LTW, 如果运行在 类似 Tomcat 这一类的容器中,由于类是由容器的 ClassLoader 来进行加载 ,少不了需要容器支持。




在 Tomcat 8.x 中,新增了InstrumentableClassLoader, 用于在加载类的时候进行类的 instrument。该 classLoader 需要添加一个transformer

用于对后续加载的class 进行 transform。


下方是具体 Tomcat 的ClassLoader在加载类时执行的操作:


Tomcat 在 addTransformer 之后,需要加载class时,如果是新加载,则会向下走,再根据transformer执行操作

        Iterator i$ = this.transformers.iterator(); // 这里是添加进来的转换器

            while(i$.hasNext()) {

                ClassFileTransformer transformer = (ClassFileTransformer)i$.next();

                try {

                    byte[] transformed =transformer.transform(this, internalName, (Class)null, (ProtectionDomain)null, binaryContent);

                    if(transformed != null) {

                        binaryContent = transformed;

                    }

                }

        }

        try {

// 这里创建类的时候,使用了转换之后的二进制内容

            clazz = this.defineClass(name,binaryContent, 0, binaryContent.length, new CodeSource(codeBase, certificates));

        } catch (UnsupportedClassVersionError var17) { }



除此之外,我们还可以在运行时修改 Class的定义,新增或者变更方法等,这个可以依赖 JVM 的 Attach机制,获取 Instrument 后再进行class的reDefine,这个我们在之前的文章中有过介绍:

类加载器与类的热替换(Hotswap)


所以,build time 或者 runtime 时对于 class 文件或者已加载的二进制内容会做修改,甚至在运行时也会被重新定义,进而生成新的Class。 所以这个也是可以运行在浏览器中的 Applet  需要运行在一个沙箱中保证安全的原因。


如果在应用运行时,遇到了比较怪异的行为,和你看到的源码不一致,不必惊讶,可能 class 是被修改过的。此时我们之前介绍过的一件武器就派上了用场:

SA(Java七武器系列长生剑 -- Java虚拟机的显微镜 Serviceability Agent) 可以将你疑惑的class 结构显示出来,或者dump到本地仔细观察。