• 字节码插桩技术


    字节码插桩

    我们知道JVM是不能直接执行.java 代码,也不能直接执行.class文件,它只能执行.class 文件中存储的指令码。这就是为什么class需要通过classLoader 装载以后才能运行。基于此机制可否在ClassLoader装载之前拦截修改class当中的内容(jvm 指令码)从而让程序中包含我们的埋点逻辑呢?答案是肯定的,但需要用到两个技术 javaagent与javassist 。前者用于拦截ClassLoad装载,后者用于操作修改class文件。

     

    javaagent

    javaagent介绍

    javaagent 是java1.5之后引入的特性,其主要作用是在class 被加载之前对其拦截,以插入我们的监听字节码

    javaagent jar包

    javaagent 最后展现形式是一个Jar包,有以下特性:

    1.必须 META-INF/MANIFEST.MF中指定Premain-Class 设定启agent启动类。

    2.在启类需写明启动方法 public static void main(String arg,)

    3.不可直接运行,只能通过 jvm 参数-javaagent:xxx.jar 附着于其它jvm 进程运行。

     

    javaagent使用

    1、编写agent方法

    public class MyAgent {
        public static void premain(String args, Instrumentation instrumentation) throws Exception {
            System.out.println("Hello javaagent permain:"+args);
        }
    }

    2、添加premain-class参数

    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-jar-plugin</artifactId>
        <version>2.2</version>
        <configuration>
            <archive>
                <manifestEntries>
                    <Project-name>${project.name}</Project-name>
                    <Project-version>${project.version}</Project-version>
                    <Premain-Class>com.javaagent.MyAgent</Premain-Class>
                    <Can-Redefine-Classes>true</Can-Redefine-Classes>
                    <Can-Retransform-Classes>true</Can-Retransform-Classes>
                </manifestEntries>
            </archive>
            <skip>true</skip>
        </configuration>
    </plugin>

    3、构建打包

    4、在任一JAVA应用中 添加jvm 参数并启动 -javaagent:xxx.jarjavaagent META-INF/MANIFEST.MF

    参数说明:

    Premain-Class:必填,agent启动

    classCan-Redefine-Classes:默认为false ,是否允许重新定义

    classCan-Retransform-Classes:默认为false,是否允许重置Class,重置后相当于class 从classLoade中清除,下次有需要的时候会重新装载,也会重新走Transformer 流程。

    Boot-Class-Path:agent 所依赖的jar 路径,多个用空格分割

    创建一个测试类MyAgentTest并运行查看结果

    public class MyAgentTest {
        public static void main(String[] args) {
            System.out.println("main");
        }
    }
    //运行结果:main

    添加jvm参数

    参数内容:-javaagent:/Users/jinyunlong/IdeaProjects/test-agent/target/test-agent-1.0-SNAPSHOT.jar=123


    再次运行测试类MyAgentTest并查看结果

    javassist

    javassist介绍

    javassist是一个开源的分析、编辑和创建Java字节码的类库。其主要的优点,在于简单,而且快速。直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成(注:也可以使用ASM实现,但需要会操作字节码指令,学习使用成本高)

    javassist使用

    使用javassist需要引入javasssist的jar包,添加内容如下:

    <dependencies>
        <dependency>
            <groupId>org.javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>3.18.1-GA</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>2.2</version>
                <configuration>
                    <archive>
                        <manifestEntries>
                            <Project-name>${project.name}</Project-name>
                            <Project-version>${project.version}</Project-version>
                            <Premain-Class>com.javaagent.MyAgent</Premain-Class>
                            <Can-Redefine-Classes>true</Can-Redefine-Classes>
                            <Can-Retransform-Classes>true</Can-Retransform-Classes>
                            <Boot-Class-Path>javassist-3.18.1-GA.jar</Boot-Class-Path>
                        </manifestEntries>
                    </archive>
                    <skip>true</skip>
                </configuration>
            </plugin>
        </plugins>
    </build>

    演示插入打印当前时间

    创建类MyServer

    public class MyServer {
        public Integer sayHello(String name,String message){
            System.out.println("hello");
            return 0;
        }
    }

    myAgent类

     

    创建测试类并调用MyServer中的sayHello方法

    演示计算方法调用时间

    类MyAgent

    public class MyAgent {
        public static void premain(String args, Instrumentation instrumentation) throws Exception {
            instrumentation.addTransformer(new ClassFileTransformer() {
                public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, 
                byte[] classfileBuffer) throws IllegalClassFormatException {
                    if(!"com/javaagent/MyServer".equals(className)){
                        return null;
                    }
                    try {
                        return buildMonitorClass();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    return null;
                }
            },true);
    
        }
        private static byte[] buildMonitorClass() throws Exception{
            /**
             * 1、拷贝一个新的方法
             * 2、修改原方法名
             * 3、加入监听代码
             */
            ClassPool pool = new ClassPool();
            pool.appendSystemPath();
            CtClass ctClass = pool.get("com.javaagent.MyServer");
            CtMethod ctMethod = ctClass.getDeclaredMethod("sayHello");
            CtMethod copyMethod = CtNewMethod.copy(ctMethod,ctClass,new ClassMap());
            ctMethod.setName("sayHello$agent");
            copyMethod.setBody("{
    " +
                    "    long begin = System.nanoTime();
    " +
                    "    try {
    " +
                    "        return sayHello$agent($1,$2);
    " +
                    "    } finally {
    " +
                    "        System.out.println(System.nanoTime() - begin);}
    " +
                    "    }");
            ctClass.addMethod(copyMethod);
            return ctClass.toBytecode();
        }
    }

    修改类MyServer

    public class MyServer {
    
        public Integer sayHello(String name,String message){
            System.out.println("hello name:"+name+",message:"+message);
            return 0;
        }
    }

    修改测试类并运行

    public class MyAgentTest {
        public static void main(String[] args) {
            MyServer myServer = new MyServer();
            myServer.sayHello("paul","1234");
        }
    }
    //运行结果:
    hello name:paul,message:1234
    186537

    javassist 特殊语法

     

     

  • 相关阅读:
    android入门之三【应用程序组成】
    Palm应用开发之一开发环境搭建
    android 入门之一【开发环境搭建】
    在DataGridView中的CheckBox值变更后立即获取值。
    根据字符串返回类型
    CSS模拟不同的拐角效果
    SQL查询生成交叉列表
    LinkButton 的 OnClick 事件 可以是一个方法
    代替marquee的滚动字幕效果代码
    JavaScript实现DataGrid中添加CheckBox列(全选与否)
  • 原文地址:https://www.cnblogs.com/paul-blog/p/11048094.html
Copyright © 2020-2023  润新知