趣文网 > 作文大全

理论知识:操作字节码 Java热加载

2020-12-02 04:15:01
相关推荐

前言

今天无意看到美团技术团队的一篇文章,感觉非常的有意思,所以自己整了一篇文章,一起给大家分享一下:

正文

对于我们Java语言的开发者来说,下面的对话应该很熟悉:

Java的对象行为(方法、函数)是存储在方法区的。

“方法区中的数据从哪来?”

“方法区中的数据是类加载时从class文件中提取出来的。”

“class文件从哪来?”

“从Java或者其他符合JVM规范的源代码中编译而来。”

“源代码从哪来?”

“废话,当然是手写!”

这里为啥要有这样的对话?因为这里讨论一个有趣的问题:

在JSP时代,我们都有过这种经历:修完Java代码,对于浏览器来说,只需要刷新网页就可以看到效果。按我们正常的思维。对于java程序来说,哪怕是Hello world也需要重新编译然后重新run。那么JSP是如何做的类似”热更新“的呢?

让我们继续上面的对话:

“倒着推,手写没问题,编译没问题,至于加载……有没有办法加载一个已经加载过的类呢?如果有的话,我们就能修改字节码中目标方法所在的区域,然后重新加载这个类,这样方法区中的对象行为(方法)就被改变了,而且不改变对象的属性,也不影响已经存在对象的状态,那么就可以搞定这个问题了。可是,这岂不是违背了JVM的类加载原理?毕竟我们不想改变ClassLoader。”

“少年,可以去看看java.lang.instrument.Instrumentation。”

java.lang.instrument.Instrumentation

看完文档之后,我们发现这么两个接口:redefineClasses和retransformClasses。一个是重新定义class,一个是修改class。这两个大同小异,看reDefineClasses的说明:

This method is used to replace the definition of a class without reference to the existing class file bytes, as one might do when recompiling from source for fix-and-continue debugging. Where the existing class file bytes are to be transformed (for example in bytecode instrumentation) retransformClasses should be used.

都是替换已经存在的class文件,redefineClasses是自己提供字节码文件替换掉已存在的class文件,retransformClasses是在已存在的字节码文件上修改后再替换之。

当然,运行时直接替换类很不安全。比如新的class文件引用了一个不存在的类,或者把某个类的一个field给删除了等等,这些情况都会引发异常。所以如文档中所言,instrument存在诸多的限制:

The redefinition may change method bodies, the constant pool and attributes. The redefinition must not add, remove or rename fields or methods, change the signatures of methods, or change inheritance. These restrictions maybe be lifted in future versions. The class file bytes are not checked, verified and installed until after the transformations have been applied, if the resultant bytes are in error this method will throw an exception.

我们能做的基本上也就是简单修改方法内的一些行为,这对于我们开头的问题,打印一段日志来说,已经足够了。当然,我们除了通过reTransform来打印日志,还能做很多其他非常有用的事情,这个下文会进行介绍。

那怎么得到我们需要的class文件呢?一个最简单的方法,是把修改后的Java文件重新编译一遍得到class文件,然后调用redefineClasses替换。但是对于没有(或者拿不到,或者不方便修改)源码的文件我们应该怎么办呢?其实对于JVM来说,不管是Java也好,Scala也好,任何一种符合JVM规范的语言的源代码,都可以编译成class文件。JVM的操作对象是class文件,而不是源码。所以,从这种意义上来讲,我们可以说“JVM跟语言无关”。既然如此,不管有没有源码,其实我们只需要修改class文件就行了。

直接操作字节码

Java是软件开发人员能读懂的语言,class字节码是JVM能读懂的语言,class字节码最终会被JVM解释成机器能读懂的语言。无论哪种语言,都是人创造的。所以,理论上(实际上也确实如此)人能读懂上述任何一种语言,既然能读懂,自然能修改。只要我们愿意,我们完全可以跳过Java编译器,直接写字节码文件,只不过这并不符合时代的发展罢了,毕竟高级语言设计之始就是为我们人类所服务,其开发效率也比机器语言高很多。

对于人类来说,字节码文件的可读性远远没有Java代码高。尽管如此,还是有一些杰出的程序员们创造出了可以用来直接编辑字节码的框架,提供接口可以让我们方便地操作字节码文件,进行注入修改类的方法,动态创造一个新的类等等操作。其中最著名的框架应该就是ASM了,cglib、Spring等框架中对于字节码的操作就建立在ASM之上。

我们都知道,Spring的AOP是基于动态代理实现的,Spring会在运行时动态创建代理类,代理类中引用被代理类,在被代理的方法执行前后进行一些神秘的操作。那么,Spring是怎么在运行时创建代理类的呢?动态代理的美妙之处,就在于我们不必手动为每个需要被代理的类写代理类代码,Spring在运行时会根据需要动态地创造出一个类,这里创造的过程并非通过字符串写Java文件,然后编译成class文件,然后加载。Spring会直接“创造”一个class文件,然后加载,创造class文件的工具,就是ASM了。

到这里,我们知道了用ASM框架直接操作class文件,在类中加一段打印日志的代码,然后调用retransformClasses就可以了。

阅读剩余内容
网友评论
相关内容
延伸阅读
小编推荐

大家都在看

什么是英雄英语作文 价值观作文 幸福一家人作文500字 享受生命中的温暖作文 梦想让生活更美好500字作文 令人开心的一件事作文 春天的作文50字左右 英语5句话作文 游观音峡作文500字 四年级作文500字 乐的作文400字 假如我是学霸作文 移风易俗作文600字 科技与情感作文 我怀念的人作文 军训作文300字初一 三年级开学了作文 明天就要考试了作文 哈尔滨旅游英语作文 关于春天风景的作文 未来的衣服作文400 我家的节日作文500字 材料作文的素材 我 作文高中 制作沙包作文400字 不由自主作文 大自然的美景作文400字 我毕业了作文 夕阳作文300字 我的一天400字作文初一