Java探針(javaagent)

JDK1.5開(kāi)始引入了Agent機(jī)制(即啟動(dòng)java程序時(shí)添加“-javaagent”參數(shù),Java Agent機(jī)制允許用戶在JVM加載class文件的時(shí)候先加載自己編寫的Agent文件,通過(guò)修改JVM傳入的字節(jié)碼來(lái)實(shí)現(xiàn)注入自定義的代碼。采用這種方式時(shí),必須在容器啟動(dòng)時(shí)添加jvm參數(shù),所以需要重啟Web容器。
JDK1.6新增了attach方式,可以對(duì)運(yùn)行中的java進(jìn)程附加agent,提供了動(dòng)態(tài)修改運(yùn)行中已經(jīng)被加載的類的途徑。一般通過(guò)VirtualMachine的attach(pid)方法獲得VirtualMachine實(shí)例,隨后可調(diào)用loadagent方法將JavaAgent的jar包加載到目標(biāo)JVM中。

什么是java agent?

在JVM中運(yùn)行中,類是通過(guò)classLoader加載.class文件進(jìn)行生成的。在類加載加載.class文件生成對(duì)應(yīng)的類對(duì)象之前時(shí),我們可以通過(guò)修改.class文件內(nèi)容(就是字節(jié)碼修改技術(shù)),達(dá)到修改類的目的。JDK提供了對(duì)字節(jié)碼進(jìn)行操作的一系列api,而使用這些api開(kāi)發(fā)出的程序就可以稱之為java agent。

java agent能做什么?

不修改目標(biāo)應(yīng)用達(dá)到代碼增強(qiáng)的目的,就好像spring的aop一樣,但是java agent是直接修改字節(jié)碼,而不是通過(guò)創(chuàng)建代理類。例如skywalking就是使用java agent技術(shù),為目標(biāo)應(yīng)用代碼植入監(jiān)控代碼,監(jiān)控代碼進(jìn)行數(shù)據(jù)統(tǒng)計(jì)上報(bào)的。這種方式實(shí)現(xiàn)了解耦,通用的功能。

javaagent作用

  • 可以在加載java文件之前進(jìn)行攔截,修改字節(jié)碼。

  • 可以在運(yùn)行期間修改已經(jīng)加載的類的字節(jié)碼。
    這種用法有很多的限制。

  • javaagent結(jié)合javassist功能更強(qiáng)大:可以創(chuàng)建類、方法、變量等。
    這實(shí)際上提供了一種虛擬機(jī)級(jí)別的 AOP 實(shí)現(xiàn)方式。通過(guò)以上方法就能實(shí)現(xiàn)對(duì)一些框架或是技術(shù)的采集點(diǎn)進(jìn)行字節(jié)碼修改,完成這些功能:對(duì)應(yīng)用進(jìn)行監(jiān)控,對(duì)執(zhí)行指定方法或是接口時(shí)額外添加操作(打印日志、打印方法執(zhí)行時(shí)間、采集方法的入?yún)⒑徒Y(jié)果等)。

    很多APM監(jiān)控系統(tǒng)就是基于此實(shí)現(xiàn)的,例如:Arthas、SkyWalking

javaagent使用方式

  • 方式1:在一個(gè)普通 Java 程序(帶有 main 函數(shù)的 Java 類)運(yùn)行時(shí),通過(guò) -javaagent 參數(shù)指定一個(gè)特定的 jar 文件(包含 Instrumentation 代理)來(lái)啟動(dòng) Instrumentation 的代理程序。
    -javaagent 這個(gè)參數(shù)的個(gè)數(shù)是不限的,如果指定了多個(gè),則會(huì)按指定的先后執(zhí)行,執(zhí)行完各個(gè) agent 后,才會(huì)執(zhí)行主程序的 main 方法。例如:
java -javaagent:D:\workspace\javaagent.jar=hello1 
-javaagent:D:\workspace\javaagent.jar=hello2 -jar D:\workspace\myTest.jar

