一文搞定Spring容器初始化方法栈调用栈

11 篇文章 1 订阅

背景

大家都知道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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值