以前的permain
方法只能在java程序啓動以前執行,並不能程序啓動以後再執行,可是在實際的不少的狀況下,咱們沒有辦法在虛擬機啓動之時就爲其設定代理,這樣實際上限制了instrument的應用。而Java SE 6的新特性改變了這種狀況,能夠經過Java Tool API中的attach方式來達到這種程序啓動以後設置代理的效果。html
Attach API 不是 Java 的標準 API,而是 Sun 公司提供的一套擴展 API,用來向目標 JVM 「附着」(Attach)代理工具程序的。有了它,開發者能夠方便的監控一個 JVM,運行一個外加的代理程序。java
Attach API 很簡單,只有 2 個主要的類,都在 com.sun.tools.attach
包裏面: VirtualMachine
表明一個 Java 虛擬機,也就是程序須要監控的目標虛擬機,提供了 JVM 枚舉,Attach
動做和 Detach
動做(從 JVM 上面解除一個代理)等等 ; VirtualMachineDescriptor
則是一個描述虛擬機的容器類,配合VirtualMachine
類完成各類功能。編程
結合上一篇文章和Attach,看看如何使用bash
Agent類增長了2個agentmain()方法,它們的參數不用,2個參數的優先級大於1個參數的,因此這裏只有agentmain (String agentArgs, Instrumentation inst)
會被執行微信
public class JpAgent {
public static void premain(String agentArgs){
System.out.println("我是一個參數的 Java Agent premain");
}
public static void agentmain (String agentArgs, Instrumentation inst) throws UnmodifiableClassException {
inst.addTransformer(new JpClassFileTransformerDemo(), true);
// retransformClasses 是 Java SE 6 裏面的新方法,它跟 redefineClasses 同樣,能夠批量轉換類定義
inst.retransformClasses(Dog.class);
System.out.println("我是兩個參數的 Java Agent agentmain");
}
public static void agentmain (String agentArgs){
System.out.println("我是一個參數的 Java Agent agentmain");
}
}
複製代碼
新增一個Dog類,內容以下,關鍵點在package 路徑要和目標程序同樣,其餘無所謂jvm
package cn.jpsite.learning;
public class Dog {
}
複製代碼
在以前的example01工程中新建一個帶有main函數的類WhileMain.java
ide
public class WhileMain {
public static void main(String[] args) throws InterruptedException {
System.out.println(new Dog().say());
int count = 0;
while (true) {
// 等待0.5秒
Thread.sleep(500);
count++;
String say = new Dog().say();
// 輸出內容和次數
System.out.println(say + count);
// 內容不對或者次數達到1000次以上輸出
if (!"dog".equals(say) || count >= 1000) {
System.out.println("有人偷了個人狗!");
//break;
}
}
}
}
複製代碼
準備一個修改過的Dog.class,內容以下,單獨保存到目錄D:\learning\Dog.class
函數
// 這是一個修改後編譯的.class文件,單獨存放
public class Dog {
public String say() {
return "cat";
}
}
複製代碼
resource/META-INF/MANIFEST.MF新增內容工具
Agent-Class: cn.jpsite.learning.javaagent01.JpAgent
Can-Retransform-Classes: true
複製代碼
到了這裏,準備工做基本已經完成,執行打包構建,此時執行如下不管哪一條,都不會有結果agentmain
輸出源碼分析
java -javaagent:jpAgent.jar -cp example01-1.0-SNAPSHOT.jar cn.jpsite.learning.Main
java -javaagent:jpAgent.jar -cp example01-1.0-SNAPSHOT.jar cn.jpsite.learning.WhileMain
java -cp example01-1.0-SNAPSHOT.jar cn.jpsite.learning.WhileMain
複製代碼
結果以下,並沒用調用 agentmain
方法,這該怎麼辦呢?
這時候就要用到com.sun.tools.attach來幫助咱們達到虛擬機啓動以後的代理設置,代碼以下:
import com.sun.tools.attach.AttachNotSupportedException;
import com.sun.tools.attach.VirtualMachine;
import com.sun.tools.attach.VirtualMachineDescriptor;
import java.io.IOException;
import java.util.List;
import java.util.Objects;
/**
*
* @author jiangpeng
* @date 2019/12/1 0001
*/
public class AttachThread extends Thread {
/**
* 記錄程序啓動時的 VM 集合
*/
private final List<VirtualMachineDescriptor> listBefore;
/**
要加載的agent.jar
*/
private final String jar;
private AttachThread(String attachJar, List<VirtualMachineDescriptor> vms) {
listBefore = vms;
jar = attachJar;
}
@Override
public void run() {
VirtualMachine vm;
List<VirtualMachineDescriptor> listAfter;
int count = 0;
try {
while (true) {
listAfter = VirtualMachine.list();
vm = hasNewVm(listAfter);
if(vm == null){
System.out.println("沒有新jvm程序,請手動指定java pid");
try{
vm = VirtualMachine.attach("7716");
}catch (AttachNotSupportedException e){
//System.out.println("拒絕訪問 Disconnected from the target VM");
}
}
Thread.sleep(1000);
System.out.println(count++);
if (null != vm || count >= 100) {
break;
}
}
Objects.requireNonNull(vm).loadAgent(jar);
vm.detach();
} catch (Exception e) {
System.out.println("異常:" + e);
}
}
/**
* 判斷是否有新的 JVM 程序運行
*/
private VirtualMachine hasNewVm(List<VirtualMachineDescriptor> listAfter) throws IOException, AttachNotSupportedException {
for (VirtualMachineDescriptor vmd : listAfter) {
if (!listBefore.contains(vmd)) {
// 若是 VM 有增長,,咱們開始監控這個 VM
System.out.println("有新的 vm 程序:"+ vmd.displayName());
return VirtualMachine.attach(vmd);
}
}
return null;
}
public static void main(String[] args) {
new AttachThread("D:/learning/jpAgent.jar", VirtualMachine.list()).start();
}
}
複製代碼
其中while
循環部分每隔1秒獲取一次java進程集合,若是沒有的話就會提示手動指定一個java程序進行attach
, 當循環了100次或者 獲取到了VirtualMachine
,則退出while(true)
去加載指定的agent.jar
。
先執行java -cp example01-1.0-SNAPSHOT.jar cn.jpsite.learning.WhileMain
啓動example01程序,看到以下結果,還記得咱們以前準備了一份Dog.class,裏面的say()內容是cat的嘛,稍後你就會看到神奇的一幕
jps -l
命令查看全部有運行中的java程序端口號
修改AttachThread.java中的VirtualMachine.attach("7716");
代碼爲 VirtualMachine.attach("16304");
16304爲上圖中WhileMain java 進程id,從新啓動AttachThread.java,結果以下
loader className: cn/jpsite/learning/Dog
我是兩個參數的 Java Agent agentmain
能夠看到agentmain已經執行了,並且原Dog.say()方法裏的內容dog也被改變了,成了cat
首先啓動AttachThread.java,而後執行java -cp example01-1.0-SNAPSHOT.jar cn.jpsite.learning.WhileMain
,能夠很快就看到結果被改變了
Can-Set-Native-Method-Prefix
System-Class-Path
Boot-Class-Path
複製代碼
注意幾點。首先,咱們加入到 classpath 的 jar 文件中不該當帶有任何和系統的 instrumentation 有關的系統同名類,否則,一切都陷入不可預料之中
咱們要注意到虛擬機的 ClassLoader 的工做方式,它會記載解析結果。好比,咱們曾經要求讀入某個類 someclass,可是失敗了,ClassLoader 會記得這一點。即便咱們在後面動態地加入了某一個 jar,含有這個類,ClassLoader 依然會認爲咱們沒法解析這個類,與上次出錯的相同的錯誤會被報告。
再次咱們知道在 Java 語言中有一個系統參數「java.class.path」,這個 property 裏面記錄了咱們當前的 classpath,可是,咱們使用這兩個函數,雖然真正地改變了實際的 classpath,卻不會對這個 property 自己產生任何影響。
文章每週持續更新,能夠微信搜索「 十分鐘學編程 」第一時間閱讀和催更,若是這個文章寫得還不錯,以爲有點東西的話 ~求點贊👍 求關注❤️ 求分享❤️
各位的支持和承認,就是我創做的最大動力,咱們下篇文章見!