注: hello1是參數(shù)

  • 方式2:在一個(gè)普通 Java 程序(帶有 main 函數(shù)的 Java 類)運(yùn)行時(shí),通過(guò) Java Tool API 中的 attach 方式指定進(jìn)程id和特定jar包地址,啟動(dòng) Instrumentation 的代理程序。

javaagent其他功能

  • 獲取所有已經(jīng)被加載過(guò)的類
  • 獲取所有已經(jīng)被初始化過(guò)了的類(執(zhí)行過(guò)了clinit方法,是上面的一個(gè)子集)
  • 獲取某個(gè)對(duì)象的大小
  • 將某個(gè)jar加入到bootstrapclasspath里作為高優(yōu)先級(jí)被bootstrapClassloader加載
  • 將某個(gè)jar加入到classpath里供AppClassload去加載
  • 設(shè)置某些native方法的前綴,主要在查找native方法的時(shí)候做規(guī)則匹配

靜態(tài)agent與動(dòng)態(tài)agent

Agent分為如下兩種:

  • 靜態(tài)Instrument:在main加載之前運(yùn)行的Agent
  • 動(dòng)態(tài)Instrument:在main運(yùn)行之后運(yùn)行的Agent(JDK1.6以后提供)。

靜態(tài)Instrument(啟動(dòng)時(shí))加載Instrument過(guò)程

  • 創(chuàng)建并初始化 JPLISAgent;
  • 監(jiān)聽(tīng)VMInit事件,在JVM初始化完成之后做下面的事情:
  • 創(chuàng)建InstrumentationImpl對(duì)象;
  • 監(jiān)聽(tīng)ClassFileLoadHook事件;
  • 調(diào)用InstrumentationImpl的loadClassAndCallPremain方法,在這個(gè)方法里會(huì)去調(diào)用javaagent中MANIFEST.MF里指定的Premain-Class類的premain方法 ;
  • 解析javaagent中MANIFEST.MF文件的參數(shù),并根據(jù)這些參數(shù)來(lái)設(shè)置JPLISAgent里的一些內(nèi)容。

動(dòng)態(tài)Instrument運(yùn)行時(shí)加載Instrument過(guò)程

通過(guò)JVM的attach機(jī)制來(lái)請(qǐng)求目標(biāo)JVM加載對(duì)應(yīng)的agent,過(guò)程大致如下:

  • 創(chuàng)建并初始化JPLISAgent;
  • 解析 javaagent 里 MANIFEST.MF 里的參數(shù);
  • 創(chuàng)建 InstrumentationImpl 對(duì)象;
  • 監(jiān)聽(tīng) ClassFileLoadHook 事件;
  • 調(diào)用 InstrumentationImpl 的loadClassAndCallAgentmain方法,在這個(gè)方法里會(huì)去調(diào)用javaagent里 MANIFEST.MF 里指定的Agent-Class類的agentmain方法。

示例1: 簡(jiǎn)單例子

agent程序

1.提供premain方法

package com.example.a;
 
import java.lang.instrument.Instrumentation;
 
public class DemoAgent {
    /**
     * 該方法在main方法之前運(yùn)行,與main方法運(yùn)行在同一個(gè)JVM中
     */
    public static void premain(String arg, Instrumentation instrumentation) {
        System.out.println("agent的premain(String arg, Instrumentation instrumentation)方法");
    }
 
    /**
     * 若不存在 premain(String agentArgs, Instrumentation inst),
     * 則會(huì)執(zhí)行 premain(String agentArgs)
     */
    public static void premain(String arg) {
        System.out.println("agent的premain(String arg)方法");
    }
}

2.提供META-INF/MANIFEST.MF

在src/main/java的同級(jí)目錄下新建META-INF文件夾,在里邊新建MANIFEST.MF文件(注意最后一行必須是空行)

