神器之ByteBuddy,字節碼注入分析代碼執行性能

前文

一路上看見晦澀難懂的ASM,小巧可人的Javassist。直到遇見了ByteBuddy才知世上竟有如此的冷豔簡潔。

JavaAgent

從Jdk1.5開始Java開始支持Java Agent特性,可以通過premain方法,在Class字節碼加載進虛擬機之前對底層的字節碼進行修改。從而達到可以自定義特性的功能。給Aop的實現提供了一種更加簡潔的方式。

ByteBuddy

字節碼修改工具貌似從Java的誕生就一直存在,一開始的ASM,後來可以通過人類可以理解的方式修改字節碼的Javassist,到現在的ByteBuddy,甚至不需要了解Class文件的構成就可以修改字節碼,實現自己需求,簡直神器。對於Class文件結構構成不在此贅述,有興趣可以去之前摹寫的一個加載Class文件的工具瞭解一下GOM,ByteBuddy官方地址。

通過ByteBuddy修改字節碼,計算方法耗時

直接上代碼

入口文件

package com.langel.anal.agent;

import com.langel.anal.agent.constant.Const;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.matcher.ElementMatchers;
import net.bytebuddy.utility.JavaModule;

import java.lang.instrument.Instrumentation;

/**
 * @author [email protected]
 * @date 2019-07-04
 */
public class AnalAgent {

    public static void premain(String agentOps, Instrumentation inst) {
        System.out.println(Const.LOG_PREFIX + "This is an perform monitor agent.");
        new Param(agentOps);
        Param.print();
        AgentBuilder.Transformer transformer = new AgentBuilder.Transformer() {
            @Override
            public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder,
                                                    TypeDescription typeDescription,
                                                    ClassLoader classLoader) {
                return builder
                        .method(ElementMatchers.<MethodDescription>any()) // 攔截任意方法
                        .intercept(MethodDelegation.to(TimeInterceptor.class)); // 委託
            }
        };

        AgentBuilder.Listener listener = new AgentBuilder.Listener() {
            @Override
            public void onTransformation(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module, DynamicType dynamicType) {
            }

            @Override
            public void onIgnored(TypeDescription typeDescription, ClassLoader classLoader, JavaModule module) {
            }

            @Override
            public void onError(String typeName, ClassLoader classLoader, JavaModule module, Throwable throwable) {
            }

            @Override
            public void onComplete(String typeName, ClassLoader classLoader, JavaModule module) {
            }
        };

        String packagePrefix = Param.get("prefix") == null ? "com" : Param.get("prefix");
        new AgentBuilder
                .Default()
                .type(ElementMatchers.nameStartsWith(packagePrefix)
                        .and(ElementMatchers.not(ElementMatchers.isAnnotation()))
                        .and(ElementMatchers.not(ElementMatchers.isInterface()))
                        .and(ElementMatchers.not(ElementMatchers.isEnum()))) // 指定需要攔截的類
                .transform(transformer)
                .with(listener)
                .installOn(inst);
    }

}

攔截器文件

package com.langel.anal.agent;

import net.bytebuddy.implementation.bind.annotation.Origin;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.implementation.bind.annotation.SuperCall;

import java.lang.reflect.Method;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.Callable;

/**
 * @author [email protected]
 * @date 2019-07-05
 */
public class TimeInterceptor {

    @RuntimeType
    public static Object intercept(@Origin Method method,
                                   @SuperCall Callable<?> callable) throws Exception {
        String methodName = method.getName();
        if (methodName.startsWith("equals")
                || methodName.startsWith("hashCode")
                || methodName.startsWith("toString")) {
            return callable.call();
        }
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");


        long start = System.currentTimeMillis();
        try {
            // 原有函數執行
            return callable.call();
        } finally {
            Long elpse = System.currentTimeMillis() - start;
            LogCachePool.putInstant(method.getDeclaringClass().getName(), methodName, elpse);
        }
    }
}

然後在JVMOptions加入下面配置

-javaagent:/Users/rick/Workspaces/sourcecode/anal/target/anal-agent.jar=file=log.txt,prefix=com.langel.kmp

這只是ByteBuddy 的API的一個簡單使用。更多特性值得去探索

測試

測試代碼

package com.langel.kmp;

import com.langel.util.PrintUtils;

/**
 * @Author: rick
 * @Date: 2018/4/15 上午1:55
 * @Description:
 */
public class KMP {
    private static final String searchStr = "bacbababadababacambabacaddababacasdsd";
    private static final String ptrStr = "ababaca";//-1,-1,0,1,2,-1,0

    private static int[] calNext(String pStr) {
        int[] next = new int[pStr.length()];
        next[0] = -1;
        int k = -1;
        for (int i = 1; i < next.length; i++) {
            while (k > -1 && pStr.charAt(i) != pStr.charAt(k + 1)) {
                k = next[k];
            }
            if (pStr.charAt(k + 1) == pStr.charAt(i)) {
                next[i] = k = k + 1;
            }
        }
        return next;
    }

    private static int search(String sStr, String pStr) {
        int[] next = calNext(pStr);
        PrintUtils.println(next);
        int k = -1;
        for (int i = 0; i < sStr.length(); i++) {
            while (k > -1 && sStr.charAt(i) != pStr.charAt(k + 1)) {
                k = next[k];
                System.out.println(k);
            }
            if (pStr.charAt(k + 1) == sStr.charAt(i)) {
                k = k + 1;
                System.out.println("k + 1 : " + k);
            }
            if (k == pStr.length() - 1) {
                return i - pStr.length() + 1;
            }
            System.out.println();
        }
        return -1;
    }

    public static void main(String[] args) throws InterruptedException {
        for (int idx = 0; idx < 1000; idx++) {
            Thread.sleep(1000);
            System.out.println(search(searchStr, ptrStr));
        }

    }
}

日誌

Anal monitor : time - 2019-07-11T01:14:30.785
com.langel.kmp.KMP
    -- search : 27ms
    -- calNext : 0ms

個人博客導流(L-Angel)http://l-angel.fun

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章