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());
        }
    }

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

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

相关文章

  • 详解springmvc控制登录用户session失效后跳转登录页面

    下面我将详细讲解“详解SpringMVC控制登录用户Session失效后跳转登录页面”的完整攻略,包括具体步骤和示例说明: 背景 在Web应用中,通常会对用户进行登录验证,并在登录成功后将用户的登录状态保存在Session中,当用户操作时,需要检查Session是否过期或失效,若失效或过期需要重新登录。 实现步骤 1. 配置web.xml文件 在web.xm…

    Java 2023年6月16日
    00
  • 详解spring整合hibernate的方法

    下面是详解spring整合hibernate的方法的完整攻略: 一、准备工作 1.1 项目结构 首先,我们需要新建一个Maven项目,并在其中引入Spring和Hibernate的相关依赖,具体的pom.xml文件可以参考以下代码: <!– Spring –> <dependency> <groupId>org.spr…

    Java 2023年5月19日
    00
  • Java 跳出递归循环问题解决办法

    Java 跳出递归循环问题通常出现在递归函数内部的某一个条件不满足时,需要跳出递归循环。 常见的解决办法有以下几种: 使用非递归实现 将递归函数转换成非递归的形式,使用栈或队列进行迭代实现。这样的好处是可以在循环中使用break或return语句来跳出循环。 示例1:阶乘的非递归实现 public static long factorial(int n) {…

    Java 2023年5月25日
    00
  • 详解JVM基础之字节码的增强技术

    详解JVM基础之字节码的增强技术 JVM(Java Virtual Machine)是一台虚拟机,它将字节码解释成可执行代码。在Java技术中,字节码是Java类文件中的中间表示形式,并且它是可移植性的关键构成部分。在字节码增强技术中,我们可以使用字节码操作库修改字节码,以实现在程序运行时的动态织入。 一、字节码增强技术的概念 字节码增强技术是在字节码级别上…

    Java 2023年5月26日
    00
  • SpringBoot是如何使用SQL数据库的?

    Spring Boot是一个基于Spring框架的快速开发框架,它可以帮助我们快速构建Web应用程序。在Spring Boot中,我们可以使用多种方式来使用SQL数据库。以下是两种常见的方式: 1. 使用Spring Data JPA Spring Data JPA是一种基于JPA的数据访问框架,它可以帮助我们快速构建数据访问层。以下是一个示例: 添加依赖 …

    Java 2023年5月14日
    00
  • Spring Cloud Config配置文件使用对称加密的方法

    关于Spring Cloud Config配置文件使用对称加密的方法的攻略如下: 1. 配置对称加密 首先我们需要在工程中添加对称加密的模块和配置文件。比如我们可以使用Jasypt来实现对称加密,只需要在pom.xml文件中引入对应的依赖即可: <dependency> <groupId>com.github.ulisesbocchi…

    Java 2023年5月20日
    00
  • 两个JSP页面父页面获取子页面内容的两种方法

    我们来详细讲解一下如何在JSP页面中实现父页面获取子页面内容的两种方法。 概述 在JSP中,子页面中可能会包含一些重要的内容,而父页面需要获取这些内容。常见的想法是通过使用JavaScript解析DOM树,但这种方法存在一些繁琐和困难。因此,在这里我们介绍两种非常简单的方法来实现该功能: 使用JSP隐式对象 使用标签 方法一:使用JSP隐式对象 JSP页面中…

    Java 2023年6月15日
    00
  • java 输出九九乘法表口诀的代码

    Java 输出九九乘法表口诀是 Java 入门学习必备的程序之一,下面我将为大家详细讲述 Java 输出九九乘法表口诀的完整攻略,让大家在学习 Java 时可以更加轻松自如地完成这个任务。 程序思路 Java 输出九九乘法表口诀是一个典型的嵌套循环程序,具体实现过程如下: 外层循环控制行数,内层循环控制列数。 每一行输出多个数值,用空格隔开,可以使用 Sys…

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