Java字節(jié)碼2-instrument初體驗(yàn)

聲明:原創(chuàng)文章,轉(zhuǎn)載請注明出處。http://www.itdecent.cn/p/be092b1c72cb

Java字節(jié)碼系列
Java字節(jié)碼1-Agent簡單上手
Java字節(jié)碼2-instrument初體驗(yàn)
Java字節(jié)碼3-使用ByteBuddy實(shí)現(xiàn)一個Java-Agent
Java字節(jié)碼4-使用Java-Agent實(shí)現(xiàn)一個JVM監(jiān)控工具
本系列代碼可見:https://github.com/hawkingfoo/demo-agent

一、概述

在上一節(jié)中Java字節(jié)碼1-Agent簡單上手中,我們了解了通過一個Agent可以在main方法前執(zhí)行。
本節(jié)中,我們將介紹java.lang.instrument,通過instrument可以實(shí)現(xiàn)一個Agent來修改類的字節(jié)碼。下面我們會借助javassist實(shí)現(xiàn)一個簡單的性能檢測工具。目的是檢測函數(shù)的調(diào)用耗時,這里僅僅拋磚引玉,instrument提供的更松耦合的AOP不止于此。

二、實(shí)現(xiàn)一個函數(shù)檢測耗時Agent

1、修改pom.xml

這里與上一節(jié)不同的是,我們需要額外引入javassist包來增強(qiáng)Agent。

<dependency
    <groupId>javassist</groupId
    <artifactId>javassist</artifactId>              
    <version>3.12.1.GA</version>
    <type>jar</type>
 </dependency>

除此之外,我們還需要將此Jar包打包到Agent中,見如下配置:

<plugin> 
  <groupId>org.apache.maven.plugins</groupId>  
  <artifactId>maven-shade-plugin</artifactId>  
  <executions> 
    <execution> 
      <phase>package</phase>  
      <goals> 
        <goal>shade</goal> 
      </goals> 
    </execution> 
  </executions>  
  <configuration> 
    <artifactSet> 
      <includes> 
        <include>javassist:javassist:jar:</include> 
      </includes> 
    </artifactSet> 
  </configuration> 
</plugin>

2、實(shí)現(xiàn)一個Agent

public class MyAgent {

    public static void premain(String agentArgs, Instrumentation inst) {
        System.out.println("this is an perform monitor agent.");
        // 添加 Transformer
        ClassFileTransformer transformer = new PerformMonitorTransformer();
        inst.addTransformer(transformer);
    }
}

與上一節(jié)不同的是,這里添加了一個Transformer。

3、實(shí)現(xiàn)一個Transformer類

public class PerformMonitorTransformer implements ClassFileTransformer {

    private static final Set<String> classNameSet = new HashSet<>();
    static {
        classNameSet.add("com.example.demo.AgentTest");
    }

    @Override
    public byte[] transform(ClassLoader loader,
                            String className,
                            Class<?> classBeingRedefined,
                            ProtectionDomain protectionDomain,
                            byte[] classfileBuffer) throws IllegalClassFormatException {
        try {
            String currentClassName = className.replaceAll("/", ".");
            if (!classNameSet.contains(currentClassName)) { // 僅僅提升Set中含有的類
                return null;
            }
            System.out.println("transform: [" + currentClassName + "]");

            CtClass ctClass = ClassPool.getDefault().get(currentClassName);
            CtBehavior[] methods = ctClass.getDeclaredBehaviors();
            for (CtBehavior method : methods) {
                enhanceMethod(method);
            }
            return ctClass.toBytecode();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private void enhanceMethod(CtBehavior method) throws Exception {
        if (method.isEmpty()) {
            return;
        }
        String methodName = method.getName();
        if (methodName.equalsIgnoreCase("main")) { // 不提升main方法
            return;
        }

        final StringBuilder source = new StringBuilder();
        source.append("{")
                    .append("long start = System.nanoTime();\n") // 前置增強(qiáng): 打入時間戳
                    .append("$_ = $proceed($$);\n") // 保留原有的代碼處理邏輯
                    .append("System.out.print(\"method:[" + methodName + "]\");").append("\n")
                    .append("System.out.println(\" cost:[\" +(System.nanoTime() -start)+ \"ns]\");") // 后置增強(qiáng)
                .append("}");

        ExprEditor editor = new ExprEditor() {
          @Override
          public void edit(MethodCall methodCall) throws CannotCompileException {
              methodCall.replace(source.toString());
          }
        };
        method.instrument(editor);
    }
}

上面的代碼中,我們增強(qiáng)了Set中保存的類的方法,這里是“com.example.demo.AgentTest”,如果是其他類需重新設(shè)置或?qū)⒋颂幣渲玫轿募小?/p>

通過enhanceMethod方法, 重新寫了其方法的字節(jié)碼,即對原有的方法體進(jìn)行了環(huán)繞增強(qiáng)。

三、運(yùn)行

與上一節(jié)中的運(yùn)行方式相同,為了體現(xiàn)本節(jié)的內(nèi)容,我們修改了Test類增加了兩個方法:

public class AgentTest {

    private void fun1() {
        System.out.println("this is fun 1.");
    }

    private void fun2() {
        System.out.println("this is fun 2.");
    }

    public static void main(String[] args) {
        AgentTest test = new AgentTest();
        test.fun1();
        test.fun2();
    }

運(yùn)行結(jié)果如下:

this is an perform monitor agent.
transform: [com.example.demo.AgentTest]
this is fun 1.
method:[fun1] cost:[79024ns]
this is fun 2.
method:[fun2] cost:[41164ns]

Process finished with exit code 0

從上面的運(yùn)行結(jié)果可以看到,我們對調(diào)用的兩個方法進(jìn)行了增強(qiáng),得到了其調(diào)用耗時。

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

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

  • title: Android AOP之字節(jié)碼插樁author: 陶超description: 實(shí)現(xiàn)數(shù)據(jù)收集SDK時...
    陶菜菜閱讀 38,674評論 40 182
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法,內(nèi)部類的語法,繼承相關(guān)的語法,異常的語法,線程的語...
    子非魚_t_閱讀 34,623評論 18 399
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,506評論 19 139
  • 今天我6點(diǎn)半就起床了,因?yàn)榻裉煲ジ魂柶靼?,所以我們得趕早點(diǎn),不然就沒水啦。( ’ - ’ * ) ...
    竹青奶茶閱讀 794評論 2 2
  • 主要形態(tài) 圖形形態(tài): 想了解具體技術(shù)細(xì)節(jié),請閱讀<日本蠟燭圖技術(shù)>,丁圣元 譯
    慢牛策略閱讀 2,736評論 0 1

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