百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

浅谈字节码增强技术系列1-字节码增强概览

zhezhongyun 2025-07-24 23:18 12 浏览

作者:董子龙

前言

前段时间一直想参照lombok的实现原理写一篇可以生成业务单据修改记录插件的专利,再查阅资料的过程中,偶然了解到了字节码增强工具-byteBuddy。但是由于当时时间紧促,所以没有深入的对该组件进行了解。其实再我们的日常开发中,字节码增强组件的身影无处不在,例如spring-aop和mybatis。本着知其然也要知其所以然的精神,我决定沉下心来,对字节码增强技术做一个深入的学习和总结,本文作为该系列的开篇,主要是对字节码做一下简单的介绍,为我们后面的深入学习打下一个好的基础。

一、字节码简述






字节码是一种中间状态的二进制文件,是由源码编译过来的,可读性没有源码的高。cpu并不能直接读取字节码,在java中,字节码需要经过JVM转译成机械码之后,cpu才能读取并运行。

使用字节码的好处:一处编译,到处运行。java就是典型的使用字节码作为中间语言,在一个地方编译了源码,拿着.class文件就可以在各种计算机运行。

二、字节码增强的使用场景

如果我们不想修改源码,但是又想加入新功能,让程序按照我们的预期去运行,可以通过编译过程和加载过程中去做相应的操作,简单来讲就是:将生成的.class文件修改或者替换称为我们需要的目标.class文件。

由于字节码增强可以在完全不侵入业务代码的情况下植入代码逻辑,所以可以用它来做一些酷酷的事,比如下面的几种常见场景:

1、动态代理

2、热部署

3、调用链跟踪埋点

4、动态插入log(性能监控)

5、测试代码覆盖率跟踪

...

三、字节码增强的实现方式

字节码工具

类创建

实现接口

方法调用

类扩展

父类方法调用

优点

缺点

常见使用

学习成本

java-proxy

支持

支持

支持

不支持

不支持

简单动态代理首选

功能有限,不支持扩展

spring-aop,MyBatis

1星

asm

支持

支持

支持

支持

支持

任意字节码插入,几乎不受限制

学习难度大,编写代码多

cglib

5星

javaassit

支持

支持

支持

支持

支持

java原始语法,字符串形式插入,写入直观

不支持jdk1.5以上的语法,如泛型,增强for

Fastjson,MyBatis

2星

cglib

支持

支持

支持

支持

支持

与bytebuddy看起来差不多

正在被bytebuddy淘汰

EasyMock,jackson-databind

3星

bytebuddy

支持

支持

支持

支持

支持

支持任意维度的拦截,可以获取原始类、方法,以及代理类和全部参数

不太直观,学习理解有些成本,API非常多

SkyWalking,Mockito,Hibernate,powermock

3星

四、简单示例

AOP是我们在日常开发中常用的架构设计思想,AOP的主要的实现有cglib,Aspectj,Javassist,java proxy等。接下来,我们就以我们日常开发中会遇到的在方法执行前后打印日志为切入点,手动用字节码来实现一下AOP。

定义目标接口与实现

public class SayService{
   public void say(String str) {
      System.out.println("hello" + str); 
   }
 }

定义了类SayService,再执行say方法之前,我们会打印方法开始执行start,方法执行之后,我们会打印方法执行结束end

ASM实现AOP

4.1.1、引入jar包

<dependency>    
    <groupId>org.ow2.asm</groupId>    
    <artifactId>asm</artifactId>   
    <version>9.1</version>
</dependency>

4.1.2、AOP具体实现

public class ResourceClassVisitor extends ClassVisitor implements Opcodes {

    public ResourceClassVisitor(ClassVisitor cv) {
        super(Opcodes.ASM4, cv);
    }

    public ResourceClassVisitor(int i, ClassVisitor classVisitor) {
        super(i, classVisitor);
    }

    /**访问类基本信息*/
    @Override
    public void visit(int version, int access, String name,
                      String signature, String superName, String[] interfaces) {
        this.cv.visit(version, access, name, signature, superName, interfaces);
    }

