一文搞定Spring容器初始化方法棧調用棧

背景

大家都知道Spring容器啓動時,主要通過調用org.springframework.context.support.AbstractApplicationContext#refresh 方法,但是在調用過程中,有很多的調用鏈,分析起來很麻煩,出於這個目的,我打算寫個小插件,Spring容器初始化的調用堆棧。

思路

有了這個背景後,就要思考怎麼能打印出來調用的堆棧信息,接觸的有兩種方式

  1. 通過java.lang.Thread#getAllStackTraces 方法獲取調用堆棧
  2. 改寫字節碼

首頁看第一種,第一種方式,可以在當前的位置,打印出到達這個方法的堆棧信息,貌似可以滿足我們的需求。但是這個只能打印我需要知道的地方的調用堆棧,也就是說,我要前提很明確會調用到哪裏,這樣顯然不能滿足我們的需求。

再看第二種方式,通過改寫字節碼的方式,通過改寫字節碼,可以在方法進入時,打印方法名稱,這樣可以知道調用了哪些方法,這樣可以滿足我們的需求。目前改寫字節碼的方式主要通過javaagent或asm的方式,我們嘗試通過javaagent的方式來完成我們的需求。

具體方法

確定了思路後,開始寫一個javaagent,關於javaagent的具體介紹不是本文重點,不做介紹了,可以參考相關文章

主要有3個文件,JavaAgent.java、DefineTransformer.java、PrintMethod.java,具體代碼如下:

// JavaAgent.java
public class JavaAgent {

    public static void premain(String agentArgs, Instrumentation inst) {
        inst.addTransformer(new DefineTransformer(), true);
    }

}
// DefineTransformer.java
public class DefineTransformer implements ClassFileTransformer {
    final static String enterPattern = "com.ethan.javaagent.PrintMethod.enterMethod(\"%s\");";
    final static String leavePattern = "com.ethan.javaagent.PrintMethod.leaveMethod(\"%s\");";
    // 被處理的方法列表
    final static List<String> wroteMethod = new ArrayList<>();
    static List<String> includePackages;

    DefineTransformer(String[] args) {
        includePackages = Arrays.asList(args);

        System.out.println(includePackages);
    }

    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
        className = className.replace("/", ".");
        // 此處可以調整,此處意思是修改org.springframework包下的字節碼,不修改org.springframework.util包下的字節碼
        if (className.startsWith("org.springframework") && !className.startsWith("org.springframework.util")) {
            CtClass ctclass = null;
            try {
                ctclass = ClassPool.getDefault().get(className);// 使用全稱,用於取得字節碼類<使用javassist>

                for (CtMethod ctMethod : ctclass.getMethods()) {
                    if (Modifier.isAbstract(ctMethod.getModifiers())) {
                        continue;
                    }
                    if (Modifier.isNative(ctMethod.getModifiers())) {
                        continue;
                    }
                    if (!ctMethod.getLongName().startsWith(className)) {
                        continue;
                    }
                    if (wroteMethod.contains(ctMethod.getLongName())) {
                        continue;
                    }
                    wroteMethod.add(ctMethod.getLongName());
                    ctMethod.insertBefore(String.format(enterPattern, ctMethod.getLongName()));
                    ctMethod.insertAfter(String.format(leavePattern, ctMethod.getLongName()), true);
                }
                return ctclass.toBytecode();
            } catch (Throwable e) {
                System.out.println("e.getMessage()" +e.getMessage());
                e.printStackTrace();
            }
        }
        return classfileBuffer;
    }

}
//PrintMethod.java
public class PrintMethod {
    private static volatile int stack = 0;

    public static void enterMethod(String methodName) {
        for (int i = 0; i < stack; i++) {
            System.out.print("  ");
        }
        System.out.println(methodName);
        stack ++;
    }

    public static void leaveMethod(String methodName) {
        stack --;
    }

}

以上三個文件是javaagent文件,算是完成了javaagent的代碼部分,下面打包部分,也在resources下添加META-INF/MANIFEST.MF文件,文件內容如下:

Manifest-Version: 1.0
Premain-Class: com.ethan.javaagent.JavaAgent
Agent-Class: com.ethan.javaagent.JavaAgent
Can-Redefine-Classes: true
Can-Retransform-Classes: true

打包agent.jar

詳細代碼參見:github

驗證

編寫一個簡單的main方法,驗證結果:

public class ClassPathBootstrap {

    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:classpath-user.xml");
   }
}

使用IDEA運行時,在VM Options中,輸入-javaagent:agent.jar, 其中agent.jar寫上述打包的絕對路徑
在這裏插入圖片描述

部分輸出結果如下
在這裏插入圖片描述
詳細代碼參見:github

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