Java字节码插装的作用是什么?

Java字节码插装是指在程序运行期间通过修改Java程序的字节码来达到修改程序行为和进行调试的目的。常见的字节码插装技术有Java Agent和AspectJ。

Java字节码插装的作用主要分为以下两个方面:

  1. 类加载时期修改类的字节码,在程序运行时对其进行增强
  2. 在程序运行时,通过对方法的字节码进行修改,实现将自己的代码嵌入到目标方法的中间或结尾位置

常见的应用场景有:

  1. 监控与诊断:通过字节码插装技术来监控Java程序的运行状态,如对内存、线程、方法执行时间等进行监控和分析,从而提高应用的健壮性和性能。例如,可以使用字节码插装实现Java监控工具,或者将统计数据插入目标方法中。

  2. AOP编程:通过字节码插装技术实现面向切面编程(AOP),实现横向扩展和重用目标代码的功能。例如,可以使用AspectJ实现具有交叉功能的切面,从而减少代码重复。

下面给出两个Java字节码插装的示例:

示例一

一个简单的应用场景是:在程序运行过程中,记录方法的执行时间,以便分析哪个方法最耗费时间,并进行优化。使用字节码插装技术可以很方便的实现这一功能。

步骤

  1. 编写Agent

    public class TimeCostAgent {
        public static void premain(String agentArgs, Instrumentation instrumentation) {
            instrumentation.addTransformer(new TimeCostTransformer());
        }
    }
    
  2. 实现Transformer

实现一个Transformer,通过在方法的字节码中添加代码记录方法执行耗时。在返回的过程中输出耗时信息。

    public class TimeCostTransformer implements ClassFileTransformer {
        @Override
        public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
                                ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
            if (className.startsWith("com/example/")) {
                ClassReader classReader = new ClassReader(classfileBuffer);
                ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
                TimeCostClassVisitor classVisitor = new TimeCostClassVisitor(classWriter);
                classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES);

                return classWriter.toByteArray();
            }
            return classfileBuffer;
        }
    }
  1. 实现ClassVisitor

实现一个ClassVisitor,遍历类的方法,在方法字节码的头部和尾部添加代码记录时间,可以使用ASM框架实现。

    public class TimeCostClassVisitor extends ClassVisitor {

        private String className;

        public TimeCostClassVisitor(ClassWriter classWriter) {
            super(Opcodes.ASM5, classWriter);
        }

        @Override
        public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
            this.className = name;
            super.visit(version, access, name, signature, superName, interfaces);
        }

        @Override
        public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
            MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
            return new TimeCostMethodVisitor(mv, className, name, desc);
        }
    }
  1. 实现MethodVisitor

实现一个MethodVisitor,通过在头部和尾部添加代码记录时间。在方法返回的时候输出运行时间。

    public class TimeCostMethodVisitor extends MethodVisitor {

        private String className;

        private String methodName;

        public TimeCostMethodVisitor(MethodVisitor methodVisitor, String className, String methodName, String desc) {
            super(Opcodes.ASM5, methodVisitor);
            this.className = className;
            this.methodName = methodName;
        }

        @Override
        public void visitCode() {
            super.visitCode();
            mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv.visitLdcInsn(className + "." + methodName + " start at " + new Date());
            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
            mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "nanoTime", "()J", false);
            mv.visitVarInsn(Opcodes.LSTORE, 1);
        }

        @Override
        public void visitInsn(int opcode) {
            if (opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN || opcode == Opcodes.ATHROW) {
                mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
                mv.visitLdcInsn(className + "." + methodName + " end at " + new Date());
                mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
                mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "nanoTime", "()J", false);
                mv.visitVarInsn(Opcodes.LSTORE, 3);
                mv.visitVarInsn(Opcodes.LLOAD, 3);
                mv.visitVarInsn(Opcodes.LLOAD, 1);
                mv.visitInsn(Opcodes.LSUB);
                mv.visitVarInsn(Opcodes.LSTORE, 5);
                mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
                mv.visitLdcInsn(className + "." + methodName + " cost " );
                mv.visitVarInsn(Opcodes.LLOAD, 5);
                mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false);
                mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Long", "toString", "()Ljava/lang/String;", false);
                mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
            }
            super.visitInsn(opcode);
        }

    }
  1. 实现Agentmain或者premain

