zoukankan      html  css  js  c++  java
  • AOP实践:java.lang.instrument的使用

    背景

    在 rcjp 项目中,需要调用 ASM API(用于字节码处理的开源库)对字节码进行处理,目标是实现对 Java 程序运行时各种对象的动态跟踪,并进一步分析各个对象之间的关系。在此之前,需要考虑如何获取程序运行的入口。

    首先,我考虑到了自定义类加载器(详情见参考资料),即在程序的 main 入口处,首先加载自定义的类加载器,然后通过反射技术使用这个类加载器加载并调用测试程序。这个方法缺点是:每次都必须先找到测试程序的入口类,而对于有的封装成 jar 的程序集合,这一点相对比较难控制。

    于是,有了这里介绍的方法:通过 java.lang.instrument 实现的 java agent 对象操作字节码,是一种 AOP 的方法。

    程序中,除了 ASMAgent 以外的所有类都是调用 ASM API 实现对测试程序中各个对象的构造、方法调用、属性赋值等操作行为的记录(其中对 Collection 子类的处理着实费了一番心血= =,字节码操作很细节,容易出错)。

    原理

    JVMTI(Java Virtual Machine Tool Interface)是一套本地编程接口集合,它提供了一套『代理』机制,可以支持第三方工具程序以代理的方式连接和访问 JVM,并利用 JVMTI 提供的丰富的编程接口,完成很多跟 JVM 相关的功能。

    java.lang.instrument 包的实现,也就是基于这种机制的:在 Instrumentation 的实现当中,存在一个 JVMTI 的代理程序,通过调用 JVMTI 当中 Java 类相关的函数来完成 Java 类的动态操作。

    Instrumentation 的最大作用就是类定义的动态改变和操作。在 Java SE 5 及其后续版本当中,开发者可以在一个普通 Java 程序(带有 main 函数的 Java 类)运行时,通过 – javaagent 参数指定一个特定的 jar 文件(包含 Instrumentation 代理)来启动 Instrumentation 的代理程序。

    步骤

    1.编写 java 代理类

    这个类中,premain 方法是关键,对比于一般的入口 main 一样,这里的 premain 是在 main 之前执行的。它会告诉 JVM 如何处理加载上来的 java 字节码。如下例:

    1
    2
    3
    4
    
    public static void premain(String agentArgs, Instrumentation inst) {
                        Trace.BeginTrace(); // it's important for trace files  
                inst.addTransformer(new ASMAgent());
            }
    

    值得注意的是,addTransformer 实现了对字节码处理的方法的回调。

    1
    
    inst.addTransformer(new ASMAgent());
    

    类 ASMAgent 包含着实现对 java 字节码处理的方法:transform()。它来自于 ClassFileTransformer 接口。为了方便,这里将对 ClassFileTransformer 接口的实现跟 ASMAgent 类放在了一起。其中 classfileBuffer 是类文件加载时的原始的字节码,retVal 则是经过处理后的字节码。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
    public byte[] transform(ClassLoader loader, String className,Class<?> classBeingRedefined,
                                            ProtectionDomain protectionDomain,byte[] classfileBuffer)
                throws IllegalClassFormatException {
                byte[] retVal = null;
    
                if(isInstrumentable(className)){
                    ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
                    ASMClassAdapter mca = new ASMClassAdapter(cw);
                    ClassReader cr = new ClassReader(classfileBuffer);
                    cr.accept(mca, 0);
                    retVal = cw.toByteArray();
                }else{
                        retVal = classfileBuffer ;
                    }
    
                return retVal;
        }
    

    2.打包代理类

    只有合理打包并在 manifest 文件中记录下相应的键值对之后,才能正常执行 premain 的内容。 manifest 文件中需要添加的键值对是:

    1
    
    Premain-Class : biaobiaoqi.asm.ASMAgent
    

    如果对字节码的处理有应用到了其他的类,需要在 manifest 中增加路径。比如使用到了 asm-3.0.jar,则增加如下语句:

    1
    
    Class-Path: asm-3.0.jar
    

    3.执行

    执行测试程序时,添加“-javaagent:代理类的 jar[=传入 premain 的参数]”选项。

    比如,对于博主的程序,就是

    java -javaagent:ASMInstrument.jar -jar XXXX.jar xxxx

    其中 ASMInstrument.jar 是第二步中打包的程序, XXX.jar 是需要测试的程序, xxx 是 XXX.jar 执行时可能的命令行参数。

    如果只是执行某.class 文件中的类,我们假设是在当前目录下的一个 XXXX 类,则是: java -javaagent:ASMInstrument.jar -cp ./ XXXX xxx

    其中 xxx 是可能的命令行参数。

    参考资料

  • 相关阅读:
    当我们开发一个接口时需要注意些什么
    一条查询语句在MySQL服务端的执行过程
    痞子衡嵌入式:基于恩智浦i.MXRT1060的MP4视频播放器(RT-Mp4Player)设计
    痞子衡嵌入式:基于恩智浦i.MXRT1010的MP3音乐播放器(RT-Mp3Player)设计
    《痞子衡嵌入式半月刊》 第 18 期
    痞子衡嵌入式:MCUBootUtility v2.4发布,轻松更换Flashloader文件
    华为HMS Core助力开发者打造精品应用,共创数智生活
    HMS Core 6.0即将发布 加码应用生态升级
    华为开发者联盟生态市场企业特惠GO第1期—应用软件专题
    卡片跳转快应用指定页面,如何点返回直接退出快应用回到卡片
  • 原文地址:https://www.cnblogs.com/daichangya/p/12958724.html
Copyright © 2011-2022 走看看