    /**访问方法基本信息*/
    @Override
    public MethodVisitor visitMethod(int access, String name,
                                     String desc, String signature, String[] exceptions) {
        MethodVisitor mv = this.cv.visitMethod(access, name, desc,
                signature, exceptions);
        //假如不是构造方法,我们构建方法的访问对象(MethodVisitor)
        if (!name.equals("<init>") && mv != null) {
            mv = new ResourceClassVisitor.MyMethodVisitor((MethodVisitor)mv);
        }

        return (MethodVisitor)mv;
    }

    /**自定义方法访问对象*/
    class MyMethodVisitor extends MethodVisitor implements Opcodes {

        public MyMethodVisitor(MethodVisitor mv) {
            super(Opcodes.ASM4, mv);
        }
        /**此方法会在方法执行之前执行*/
        @Override
        public void visitCode() {
            super.visitCode();
            this.mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out",
                    "Ljava/io/PrintStream;");
            this.mv.visitLdcInsn("方法开始执行start");
            this.mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream",
                    "println", "(Ljava/lang/String;)V", false);
        }
        /**对应方法体本身*/
        @Override
        public void visitInsn(int opcode) {
            //在方法return或异常之前,添加一个end输出
            if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) || opcode == Opcodes.ATHROW) {
                this.mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out",
                        "Ljava/io/PrintStream;");
                this.mv.visitLdcInsn("方法执行结束end");
                this.mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream",
                        "println", "(Ljava/lang/String;)V", false);
            }
            this.mv.visitInsn(opcode);
        }
    }
}
public class AopTest {

    public static void main(String[] args) throws IOException {
        //第一步:构建ClassReader对象,读取指定位置的class文件(默认是类路径-classpath)
        ClassReader classReader = new ClassReader("com/aop/SayService");
        //第二步:构建ClassWriter对象,基于此对象创建新的class文件
        //ClassWriter.COMPUTE_FRAMES 表示ASM会自动计算max stacks、max locals和stack map frame的具体内容。
        //ClassWriter.COMPUTE_MAXS 表示ASM会自动计算max stacks和max locals,但不会自动计算stack map frames。
        ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);//推荐使用COMPUTE_FRAMES
        //第三步:构建ClassVisitor对象,此对象用于接收ClassReader对象的数据,并将数据处理后传给ClassWriter对象
        ClassVisitor classVisitor = new ResourceClassVisitor(classWriter);
        //第四步:基于ClassReader读取class信息,并将数据传递给ClassVisitor对象
        //这里的参数ClassReader.SKIP_DEBUG表示跳过一些调试信息等,ASM代码看上去就会更简洁
        //这里的参数ClassReader.SKIP_FRAMES表示跳过一些方法中的部分栈帧信息,栈帧手动计算非常复杂,所以交给系统去做吧
        //推荐用这两个参数
        classReader.accept(classVisitor, ClassReader.SKIP_DEBUG|ClassReader.SKIP_FRAMES);
        //第五步:从ClassWriter拿到数据,并将数据写出到一个class文件中
        byte[] data = classWriter.toByteArray();
        //将字节码写入到磁盘的class文件
        File f = new File("target/classes/com/aop/SayService.class");
        FileOutputStream fout = new FileOutputStream(f);
        fout.write(data);
        fout.close();
        SayService rs = new SayService();
        rs.say("asm");//start,handle(),end
    }
}

4.1.3、测试类输出结果



Javassist实现AOP

4.2.1、引入jar包

<dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.28.0-GA</version>
</dependency>

4.2.2、AOP具体实现

public class AopTest {

    public static void main(String[] args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.get("com.aop.SayService");
        CtMethod personFly = cc.getDeclaredMethod("say");
        personFly.insertBefore("System.out.println(\"方法开始执行start\");");
        personFly.insertAfter("System.out.println(\"方法执行结束end\");");
        cc.toClass();
        SayService sayService = new SayService();
        sayService.say("assist");
    }
}

4.2.3、测试类输出结果



五、总结

作为字节码增强系列文章的开篇,只是简单的介绍了一下字节码的定义、字节码的实现方式,最后通过具体示例向大家展示了如何对字节码进行增强。再后续的文章中,会对相关框架的原理及具体应用做一个细化的总结,欢迎各位大佬的批评与指正。

相关推荐

Trump Pushes for 15%-20% Minimum Tariffs on EU, Sticks to Auto Duties: Report

TMTPOST--U.S.PresidentDonaldTrumpistakingatougherstanceduringtradenegotiationswiththe...

