手把手教你Java字节码Demo
今天就记录字节码的两种实现demo,作为入门了解。
Java Agent的最常用方式:
- 一种是premain方式:它属于静态注入。即在Java应用程序启动时,在类加载器对类的字节码进行加载之前对类字节码进行“再改造”来做功能增强(例如实现AOP)
- 一种是:HotSpot独有的attach方式(JDK1.6才出现),它能实现动态注入,对已经运行的Java应用的类进行字节码增强。
方式一:premain静态方式
(大多中间件/工具的方式)
1 | java -javaagent:/root/application-premain.jar MyApp |
如上面这句java启动命令,假定/root目录下已经有一个符合Java Agent规范的Jar了(这里指application-premain.jar),而MyApp是指我们Java应用的启动类(main方法的类),如此我们就能成功的对这个Java应用进行了静态注入。
接下来,分解一下具体实现步骤。
1、建个独立Maven工程
pom的必要依赖和设置
1 | <dependencies> |
2、准备MyApp
Java应用相当简单,里面就一个打印语句:
1 | public class MyApp |
此例中,我们目标对printSth()
方法进行字节码级改造,植入额外逻辑。(最终会打包成例子中的application-premain.jar)
Maven工程结构大致如下:
3、编写MyTransformer
MyTransformer类是具体实现字节码植入的实现类。
此例中,字节码植入的逻辑是两个打印语句。
具体代码如下:
1 | package org.example.asm.premain; |
可以看到MyTransformer实现了ClassFileTransformer接口,ClassFileTransformer是专门为Java Agent提供类转换功能的接口。
在transform方法中,我们可以大显身手了,从上面代码片段可以看出,我们只是对MyApp#printSth方法的之前和末尾各加入了一条打印语句,你可能会奇怪,不是字节码吗?为啥可以直接像表达式引擎一样直接输入Java表达式?是因为这里使用了javassist这一轻量级的字节码工具,它帮我们屏蔽了字节码的细节,使我们可以只关注Java代码。
4、编写Premain类
有了MyTransformer,在哪用?答案就在PremainMain类中,PremainMain要做的事情很简单,就是把我们自定义的类转换器MyTransformer加到前面提到的Instrumentation实例中:
1 | package org.example.asm.premain; |
注意
:PremainMain#premain的方法签名是Java Agent内部约定的,不能随意修改。
5、编写MANIFEST.MF
Java Agent是怎么知道premain方法在哪个类中呢?答案就application-premain.jar的resources/META-INF/MANIFEST.MF文件中,MANIFEST.MF文件内容如下:
1 | Manifest-Version: 1.0 |
注意:最后一行需要留一个空行
6、打包&运行
几步操作下来,必要的文件已就绪,我们把它打成一个jar包(需要包含javassist),需用到maven-assembly-plugin插件(再check下pom),项目的pom.xml文件内容如下:
1 | <build> |
我们直接执行Maven的打包命令:
1 | mvn clean package |
打包完成后,我们会在target目录下得到:application-premain.jar 和 application-premain-jar-with-dependences.jar。注,用application-premain-jar-with-dependences.jar
哦。为了方便,可以在cp命令拷贝到/root目录时顺便重命名的短一些。(拷贝其他任何目录都可以的哦)
一切就绪了,idea上运行
运行结果如下:
1 | =====start===== |
可以看到,原本只有一个打印”Hello World”语句的MyApp类,在前后加了两条打印语句,目标达成!!!!!
方式二、AttachAgent动态方式
(混动工程的实现方式)
如何动态注入字节码呢?为了更清晰的展示,我们创建两个独立的maven工程,一个模拟业务应用:循环执行printSth方法。一个模拟动态attach植入。
OrderApp代码如下
1 | public class OrderApp |
准备动态植入的代码
1、编写Transformer类
1 | package org.attach; |
2、新增AttachAgent类
1 | package org.example.asm.attach; |
这里约定好的方法是agentmain,但agentmain方法的本质也是把MyTransformer添加到instrumentation中,进而动态刷新目标class的transformer。
3、配置MANIFEST.MF
1 | Manifest-Version: 1.0 |
4、test-asm-attach工程打包
得到attach-premain-jar-with-dependencies.jar和attach-premain.jar
记得使用:attach-premain-jar-with-dependencies.jar(对应下面attchMain中的jar,按需重命名)
5、此时,先运行OrderApp
会看到,此时OrderApp循环打印“Hello World!”
6、编写并运行动态AttachMain代码
编写attachMain,并attach到OrderApp上去。
1 | package org.attach; |
运行上述AttachMain,此时查看OrderApp的控制台界面,会得到:
至此,就完成了。