Java字節(jié)碼增強(qiáng)技術(shù)

1.字節(jié)碼

Java剛誕生的時(shí)候有一句非常著名的宣傳口號(hào):“一次編寫讥裤,到處運(yùn)行”。為了實(shí)現(xiàn)這個(gè)目的姻报,Sun公司以及其他虛擬機(jī)提供商發(fā)布了很多可以運(yùn)行在不同平臺(tái)上的jvm虛擬機(jī)己英,虛擬機(jī)的作用就是載入和執(zhí)行一種與平臺(tái)無關(guān)的字節(jié)碼。簡(jiǎn)單來說吴旋,java程序從編寫完成到運(yùn)行损肛,大致會(huì)有兩個(gè)階段,第一個(gè)階段是從.java文件編譯成.class文件荣瑟;第二階段是jvm載入.class文件治拿,進(jìn)行解釋和執(zhí)行。
為什么稱之為字節(jié)碼笆焰,而不叫比特碼呢劫谅?是因?yàn)樽止?jié)碼文件是采用十六進(jìn)制組成,jvm讀取的時(shí)候是以兩個(gè)十六進(jìn)制數(shù)為一組讀取嚷掠,我們知道一個(gè)十六進(jìn)制是4bit捏检,所以兩個(gè)十六進(jìn)制就是一個(gè)字節(jié),jvm便是按字節(jié)讀取不皆。

2.字節(jié)碼增強(qiáng)

我們修改字節(jié)碼有兩個(gè)過程:
1.修改已生成的字節(jié)碼(即.class文件)
2.重新加載更改后的字節(jié)碼贯城,使之生效

2.1 字節(jié)碼修改技術(shù)

字節(jié)碼修改技術(shù)通常包括以下幾類:

  • ASM :一個(gè)輕量級(jí)的字節(jié)碼操作框架,直接涉及到j(luò)vm底層操作和指令霹娄,使用難度較大能犯。
  • CGLIB:屬于動(dòng)態(tài)織入(字節(jié)碼加載之后)技術(shù),基于ASM實(shí)現(xiàn)项棠,性能高悲雳。同時(shí),CGLIB突破了Java動(dòng)態(tài)代理基于接口的限制香追,采用子類繼承的方式合瓢。
  • JAVAssist:屬于動(dòng)態(tài)織入技術(shù),操作簡(jiǎn)單透典,接口強(qiáng)大晴楔,性能較ASM差顿苇。
  • ASPECTJ:靜態(tài)織入(字節(jié)碼加載之前)框架,常用于AOP編程框架税弃。

2.2 使修改后的字節(jié)碼生效

我們這里只關(guān)注通過動(dòng)態(tài)織入框架定義的字節(jié)碼纪岁。可以通過JVMTI(JVM提供的一套對(duì)JVM操作的接口工具则果,通過接口注冊(cè)事件hook幔翰,在jvm事件觸發(fā)時(shí),同時(shí)觸發(fā)我們定義好的鉤子)西壮,將字節(jié)碼文件寫成一個(gè)agent遗增,并在java程序啟動(dòng)之后,通過Attach API(提供的jvm進(jìn)程之間通信的能力)的方式款青,動(dòng)態(tài)加載進(jìn)入虛擬機(jī)做修。

Talk is cheap.Show me the code.

下面我們采用最簡(jiǎn)單的JAVAssit+AttachAPI的方式編寫一套demo。
1.首先抡草,我們先模擬一個(gè)java進(jìn)程:

package demo;

import java.lang.management.ManagementFactory;
import java.util.concurrent.TimeUnit;