Manifest-Version: 1.0
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Premain-Class: com.example.a.DemoAgent

  • Premain-Class :包含 premain 方法的類(類的全路徑名)
  • Agent-Class :包含 agentmain 方法的類(類的全路徑名)
  • Boot-Class-Path :設(shè)置引導(dǎo)類加載器搜索的路徑列表。查找類的特定于平臺(tái)的機(jī)制失敗后,引導(dǎo)類加載器會(huì)搜索這些路徑。按列出的順序搜索路徑。列表中的路徑由一個(gè)或多個(gè)空格分開(kāi)。路徑使用分層 URI 的路徑組件語(yǔ)法。如果該路徑以斜杠字符(“/”)開(kāi)頭,則為絕對(duì)路徑,否則為相對(duì)路徑。相對(duì)路徑根據(jù)代理 JAR 文件的絕對(duì)路徑解析。忽略格式不正確的路徑和不存在的路徑。如果代理是在 VM 啟動(dòng)之后某一時(shí)刻啟動(dòng)的,則忽略不表示 JAR 文件的路徑。(可選)
  • Can-Redefine-Classes :true表示能重定義此代理所需的類,默認(rèn)值為 false(可選)
  • Can-Retransform-Classes :true 表示能重轉(zhuǎn)換此代理所需的類,默認(rèn)值為 false (可選)
  • Can-Set-Native-Method-Prefix: true表示能設(shè)置此代理所需的本機(jī)方法前綴,默認(rèn)值為 false(可選)

3.將其打包為jar包

步驟1:打包的配置入口

File=> Project Structure=> Project Settings=> Artifacts=> + => JAR=> From modules with dependencies..


image.png

步驟2:打包的配置


image.png

步驟3:打包

Build=> Build Artifacts...=> Build

此時(shí)會(huì)生成out目錄,并生成jar包:


image.png

也可使用maven配置META-INF/MANIFEST.MF

使用maven,打包方便,而且不用手寫META-INF/MANIFEST.MF,用插件即可:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.5.1</version>
            <!-- 指定maven編譯的jdk版本。若不指定,maven3默認(rèn)用jdk 1.5 maven2默認(rèn)用jdk1.3 -->
            <configuration>
                <source>8</source>
                <target>8</target>
            </configuration>
        </plugin>
 
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-jar-plugin</artifactId>
            <version>3.2.0</version>
            <configuration>
                <archive>
                    <!--自動(dòng)添加META-INF/MANIFEST.MF -->
                    <manifest>
                        <addClasspath>true</addClasspath>
                    </manifest>
                    <manifestEntries>
                        <Menifest-Version>1.0</Menifest-Version>
                        <Premain-Class>com.example.a.DemoAgent</Premain-Class>
                        <Can-Redefine-Classes>true</Can-Redefine-Classes>
                        <Can-Retransform-Classes>true</Can-Retransform-Classes>
                    </manifestEntries>
                </archive>
            </configuration>
        </plugin>
    </plugins>
</build>

maven的項(xiàng)目結(jié)構(gòu)為:


image.png

應(yīng)用程序
項(xiàng)目結(jié)構(gòu)


image.png

1.提供main程序

package com.example.a;
public class Demo {
    public static void main(String[] args) {
        System.out.println("應(yīng)用的main方法");
    }
}

測(cè)試
java -javaagent:D:\tmp\demo_javaagent.jar -jar demo_java.jar

結(jié)果:


image.png

示例2:統(tǒng)計(jì)方法的執(zhí)行時(shí)間

