JDK1.6“新“特性Instrumentation之JavaAgent(推薦)
簡介
Java Agent是在JDK1.5以后,我們可以使用agent技術(shù)構(gòu)建一個獨立于應(yīng)用程序的代理程序(即為Agent),用來協(xié)助監(jiān)測、運行甚至替換其他JVM上的程序。使用它可以實現(xiàn)虛擬機級別的AOP功能。
Agent分為兩種,一種是在主程序之前運行的Agent,一種是在主程序之后運行的Agent(前者的升級版,1.6以后提供)。
JavaAgent的作用Agent給我們程序帶來的影響

使用Agent-premain方法影響的程序效果圖

使用Agent-agentmain方法影響的程序效果圖

JavaAgent相關(guān)的API
在java.lang.instrument包下 給我們提供了相關(guān)的API
而最為主要的就是Instrumentation這個接口中的幾個方法

public interface Instrumentation {
/**
* 添加Transformer(轉(zhuǎn)換器)
* ClassFileTransformer類是一個接口,通常用戶只需實現(xiàn)這個接口的 byte[] transform()方法即可;
* transform這個方法會返回一個已經(jīng)轉(zhuǎn)換過的對象的byte[]數(shù)組
* @param transformer 攔截器
* @return canRetransform 是否能重新轉(zhuǎn)換
*/
void addTransformer(ClassFileTransformer transformer, boolean canRetransform);
/**
* 重新觸發(fā)類加載,
* 該方法可以修改方法體、常量池和屬性值,但不能新增、刪除、重命名屬性或方法,也不能修改方法的簽名
* @param classes Class對象
* @throws UnmodifiableClassException 異常
*/
void retransformClasses(Class<?>... classes) throws UnmodifiableClassException;
/**
* 直接替換類的定義
* 重新轉(zhuǎn)換某個對象,并已一個新的class格式,進行轉(zhuǎn)化。
* 該方法可以修改方法體、常量池和屬性值,但不能新增、刪除、重命名屬性或方法,也不能修改方法的簽名
* @param definitions ClassDefinition對象[Class定義對象]
* @throws ClassNotFoundException,UnmodifiableClassException 異常
*/
void redefineClasses(ClassDefinition... definitions)throws ClassNotFoundException, UnmodifiableClassException;
/**
* 獲取當前被JVM加載的所有類對象
* @return Class[] class數(shù)組
*/
Class[] getAllLoadedClasses();
}
后面我們會在代碼中具體用到這些方法。再詳細說明。
JavaAgent-premain方法1-初探效果:
實現(xiàn)main方法前執(zhí)行業(yè)務(wù)邏輯
Agent1.java
public class Agent1 {
public static void premain(String agent){
System.out.println("Agent1 premain :" + agent);
}
}
Demo1.java
public class Demo1 {
/**
* VM參數(shù)
* -javaagent:D:\desktop\text\code\mycode\JavaAgentDemo\agent\target/agent.jar=input
* */
public static void main(String[] args) throws Exception {
System.out.println("demo1");
}
}
resources/META-INF/MANIFEST.MF
Manifest-Version: 1.0 Archiver-Version: Plexus Archiver Built-By: dahuoyzs Created-By: Apache Maven 3.6.0 Build-Jdk: 1.8.0_171 Premain-Class: cn.bigfire.Agent1 Can-Retransform-Classes: true
運行效果
Agent1 premain :input
demo1
JavaAgent-premain方法2-實現(xiàn)修改代碼邏輯效果:
實現(xiàn) 修改 程序源代碼 hello -> hello agented
Agent2.java
public class Agent2 {
/**
* 可以運行在main方法啟動前
* @param agent 輸入的參數(shù)
* @param instrumentation 輸入的參數(shù)
*/
public static void premain(String agent, Instrumentation instrumentation){
System.out.println("Agent2 premain 2param :" + agent);
instrumentation.addTransformer(new ConsoleTransformer(),true);
}
}
ConsoleTransformer.java
public class ConsoleTransformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
if (className.equals("cn/bigfire/Console")){
String root = StrUtil.subBefore(System.getProperty("user.dir"), "JavaAgentDemo", true);
String classFile = root + "JavaAgentDemo/agent/src/main/resources/Console.class";
return FileUtil.readBytes(classFile);
}
return classfileBuffer;
}
}
Demo2.java
public class Demo2 {
/**
* VM參數(shù)
* -javaagent:D:\desktop\text\code\mycode\JavaAgentDemo\agent\target/agent.jar=input
* */
public static void main(String[] args) throws Exception {
new Thread(()->{
while (true){
Console.hello();// public static void hello(){System.out.println("hello"); }
ThreadUtil.sleep(2000);
}
}).start();
}
}
resources/META-INF/MANIFEST.MF
Manifest-Version: 1.0 Archiver-Version: Plexus Archiver Built-By: dahuoyzs Created-By: Apache Maven 3.6.0 Build-Jdk: 1.8.0_171 Premain-Class: cn.bigfire.Agent2 Can-Retransform-Classes: true
運行效果
Agent2 premain 2param :input
滿足條件
hello agented
hello agented
hello agented
hello agented
JavaAgent-premain方法3-無侵入動態(tài)修改程序源代碼實現(xiàn)方法耗時統(tǒng)計效果:
實現(xiàn)main方法外的所有方法統(tǒng)計時間
Agent3.java
public class Agent3 {
/**
* 可以運行在main方法啟動前
* @param agent 輸入的參數(shù)
* @param instrumentation instrumentation對象由JVM提供并傳入
*/
public static void premain(String agent, Instrumentation instrumentation) {
System.out.println("Agent3 premain :" + agent);
instrumentation.addTransformer(new TimeCountTransformer());
}
/**
* 時間統(tǒng)計Transformer 給要代理的方法添加時間統(tǒng)計
*/
private static class TimeCountTransformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) {
try {
className = className.replace("/", ".");
if (className.equals("cn.bigfire.Demo3")) {
//使用全稱,用于取得字節(jié)碼類<使用javassist>
CtClass ctclass = ClassPool.getDefault().get(className);
//獲得方法列表
CtMethod[] methods = ctclass.getDeclaredMethods();
//給方法設(shè)置代理
Stream.of(methods).forEach(method-> agentMethod(ctclass,method));
//CtClass轉(zhuǎn)byte[]數(shù)組
return ctclass.toBytecode();
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
/**
* 代理方法,把傳入的方法經(jīng)寫代理,并生成帶時間統(tǒng)計的方法,
* @param ctClass javassist的Class類
* @param ctMethod javassist的ctMethod方法
* */
public static void agentMethod(CtClass ctClass,CtMethod ctMethod){
try {
String mName = ctMethod.getName();
if (!mName.equals("main")){//代理除了main方法以外的所有方法
String newName = mName + "$Agent";
ctMethod.setName(newName);
CtMethod newMethod = CtNewMethod.copy(ctMethod, mName, ctClass, null);
// 構(gòu)建新的方法體
String bodyStr = "{\n" +
"long startTime = System.currentTimeMillis();\n" +
newName + "();\n" +
"long endTime = System.currentTimeMillis();\n" +
"System.out.println(\""+newName+"() cost:\" +(endTime - startTime));\n" +
"}";
newMethod.setBody(bodyStr);// 替換新方法
ctClass.addMethod(newMethod);// 增加新方法
}
}catch (Exception e){
e.printStackTrace();
}
}
}
Demo3.java
public class Demo3 {
/**
* VM參數(shù)
* -javaagent:D:\desktop\text\code\mycode\JavaAgentDemo\agent\target/agent.jar=input
*/
public static void main(String[] args) throws Exception {
sleep1();
sleep2();
}
public static void sleep1(){
ThreadUtil.sleep(1000);
}
public static void sleep2(){
ThreadUtil.sleep(2000);
}
}
resources/META-INF/MANIFEST.MF
Manifest-Version: 1.0 Archiver-Version: Plexus Archiver Built-By: dahuoyzs Created-By: Apache Maven 3.6.0 Build-Jdk: 1.8.0_171 Class-Path: ../javassist-3.12.1.GA.jar Premain-Class: cn.bigfire.Agent3 Can-Retransform-Classes: true
運行效果
Agent3 premain :input
sleep1$Agent() cost:1005
sleep2$Agent() cost:2001
JavaAgent-agentmain方法1-實現(xiàn)運行時修改程序效果:
實現(xiàn)運行時 修改程序 hello -> hello agented
Agent4.java
public class Agent4 {
public static void premain(String agent){
System.out.println("Agent4 premain 1param:" + agent);
}
public static void premain(String agent, Instrumentation instrumentation) {
System.out.println("Agent4 premain 2param:" + agent);
//premain時,由于堆里還沒有相應(yīng)的Class。所以直接addTransformer,程序就會生效。
// instrumentation.addTransformer(new ConsoleTransformer(),true);
}
public static void agentmain(String agent, Instrumentation instrumentation){
System.out.println("Agent4 agentmain 2param :" + agent);
instrumentation.addTransformer(new ConsoleTransformer(),true);
//agentmain運行時 由于堆里已經(jīng)存在Class文件,所以新添加Transformer后
// 還要再調(diào)用一個 inst.retransformClasses(clazz); 方法來更新Class文件
for (Class clazz:instrumentation.getAllLoadedClasses()) {
if (clazz.getName().contains("cn.bigfire.Console")){
try {
instrumentation.retransformClasses(clazz);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
public static void agentmain(String agent){
System.out.println("Agent4 agentmain 1param :" + agent);
}
}
Demo4
public class Demo4 {
/**
* 打包agent4 -> 先運行demo2 -> 運行demo4 ->選擇程序demo2結(jié)尾的程序,即可運行時修改文件
* VM參數(shù)
* -javaagent:D:\desktop\text\code\mycode\JavaAgentDemo\agent\target/agent.jar=input
* */
public static void main(String[] args) throws Exception {
while (true){
List<VirtualMachineDescriptor> list = VirtualMachine.list();
for (int i = 0; i < list.size(); i++) {
VirtualMachineDescriptor jvm = list.get(i);;
System.out.println("[" +i+ "]ID:"+jvm.id()+",Name:"+jvm.displayName());
}
System.out.println("請選擇第幾個");
Scanner scanner = new Scanner(System.in);
int s = scanner.nextInt();
VirtualMachineDescriptor virtualMachineDescriptor = list.get(s);
VirtualMachine attach = VirtualMachine.attach(virtualMachineDescriptor.id());
String root = StrUtil.subBefore(System.getProperty("user.dir"), "JavaAgentDemo", true);
String agentJar = root + "JavaAgentDemo\\agent\\target\\agent.jar";
File file = new File(agentJar);
System.out.println(file.exists());
attach.loadAgent(agentJar,"param");
attach.detach();
}
}
}
resources/META-INF/MANIFEST.MF
Manifest-Version: 1.0 Archiver-Version: Plexus Archiver Built-By: dahuoyzs Created-By: Apache Maven 3.6.0 Build-Jdk: 1.8.0_171 Premain-Class: cn.bigfire.Agent4 Agent-Class: cn.bigfire.Agent4 Can-Retransform-Classes: true Can-Redefine-Classes: true
此時的運行順序
打包agent4 -> 先運行demo2 -> 運行demo4 ->選擇程序demo2結(jié)尾的程序,即可運行時修改文件
運行效果
Demo2
Agent4 premain 2param:input
hello
hello
Demo4
[0]ID:12480,Name:cn.bigfire.Demo2 [1]ID:14832,Name:org.jetbrains.kotlin.daemon.KotlinCompileDaemon --daemon-runFilesPath xxx [2]ID:14864,Name: [3]ID:3952,Name:cn.bigfire.Demo4 [4]ID:14852,Name:org.jetbrains.idea.maven.server.RemoteMavenServer36 [5]ID:11928,Name:org.jetbrains.jps.cmdline.Launcher xxx 請選擇第幾個 0 true
Demo2
Agent4 premain 2param:input hello hello Agent4 agentmain 2param :param hello agented hello agented hello agented
JavaAgent-agentmain方法2-實現(xiàn)動態(tài)修改日志級別效果:
實現(xiàn)運行時 修改程序 模擬項目中的動態(tài)日志 info <-> debug
Agent5.java
public class Agent5 {
public static void premain(String agent, Instrumentation instrumentation){
System.out.println("Agent5 premain 2param :" + agent);
instrumentation.addTransformer(new StartTransformer(),true);
//這個方式不行。因為啟動時Class都還沒有呢。
// for (Class clazz:inst.getAllLoadedClasses()) {
// if (clazz.getName().equals("cn.bigfire.LogLevelStarter")){
// try {
// switchDebug(clazz);
// instrumentation.retransformClasses(clazz);
// } catch (Exception e) {
// e.printStackTrace();
// }
// }
// }
}
public static void agentmain(String agent, Instrumentation instrumentation){
System.out.println("Agent5 agentmain 2param :" + agent);
for (Class clazz:instrumentation.getAllLoadedClasses()) {
if (clazz.getName().equals("cn.bigfire.LogLevelStarter")){
try {
switchAtomicDebug(clazz);
instrumentation.retransformClasses(clazz);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
public static class StartTransformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
//此時由于classBeingRedefined是空,所以還是不能用這個Class修改屬性呢,只能通過 讀取byte[]往堆里丟,才能用。
if (className.equals("cn/bigfire/LogLevelStarter")){
//【這是一個錯誤的思路】 premain的時候 classBeingRedefined是空的因為很多的Class還沒加載到堆中
// if (classBeingRedefined!=null){
// switchDebug(classBeingRedefined);
// return toBytes(classBeingRedefined);
// }
//正常的讀取一共文件byte[]數(shù)組
String root = StrUtil.subBefore(System.getProperty("user.dir"), "JavaAgentDemo", true);
String classFile = root + "JavaAgentDemo/agent/src/main/resources/LogLevelStarter.class";
return FileUtil.readBytes(classFile);
}
return classfileBuffer;
}
}
/**
* 可序列化對象轉(zhuǎn)byte[]數(shù)組
* @param clazz 要轉(zhuǎn)byte[]數(shù)組的對象
* @return byte[] 返回byte[]數(shù)組
*/
public static byte[] toBytes(Serializable clazz){
try {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream stream = new ObjectOutputStream(byteArrayOutputStream);
stream.writeObject(clazz);
return byteArrayOutputStream.toByteArray();
}catch (Exception e){
e.printStackTrace();
}
return null;
}
public static void switchDebug(Class clazz){
try {
Field field1 = clazz.getDeclaredField("isDebug");
field1.setAccessible(true);
boolean debug = field1.getBoolean(clazz);
field1.setBoolean(clazz,!debug);
}catch (Exception e){
e.printStackTrace();
}
}
public static void switchAtomicDebug(Class clazz){
try {
Field field2 = clazz.getDeclaredField("atomicDebug");
field2.setAccessible(true);
AtomicBoolean atomicDebug = (AtomicBoolean)field2.get(clazz);
atomicDebug.set(!atomicDebug.get());
}catch (Exception e){
e.printStackTrace();
}
}
}
注意,需要先把LogLevelStarter.java中的isDebug 改為true編譯一下。放到src/main/resources/目錄下;
LogLevelStarter.java
public class LogLevelStarter {
public static volatile boolean isDebug = false;
public static AtomicBoolean atomicDebug = new AtomicBoolean(false);
/**
* VM參數(shù)
* -javaagent:D:\desktop\text\code\mycode\JavaAgentDemo\agent\target/agent.jar=input
*/
public static void main(String[] args) throws Exception {
new Thread(()->{
for (;;){
//死循環(huán),每隔兩秒打印一個日志。
System.out.print(isDebug ? "volatile debug" : "volatile info");
System.out.print("\t");
System.out.println(atomicDebug.get() ? "atomicDebug debug" : "atomicDebug info");
ThreadUtil.sleep(2000);
}
}).start();
}
}
resources/META-INF/MANIFEST.MF
Manifest-Version: 1.0 Archiver-Version: Plexus Archiver Built-By: dahuoyzs Created-By: Apache Maven 3.6.0 Build-Jdk: 1.8.0_171 Premain-Class: cn.bigfire.Agent5 Agent-Class: cn.bigfire.Agent5 Can-Retransform-Classes: true Can-Redefine-Classes: true
此時的運行順序
打包agent5 -> 先運行LogLevelStarter -> 運行demo4 ->選擇程序LogLevelStarter結(jié)尾的程序,即可運行時修改文件
運行效果
LogLevelStarter
Agent5 premain 2param :input
volatile debug atomicDebug info
volatile debug atomicDebug info
Demo4
[0]ID:12592,Name:cn.bigfire.LogLevelStarter [1]ID:12880,Name:cn.bigfire.Demo4 [2]ID:14832,Name:org.jetbrains.kotlin.daemon.KotlinCompileDaemon --daemon-runFilesPath xxx [3]ID:14864,Name: [4]ID:14852,Name:org.jetbrains.idea.maven.server.RemoteMavenServer36 [5]ID:8116,Name:org.jetbrains.jps.cmdline.Launcher xxx 請選擇第幾個 0 true
LogLevelStarter
Agent5 premain 2param :input volatile debug atomicDebug info volatile debug atomicDebug info Agent5 agentmain 2param :param volatile debug atomicDebug debug volatile debug atomicDebug debug
在Agent5中,其實使用premain和agentmain。
premain把volatile修飾的isDbug給修改為true了。
而agentmain時把atomicDebug的值進行多次取反操作。
自己實現(xiàn)一個熱部署功能的大致思路
當運行完本項目中的幾個demo之后。
讀者可能對Java Agent有了一些基本的概念
最起碼我們知道了premain是可以運行在main函數(shù)前的。
而agentmain是可以在程序運行時,修改程序內(nèi)的一些類文件的。
那么熱部署很明顯就是使用的agentmain這個特性了
那么熱部署具體應(yīng)該怎么實現(xiàn)呢?
這里先有個大概的思路。后續(xù)如果有經(jīng)歷,可以簡單按照這個思路實現(xiàn)一下
思路
當我們文件發(fā)生修改的時候,項目會重新加載我們的類。
那么這里肯定會涉及到文件變化的觀察。 即 觀察者設(shè)計模式跑不了
首先遞歸當前項目目錄。并根據(jù)文件類型,如(.java ,xml,yml等)將此類文件注冊觀察者模式。
當文件內(nèi)容發(fā)生變化時,會調(diào)用 監(jiān)聽器中的回調(diào)方法;
在回調(diào)中完成如下(具體實現(xiàn)時未必需要)
使用Java1.6的JavaCompiler編譯Java文件;
自定義ClassLoader 裝載 編譯好的Class到堆中
使用agentmain修改原Class文件替換成新的Class文件
完成熱加載
JavaAgent的應(yīng)用場景
apm:(Application Performance Management)應(yīng)用性能管理。pinpoint、cat、skywalking等都基于Instrumentation實現(xiàn)
idea的HotSwap、Jrebel等熱部署工具
應(yīng)用級故障演練
Java診斷工具Arthas、Btrace等
{
"author": "大火yzs",
"title": "【JavaAgent】JavaAgent入門教程",
"tag": "JavaAgent,Instrumentation,運行時動態(tài)修改源程序",
"createTime": "2020-08-02 18:30"
}
總結(jié)
到此這篇關(guān)于JDK1.6“新“特性Instrumentation之JavaAgent的文章就介紹到這了,更多相關(guān)JDK1.6“新“特性Instrumentation內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springboot+thymeleaf+shiro標簽的實例
這篇文章主要介紹了springboot+thymeleaf+shiro標簽的實例,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-01-01
SpringMVC實現(xiàn)參數(shù)校驗配置方法
這篇文章主要介紹了SpringMVC實現(xiàn)參數(shù)校驗的配置方式,Spring MVC會拋出MethodArgumentNotValidException異常,并將錯誤信息綁定到相應(yīng)的字段上,感興趣的朋友跟隨小編一起看看吧2024-03-03
Java+mysql本地圖片上傳數(shù)據(jù)庫及下載示例
本篇文章主要介紹了Java+mysql本地圖片上傳數(shù)據(jù)庫及下載示例,具有一定的參加價值,有興趣的可以了解一下。2017-01-01