在程序运行前通过Java Agent加载Agent,在加载的时候将Transformer添加到Agent中。

    public class TimeCostAgent {

        public static void premain(String agentArgs, Instrumentation instrumentation) {
            instrumentation.addTransformer(new TimeCostTransformer());
        }

        public static void agentmain(String agentArgs, Instrumentation instrumentation) {
            instrumentation.addTransformer(new TimeCostTransformer());
        }
    }

示例二

字节码插装的另一个应用场景是:使用字节码插装工具修改已有类的行为,达到自己的业务需求。

例如,有一个类:

public class Hello {
    public void sayHello(String name) {
        System.out.println("Hello, " + name + "!");
    }
}

现在需要修改这个类的sayHello方法的行为,增加一个参数age,输出Hello之前判断age是否大于18,如果大于18则输出"Hello, " + name + "!",否则输出"You are not legal!"

步骤

  1. 编写Agent

    public class HelloAgent {
        public static void premain(String agentArgs, Instrumentation instrumentation) {
            instrumentation.addTransformer(new HelloTransformer());
        }
    }
    
  2. 实现Transformer

实现一个Transformer,通过修改方法字节码来修改类行为。使用ASM框架实现。

    public class HelloTransformer implements ClassFileTransformer {

        @Override
        public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
                                ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
            if (className.equals("Hello")) {
                ClassReader classReader = new ClassReader(classfileBuffer);
                ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
                classReader.accept(new HelloClassVisitor(classWriter), ClassReader.EXPAND_FRAMES);
                return classWriter.toByteArray();
            }
            return classfileBuffer;
        }
    }
  1. 实现ClassVisitor

实现一个ClassVisitor,遍历类的方法,在方法字节码中添加代码实现自己的业务需求。可以使用ASM框架实现。

    public class HelloClassVisitor extends ClassVisitor {
        public HelloClassVisitor(ClassVisitor classVisitor) {
            super(Opcodes.ASM5, classVisitor);
        }

        @Override
        public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
            MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
            if (name.equals("sayHello")) {
                mv = new HelloMethodVisitor(mv);
            }
            return mv;
        }
    }
  1. 实现MethodVisitor

实现一个MethodVisitor,在方法字节码中添加代码实现自己的业务需求。增加一个参数age,输出Hello之前判断age是否大于18。

    public class HelloMethodVisitor extends MethodVisitor {
        public HelloMethodVisitor(MethodVisitor methodVisitor) {
            super(Opcodes.ASM5, methodVisitor);
        }

        @Override
        public void visitCode() {
            super.visitCode();
            mv.visitIntInsn(Opcodes.BIPUSH, 18);
            mv.visitVarInsn(Opcodes.ILOAD, 2);
            Label label = new Label();
            mv.visitJumpInsn(Opcodes.IF_ICMPLE, label);
            mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder");
            mv.visitInsn(Opcodes.DUP);
            mv.visitLdcInsn("Hello, ");
            mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "(Ljava/lang/String;)V", false);
            mv.visitVarInsn(Opcodes.ALOAD, 1);
            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
            mv.visitLdcInsn("!");
            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
            mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;", false);
            mv.visitInsn(Opcodes.ARETURN);
            mv.visitLabel(label);
        }
    }
  1. 实现Agentmain或者premain

在程序运行前通过Java Agent加载Agent,在加载的时候将Transformer添加到Agent中。

    public class HelloAgent {
        public static void premain(String agentArgs, Instrumentation instrumentation) {
            instrumentation.addTransformer(new HelloTransformer());
        }

        public static void agentmain(String agentArgs, Instrumentation instrumentation) {
            instrumentation.addTransformer(new HelloTransformer());
        }
    }