需求:寫一個(gè)agent,統(tǒng)計(jì)應(yīng)用的某個(gè)方法的執(zhí)行時(shí)間。(本處要統(tǒng)計(jì)的方法是:TimeTest#test方法)

agent程序

agent代碼

package com.example.a;
 
import java.lang.instrument.Instrumentation;
 
public class DemoAgent {
    /**
     * 該方法在main方法之前運(yùn)行,與main方法運(yùn)行在同一個(gè)JVM中
     */
    public static void premain(String arg, Instrumentation instrumentation) {
        System.out.println("agent的premain(String arg, Instrumentation instrumentation)方法");
 
        instrumentation.addTransformer(new MyTransformer());
    }
 
    /**
     * 若不存在 premain(String agentArgs, Instrumentation inst),
     * 則會(huì)執(zhí)行 premain(String agentArgs)
     */
    public static void premain(String arg) {
        System.out.println("agent的premain(String arg)方法");
    }
}

Transformer代碼

package com.example.a;
 
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
 
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
 
public class MyTransformer implements ClassFileTransformer {
    private final String injectedClass = "com.example.a.TimeTest";
    private final String injectedMethod = "test";
 
    @Override
    public byte[] transform(ClassLoader loader,
                            String className,
                            Class<?> classBeingRedefined,
                            ProtectionDomain protectionDomain,
                            byte[] classfileBuffer) throws IllegalClassFormatException {
 
        String realClassName = className.replace("/", ".");
 
        if (realClassName.equals(injectedClass)) {
            CtClass ctClass;
            try {
                // 使用全稱,取得字節(jié)碼類<使用javassist>
                ClassPool classPool = ClassPool.getDefault();
                ctClass = classPool.get(realClassName);
 
                // 得到方法實(shí)例
                CtMethod ctMethod = ctClass.getDeclaredMethod(injectedMethod);
                // 添加變量
                ctMethod.addLocalVariable("time", CtClass.longType);
                ctMethod.insertBefore("System.out.println(\"------------ Before --------\");");
                ctMethod.insertBefore("time = System.currentTimeMillis();");
 
                ctMethod.insertAfter("System.out.println(\"Elapsed Time(ms): \" + (System.currentTimeMillis() - time));");
                ctMethod.insertAfter("System.out.println(\"------------- After --------\");");
 
                return ctClass.toBytecode();
            } catch (Throwable e) { //這里要用Throwable,不要用Exception
                System.out.println(e.getMessage());
                e.printStackTrace();
            }
        }
 
        // 返回原類字節(jié)碼
        return classfileBuffer;
    }
}

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
 
  <groupId>com.example</groupId>
  <artifactId>demo_javaagent</artifactId>
  <version>1.0-SNAPSHOT</version>
 
  <dependencies>
    <dependency>
      <groupId>org.javassist</groupId>
      <artifactId>javassist</artifactId>
      <version>3.28.0-GA</version>
    </dependency>
  </dependencies>
 
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.5.1</version>
        <!-- 指定maven編譯的jdk版本。若不指定,maven3默認(rèn)用jdk 1.5 maven2默認(rèn)用jdk1.3 -->
        <configuration>
          <source>8</source>
          <target>8</target>
        </configuration>
      </plugin>
 
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-jar-plugin</artifactId>
        <version>3.2.0</version>
        <configuration>
          <archive>
            <!--自動(dòng)添加META-INF/MANIFEST.MF -->
            <manifest>
              <addClasspath>true</addClasspath>
            </manifest>
            <manifestEntries>
              <Menifest-Version>1.0</Menifest-Version>
              <Premain-Class>com.example.a.DemoAgent</Premain-Class>
              <Can-Redefine-Classes>true</Can-Redefine-Classes>
              <Can-Retransform-Classes>true</Can-Retransform-Classes>
            </manifestEntries>
          </archive>
        </configuration>
      </plugin>
    </plugins>
  </build>
 
</project>

應(yīng)用程序
main類

package com.example.a;
 
public class Demo {
    public static void main(String[] args) {
        System.out.println("應(yīng)用的main方法");
        new TimeTest().test();
    }
}

測(cè)試類

package com.example.a;
 
public class TimeTest {
    public void test() {
        System.out.println("開(kāi)始執(zhí)行TimeTest#test");
        System.out.println("sleep開(kāi)始");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("sleep結(jié)束");
    }
}

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
 
    <groupId>com.example</groupId>
    <artifactId>demo_maven</artifactId>
    <version>1.0-SNAPSHOT</version>
 
    <dependencies>
        <dependency>
            <groupId>org.javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>3.28.0-GA</version>
        </dependency>
    </dependencies>
 
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.5.1</version>
                <!-- 指定maven編譯的jdk版本。若不指定,maven3默認(rèn)用jdk 1.5 maven2默認(rèn)用jdk1.3 -->
                <configuration>
                    <source>8</source>
                    <target>8</target>
                </configuration>
            </plugin>
 
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <archive>
                        <manifest>
                            <mainClass>com.example.a.Demo</mainClass>
                        </manifest>
                    </archive>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                    <appendAssemblyId>false</appendAssemblyId>
 
                </configuration>
 
                <executions>
                    <execution>
                        <id>make-assembly</id>
                        <phase>package</phase>
                        <goals>
                            <goal>assembly</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

測(cè)試
java -javaagent:D:\tmp\demo_javaagent-1.0-SNAPSHOT.jar -jar demo_maven-1.0-SNAPSHOT.jar

結(jié)果:


image.png

Agentmain(attach)

在 Java SE 6 的 Instrumentation 當(dāng)中,提供了一個(gè)新的代理操作方法:agentmain,可以在 main 函數(shù)開(kāi)始運(yùn)行之后再運(yùn)行。
跟premain函數(shù)一樣, 開(kāi)發(fā)者可以編寫一個(gè)含有agentmain函數(shù)的 Java 類:

//采用attach機(jī)制,被代理的目標(biāo)程序VM有可能很早之前已經(jīng)啟動(dòng),當(dāng)然其所有類已經(jīng)被加載完成,
//這個(gè)時(shí)候需要借助Instrumentation#retransformClasses(Class<?>... classes)
//讓對(duì)應(yīng)的類可以重新轉(zhuǎn)換,從而激活重新轉(zhuǎn)換的類執(zhí)行ClassFileTransformer列表中的回調(diào)
public static void agentmain (String agentArgs, Instrumentation inst)

public static void agentmain (String agentArgs)

agentMain 主要用于對(duì)java程序的監(jiān)控,調(diào)用java進(jìn)程,將自己編寫的agentMain 注入目標(biāo)完成對(duì)程序的監(jiān)控,修改。

創(chuàng)建agentmain

public class TestMainAgent {
    public static void agentmain(String agentArgs, Instrumentation instrumentation) {
        System.out.println("loadagent after main run.args=" + agentArgs);

        Class<?>[] classes = instrumentation.getAllLoadedClasses();

        for (Class<?> cls : classes)
        {
            System.out.println(cls.getName());
        }

        System.out.println("agent run completely.");
    }

    static class DefineTransformer implements ClassFileTransformer {

        @Override
        public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
            System.out.println("premain load Class:" + className);
            return classfileBuffer;
        }
    }
}

