九、JavaAgent核心——Instrumentation
动态 Instrumentation 是 Java SE 5 的新特性,它在 java.lang.instrument 包中,它把 Java 的 instrument 功能从本地代码中释放出来,使其可以用 Java 代码的方式解决问题。使用 Instrumentation,开发者可以构建一个独立于应用程序的代理程序(Agent),用来监测和协助运行在 JVM 上的程序,甚至可以替换和修改某些类的定义。有了这样的功能,开发者就可以实现更为灵活的虚拟机监控和 Java的 类操作了,这样的特性实际上提供了一种JVM级别支持的 AOP方式,使得开发者无需对原有应用做任何修改,就可以实现类的动态修改和增强
instrumentation接口:
9.1 如何使用Instrumentation
Instrumentation类在API注释中有十分简洁的使用方式描述: 有两种方式可以获取Instrumentation接口的实例:
9.2 premain和agentmain非本质功能的区别
premain需要通过命令行使用外部代理jar包;而agentmain则可以通过attach机制直接附着到目标VM中加载代理,也就是使用agentmain方式下,操作attach的程序和被代理的程序可以是完全不同的两个程序。
premain方式回调到ClassFileTransformer中的类是虚拟机加载的所有类,这个是由于代理加载的顺序比较靠前决定的,在开发者逻辑看来就是:所有类首次加载并且进入程序main()方法之前,premain方法会被激活,然后所有被加载的类都会执行ClassFileTransformer列表中的回调。
agentmain方式由于是采用attach机制,被代理的目标程序VM有可能很早之前已经启动,当然其所有类已经被加载完成,这个时候需要借助Instrumentation.retransformClasses(Class<?>... classes)让对应的类可以重新转换,从而激活重新转换的类执行ClassFileTransformer列表中的回调。
premain方式是JDK1.5引入的,而agentmain方式是JDK1.6引入的,也就是JDK1.6之后可以自行选择使用premain或者agentmain
9.3 Instrumentation的局限性
大多数情况下,我们使用Instrumentation都是使用其字节码插桩的功能,或者笼统说就是类重定义(Class Redefine)的功能,但是有以下的局限性:
premain和agentmain两种方式修改字节码的时机都是类文件加载之后,也就是说必须要带有Class类型的参数,不能通过字节码文件和自定义的类名重新定义一个本来不存在的类。
类的字节码修改称为类转换(Class Transform),类转换其实最终都回归到类重定义Instrumentation.redefineClasses()方法,此方法有以下限制:
新类和老类的父类必须相同。
新类和老类实现的接口数也要相同,并且是相同的接口。
新类和老类访问符必须一致。
新类和老类字段数和字段名要一致。
新类和老类新增或删除的方法必须是private static/final修饰的。
可以修改方法体。