public class Application {
    public static void main(String[] args) {
        String name = ManagementFactory.getRuntimeMXBean().getName();
        String s = name.split("@")[0];
        System.out.println("pid:" + s);
        while (true) {
            boolean logined = login("admin", "111");
            System.out.println((logined ? "成功" : "失敗") + "   pid:" + s);
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static boolean login(String user, String passwd) {
        System.out.println("login...");
        if ("admin".equals(user) && "123".equals(passwd)) {
            return true;
        }
        return false;
    }
}

此程序會(huì)一直返回失敗饰及,并且打印出程序的進(jìn)程id。

2.接下來康震,我們用JVMTI接口編寫一個(gè)agent:

import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
public class MyAgent {
    public static void agentmain(String args, Instrumentation inst) throws UnmodifiableClassException {
        inst.addTransformer(new MyTransformer(),true);
        System.out.println("agent加載完畢");
        for (Class aClass : inst.getAllLoadedClasses()) {
            if(aClass.getName().contains("Application")){
                System.out.println(aClass.getName());
                inst.retransformClasses(aClass);
                System.out.println("重新加載class完畢");
            }
        }
    }

}
import javassist.*;
import java.io.IOException;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
import java.util.Objects;

public class MyTransformer implements ClassFileTransformer {

    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
        System.out.println("我進(jìn)到transformer了:"+className);
        if (!className.contains("Application")) {
            return classfileBuffer;
        }
        ClassPool cp = ClassPool.getDefault();
        try {
            CtClass ctClass1 = cp.get("demo.Application");
            CtClass ctClass2 = cp.get(className);
            CtClass ctClass = Objects.isNull(ctClass1) ? ctClass2 : ctClass1;
            CtMethod ctMethod = ctClass.getDeclaredMethod("login");
            ctMethod.setBody("{return true;}");
            System.out.println("修改class完畢");
            return ctClass.toBytecode();
        } catch (NotFoundException e) {
            e.printStackTrace();
        } catch (CannotCompileException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return classfileBuffer;
    }
}

完成之后燎含,我們用編輯器或者jar命令將以上兩個(gè)類打成一個(gè)jar包,命名為javabyte.jar签杈,不管用什么方法瘫镇,最終保持jar包結(jié)構(gòu)如下:


image.png

然后下一步鼎兽,需要解壓jar答姥,修改里面的MANIFEST.MF文件,保持文件內(nèi)容與以下內(nèi)容一致:

Manifest-Version: 1.0
Agent-Class: MyAgent
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Class-Path: javassist-3.24.1-GA.jar
Main-Class: 

3.通過Attach API谚咬,動(dòng)態(tài)加載改過的字節(jié)碼

import com.sun.tools.attach.AgentInitializationException;
import com.sun.tools.attach.AgentLoadException;
import com.sun.tools.attach.AttachNotSupportedException;
import com.sun.tools.attach.VirtualMachine;
import java.io.IOException;

public class Demo {
    public static void main(String[] args) {
        try {
            VirtualMachine virtualMachine = VirtualMachine.attach("30421");
            virtualMachine.loadAgent("javabyte.jar");
        } catch (AttachNotSupportedException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (AgentLoadException e) {
            e.printStackTrace();
        } catch (AgentInitializationException e) {
            e.printStackTrace();
        }
    }
}

注意:以上代碼中的路徑一定要跟自己工程路徑一致鹦付,比如:demo.Application,demo是我的包名择卦;javabyte.jar這個(gè)可以直接替換為jar的絕對(duì)路徑敲长。
操作步驟:
1.運(yùn)行1程序,會(huì)打印出進(jìn)程id
2.打包2程序
3.根據(jù)pid修改3程序秉继,運(yùn)行
結(jié)果如下:
運(yùn)行1程序:


image.png

運(yùn)行3程序:


image.png

如果程序運(yùn)行報(bào)錯(cuò)和tools有關(guān)祈噪,直接在項(xiàng)目里面添加依賴即可:
<dependency>
        <groupId>com.sun</groupId>
        <artifactId>tools</artifactId>
        <version>1.8.0</version>
        <scope>system</scope>
        <systemPath>/Library/Java/JavaVirtualMachines/jdk1.8.0_281.jdk/Contents/Home/lib/tools.jar</systemPath>
    </dependency>

遇到問題也不用著急,可以打印各種日志來跟蹤你的程序運(yùn)行尚辑,并找到問題辑鲤。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市杠茬,隨后出現(xiàn)的幾起案子月褥,更是在濱河造成了極大的恐慌弛随,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宁赤,死亡現(xiàn)場(chǎng)離奇詭異舀透,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)决左,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門愕够,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人佛猛,你說我怎么就攤上這事链烈。” “怎么了挚躯?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵强衡,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我码荔,道長(zhǎng)漩勤,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任缩搅,我火速辦了婚禮越败,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘硼瓣。我一直安慰自己究飞,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布堂鲤。 她就那樣靜靜地躺著亿傅,像睡著了一般。 火紅的嫁衣襯著肌膚如雪瘟栖。 梳的紋絲不亂的頭發(fā)上葵擎,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音半哟,去河邊找鬼酬滤。 笑死,一個(gè)胖子當(dāng)著我的面吹牛寓涨,可吹牛的內(nèi)容都是我干的盯串。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼戒良,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼体捏!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤译打,失蹤者是張志新(化名)和其女友劉穎耗拓,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體奏司,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡乔询,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了韵洋。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片竿刁。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖搪缨,靈堂內(nèi)的尸體忽然破棺而出食拜,到底是詐尸還是另有隱情,我是刑警寧澤副编,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布负甸,位于F島的核電站,受9級(jí)特大地震影響痹届,放射性物質(zhì)發(fā)生泄漏呻待。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一队腐、第九天 我趴在偏房一處隱蔽的房頂上張望蚕捉。 院中可真熱鬧,春花似錦柴淘、人聲如沸迫淹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)敛熬。三九已至,卻和暖如春梗脾,著一層夾襖步出監(jiān)牢的瞬間荸型,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來泰國(guó)打工炸茧, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人稿静。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓梭冠,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親改备。 傳聞我的和親對(duì)象是個(gè)殘疾皇子控漠,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容