添加maven插件打包

 <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.1.0</version>
                <configuration>
                    <archive>
                        <!--自動(dòng)添加META-INF/MANIFEST.MF -->
                        <manifest>
                            <addClasspath>true</addClasspath>
                        </manifest>
                        <manifestEntries>
                            <Agent-Class>com.tttiger.TestMainAgent</Agent-Class>
                            <Can-Redefine-Classes>true</Can-Redefine-Classes>
                            <Can-Retransform-Classes>true</Can-Retransform-Classes>
                        </manifestEntries>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>

測(cè)試agentMain插樁到其他類
另外啟用了一個(gè)jvm進(jìn)程,找到需要attach的jvm進(jìn)程,讓它加載agentMain,那么agentMain就會(huì)被加載到對(duì)方j(luò)vm執(zhí)行。arthas就是使用這種方式attach進(jìn)jvm進(jìn)程,開(kāi)啟一個(gè)socket然后進(jìn)行目標(biāo)jvm的監(jiān)控。

    public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException, InterruptedException {
        //獲取當(dāng)前系統(tǒng)中所有 運(yùn)行中的 虛擬機(jī)
        System.out.println("running JVM start ");
        List<VirtualMachineDescriptor> list = VirtualMachine.list();
        for (VirtualMachineDescriptor vmd : list) {
            //如果虛擬機(jī)的名稱為 xxx 則 該虛擬機(jī)為目標(biāo)虛擬機(jī),獲取該虛擬機(jī)的 pid
            //然后加載 agent.jar 發(fā)送給該虛擬機(jī)
            System.out.println(vmd.displayName());
            if (vmd.displayName().endsWith("com.tttiger.TestJVM")) {
                System.out.println(vmd.id());
                VirtualMachine virtualMachine = VirtualMachine.attach(vmd.id());
                virtualMachine.loadAgent("e:/test-agentMain-1.0-SNAPSHOT.jar");
                virtualMachine.detach();
                System.out.println("attach");
            }
        }
        Thread.sleep(10000L);
    }