Note-15.使用A4988控制步进电机(dvp15mc11t控制步进电机)

如果需要控制一堆步进电机,那么只使用一个Arduino来控制,就会占用大量的处理时间,而无法处理其他事情,除非使用一个独立的专用步进电机驱动器:A4988。A4988只需两个引脚就可以控制双极步进电机...

Negotiated settlement, not sanctions, the right way to end the Ukraine crisis

Negotiatedsettlement,notsanctions,therightwaytoendtheUkrainecrisis:ChinaDailyeditorial...

U.S. Services May be Added to EU&#39;s Retaliatory Target List as More Members Seek Powerful Trade Tool If Talks Fail

TMTPOST--AmericanservicescouldbeaddedtotheEuropeanUnion’sretaliatorytargetlist,highlig...

S7-1200伺服指令运动指令(s71200伺服位置控制实例)

1.MC_Halt指令名称:停止轴运行指令功能:停止所有运动并以组态的减速度停止轴。使用技巧:常用MC_Halt指令来停止通过MC_MoveVelocity指令触发的轴的运行。『注意』部分输入/输出管...

Deepseek太强了!等了10年的Excel模糊下拉,竟然5分钟就搞定了

今天跟大家分享下我们如何通过Deepseek来编写VBA代码,制作模糊搜索的下拉菜单,这个等来十来年的功能,用Deepseek竟然几分钟就搞定了,不得不感叹AI工具的强大,我们以后能干的过AI吗,这真...

7.Unity物理关节(unity物理骨骼)

7.物理关节Unity的物理关节组件将刚体连接到另一个刚体或空间中的固定点。施加使刚体移动的力,关节限制可以限制移动。关节赋予刚体一定的自由度,从而使这些刚体具有不同的运动。Unity提供的物理关...

西门子111报文对应FB284引脚(西门子111报文详解)

西门子FB284是基于111报文的功能块,使用FB284比较方便,直接调用就可以控制V90伺服的定位控制。下面是整理的FB284对应的111报文。便于理解FB284功能块,更方面应用在实际项目中。11...

数据质量动态探查及相关前端实现(数据质量改进实践指南)

需求背景数据探查上线之前,数据验证都是通过写SQL方式进行查询的,从编写SQL,到解析运行出结果,不仅时间长,还会反复消耗计算资源,探查上线后,只需要一次探查,就可以得到整张表的探查报告,但后续...

阿里面试官:你连个排序算法都讲不明白?出门右拐吧

排序算法一表总览其他注意事项:计数排序中,kkk是整数的范围稳定性是指,序列中相同的数是否有可能交换顺序,例如序列中有两个8,顺序为888和8′8^{'}8′,如果在排序完之后,顺序有...

直流电机速度、位置双环控制简明教程

速度、位置的双环控制是我们在电机的控制系统中常用的方法,很实用。下面让我们来看一下内部实现的原理。1.速度闭环控制我们一般在速度闭环控制系统里面,使用增量式PI控制。而在我们的微处理器里面,因为控制器...

纳米机器人的精准定位与导航(纳米机器人怎么控制位置)

纳米机器人的精准定位与导航涉及多学科交叉技术,其“源码”(控制逻辑与算法)需结合硬件特性、环境感知和执行器设计。以下从控制原理、算法逻辑、关键技术实现等方面提供概念性思路(非实际可运行代码),供技术探...

C语言进阶教程:多级指针的应用(c语言一级指针和二级指针)

在C语言中,指针可以指向变量的地址。多级指针(PointerstoPointers或MultilevelPointers)则是指向另一个指针地址的指针。这种概念可以扩展到任意级别(二级指针、...

被 Trac 的文件整理能力圈粉了!这 “香” 气挡不住

前阵子整理电脑文件夹时,我遇到了个麻烦:上千个文档和照片,要是手动一个个重命名、分类,不仅手得点到发麻,估计还得耗上一上午。于是我就琢磨着,能不能找个办法实现一键批量重命名。我先在网上搜了些批量改名软...

Enhancer-轻量化的字节码增强组件包

一、问题描述当我们的业务发展到一定阶段的时候,系统的复杂度往往会非常高,不再是一个简单的单体应用所能够承载的,随之而来的是系统架构的不断升级与演变。一般对于大型的ToC的互联网企业来说,整个系统都是...