阅读剩余 81%

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Java字节码插装的作用是什么? - Python技术站

(0)
上一篇 2023年5月11日
下一篇 2023年5月11日

相关文章

  • Spring中如何操作JDBC的实现

    Spring中操作JDBC主要有以下三种方式: 使用JdbcTemplate或NamedParameterJdbcTemplate 实现JdbcOperations接口 使用SimpleJdbcInsert或SimpleJdbcCall 下面对每种方式进行详细的讲解。 1. 使用JdbcTemplate或NamedParameterJdbcTemplate …

    Java 2023年5月20日
    00
  • Spring Boot 应用的热部署配置方法

    下面我将详细讲解 Spring Boot 应用的热部署配置方法。 什么是热部署? 热部署是指在应用运行的过程中,不需要重新启动应用,就能实现应用代码的更新。这在开发调试阶段非常有用,因为可以避免频繁地重启应用,提高开发效率。 Spring Boot 应用的热部署配置方法 方法一:使用 Spring Boot DevTools Spring Boot DevT…

    Java 2023年5月19日
    00
  • Fixie.js 自动填充内容的插件

    Fixie.js 是一个用于自动填充表单内容的 JavaScript 插件,可以自动填充表单、日期、时间等多种类型的数据。下面是使用 Fixie.js 的详细攻略: 第一步:引入 Fixie.js 将 Fixie.js 文件下载到本地,并在 HTML 中引入该文件,代码如下: <script src="path/to/fixie.js&quo…

    Java 2023年6月15日
    00
  • Java StringBuilder类相关知识总结

    下面是关于Java StringBuilder类的详细讲解攻略。 字符串拼接的问题 在Java中,字符串拼接可以使用+号或者字符串连接方法concat()来实现,示例如下: String str1 = "Hello"; String str2 = "World"; String str3 = str1 + "…

    Java 2023年5月27日
    00
  • Java中将base64编码字符串转换为图片的代码

    要将base64编码字符串转换为图片,可以按照以下步骤进行操作: 1. 解码base64编码字符串 首先需要将base64编码的字符串解码为字节数组。在Java中,可以通过使用Base64类的getDecoder()方法获取Base64.Decoder对象来解码base64编码的字符串,示例代码如下: import java.util.Base64; Str…

    Java 2023年5月20日
    00
  • 图文详解Maven工程打jar包的N种方式

    图文详解Maven工程打jar包的N种方式 在Maven工程开发中,打jar包是必不可少的步骤之一。本文将介绍多种方式打jar包的方法并逐一详细讲解。下面为大家列出打jar包的多种方法: 通过Maven命令打jar包 通过Maven插件打可执行的jar包 通过Maven插件打普通jar包 通过Maven配置pom.xml文件打jar包 利用Maven打jar…

    Java 2023年5月20日
    00
  • Java 入门图形用户界面设计之列表框JList

    下面我将详细讲解Java入门图形用户界面设计之列表框JList的完整攻略,包含以下几个方面: 列表框JList的介绍 列表框JList的基本使用方式 列表框JList的高级使用方式 示例说明 注意事项 1. 列表框JList的介绍 列表框JList是Swing组件库中的一种用于显示列表项的组件,它可以显示一个或多个列表项,并且支持单选、多选等不同的选择模式。…

    Java 2023年5月26日
    00
  • JAVA记住密码功能的实现代码

    下面我将为您详细讲解“JAVA记住密码功能的实现代码”的完整攻略。 什么是记住密码功能? 记住密码功能是指在用户登录过网站后,即使关闭浏览器或者退出系统之后再次打开网站或系统时,该用户的帐号及密码仍然能够自动填写在登录框中,方便用户使用。 JAVA实现记住密码功能的步骤 第一步:保存用户登录信息 当用户登录成功后,保存用户的登录信息到本地,一般采用cooki…

    Java 2023年6月15日
    00
合作推广
合作推广
分享本页
返回顶部