VirtualMachine 字面意義表示一個(gè)Java 虛擬機(jī),也就是程序需要監(jiān)控的目標(biāo)虛擬機(jī),提供了獲取系統(tǒng)信息(比如獲取內(nèi)存dump、線程dump,類信息統(tǒng)計(jì)(比如已加載的類以及實(shí)例個(gè)數(shù)等), loadAgent,Attach 和 Detach (Attach 動(dòng)作的相反行為,從 JVM 上面解除一個(gè)代理)等方法,可以實(shí)現(xiàn)的功能可以說(shuō)非常之強(qiáng)大 。該類允許我們通過(guò)給attach方法傳入一個(gè)jvm的pid(進(jìn)程id),遠(yuǎn)程連接到j(luò)vm上 。

代理類注入操作只是它眾多功能中的一個(gè),通過(guò)loadAgent方法向jvm注冊(cè)一個(gè)代理程序agent,在該agent的代理程序中會(huì)得到一個(gè)Instrumentation實(shí)例,該實(shí)例可以 在class加載前改變class的字節(jié)碼,也可以在class加載后重新加載。在調(diào)用Instrumentation實(shí)例的方法時(shí),這些方法會(huì)使用ClassFileTransformer接口中提供的方法進(jìn)行處理。

VirtualMachineDescriptor 則是一個(gè)描述虛擬機(jī)的容器類,配合 VirtualMachine 類完成各種功能

通過(guò)VirtualMachine類的attach(pid)方法,便可以attach到一個(gè)運(yùn)行中的java進(jìn)程上,之后便可以通過(guò)loadAgent(agentJarPath)來(lái)將agent的jar包注入到對(duì)應(yīng)的進(jìn)程,然后對(duì)應(yīng)的進(jìn)程會(huì)調(diào)用agentmain方法。


image.png

Instrumentation的局限性
大多數(shù)情況下,我們使用Instrumentation都是使用其字節(jié)碼插樁的功能,或者籠統(tǒng)說(shuō)就是類重定義(Class Redefine)的功能,但是有以下的局限性:

  1. premain和agentmain兩種方式修改字節(jié)碼的時(shí)機(jī)都是類文件加載之后,也就是說(shuō)必須要帶有Class類型的參數(shù),不能通過(guò)字節(jié)碼文件和自定義的類名重新定義一個(gè)本來(lái)不存在的類。
  2. 類的字節(jié)碼修改稱為類轉(zhuǎn)換(Class Transform),類轉(zhuǎn)換其實(shí)最終都回歸到類重定義Instrumentation#redefineClasses()方法,此方法有以下限制:
  • 2.1 新類和老類的父類必須相同;
  • 2.2 新類和老類實(shí)現(xiàn)的接口數(shù)也要相同,并且是相同的接口;
  • 2.3 新類和老類訪問(wèn)符必須一致。 新類和老類字段數(shù)和字段名要一致;
  • 2.4 新類和老類新增或刪除的方法必須是private static/final修飾的;
  • 2.5 可以修改方法體。

除了上面的方式,如果想要重新定義一個(gè)類,可以考慮基于類加載器隔離的方式:創(chuàng)建一個(gè)新的自定義類加載器去通過(guò)新的字節(jié)碼去定義一個(gè)全新的類,不過(guò)也存在只能通過(guò)反射調(diào)用該全新類的局限性。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容