ASM字节码插装技术初探

2024-06-14 23:20
文章标签 技术 初探 字节 asm 插装

本文主要是介绍ASM字节码插装技术初探,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、ASM简介

     ASM(全称:ASMifier Class Visitor)是一个java字节码操纵框架,ASM 提供了许多 API 和工具,可以直接以二进制形式读取和修改类文件、动态生成类或者增强既有类的功能。                

1、 ASM 主要作用

       asm用于生成、编辑、分析java的class文件

◆ 字节码生成

         可以通过 ASM 生成 Java 类的字节码,可以用于生成代理类、动态生成类等场景。

◆ 字节码修改

         可以通过 ASM 对已有的类字节码进行修改,实现一些类增强、方法拦截等功能。

◆ 字节码分析

       可以通过 ASM 对已有的类字节码进行分析,实现一些类结构的分析和转换。

2、核心API

   ASM框架中的核心类有以下几个:

◆ ClassReader

        该类用来解析编译过的class字节码文件。

◆ ClassWriter

       该类用来重新构建编译后的类,比如说修改类名、属性以及方法,甚至可以生成新的类的字

       节码文件。

◆ ClassAdapter

      该类也实现了ClassVisitor接口,它将对它的方法调用委托给另一个ClassVisitor对象。

3、工作原理

        要了解Asm的工作原理,首先需要对java的字节码文件有一定了解。

◆ class文件结构

        一个Class文件都对应着唯一的一个类或接口的定义信息,Class 文件是一组以 8 个字节为基础单位的二进制流。calss文件由“无符号数” 和 “表” 两种数据类型组成,相邻的项之间没有间隔,这使得 class 文件变得紧凑,减少存储空间。在 Java 类文件中包含了许多大小不同的项,由于每一项的结构都有严格规定,这使得 class 文件能够从头到尾被顺利地解析。

       无符号数属于基本数据类型,以 u1、u2、u4、u8 来分别代表  个字节、2 个字节、4 个字节和 8 个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值;
        表是由多个无符号数或其他表作为数据项构成的复合数据类型,以“_info”结尾。表用于描述有层次关系的复合结构的数据。

       主要的字节码数据类型见3-1图表格。

                                   (图3-1)
◆ ASM运行机制

       基于上述字节码的结构特点,ASM的ClassReader这个类可以直接由字节数组或由 class 文件间接的获得字节码数据,它能正确的分析字节码,构建出抽象的树在内存中表示字节码,对字节码树进行遍历,在遍历过程中对字节码进行修改。

   ClassReader的accept会动态绑定ClassVisitor的实现类,依次调用接口的方法,字节码空间上的偏移被转换成 visit 事件时间上调用的先后,所谓 visit 事件是指对各种不同 visit 函数的调用,ClassReader知道如何调用各种 visit 函数。

   ClassAdapter类实现了 ClassVisitor接口所定义的所有函数,当新建一个 ClassAdaptor对象的时候,需要传入一个实现了 ClassVisitor接口的对象,作为职责链中的下一个访问者Visitor,这些函数的默认实现就是简单的把调用委派给这个对象,然后依次传递下去形成职责链。当用户需要对字节码进行调整时,只需从ClassAdaptor类派生出一个子类,覆写需要修改的方法,完成相应功能后再把调用传递下去。

二、代码实操

1、生成字节码

            现在我们通过asm生成一个Student类,类定义一个name属性及其getter setter方法

public class Student{private String   name;private int  age;public void setName(String  name) {this.name= name;}public String getName() {return name;}}

             asm 生成类字节码

        ClassWriter classWriter = new ClassWriter(0);/// 定义类名Student  包名使用/分隔classWriter.visit(61, ACC_PUBLIC | ACC_SUPER, "org/example/cn/entity/Student", null, "java/lang/Object", null);classWriter.visitSource("Student.java", null);/// 定义name属性FieldVisitor   fieldVisitor = classWriter.visitField(ACC_PRIVATE, "name", "Ljava/lang/String;", null, null);fieldVisitor.visitEnd();/// 定义age属性fieldVisitor = classWriter.visitField(ACC_PRIVATE, "age", "I", null, null);fieldVisitor.visitEnd();/// 定义空参构造器MethodVisitor  methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);methodVisitor.visitCode();Label initLabelStart = new Label();methodVisitor.visitLabel(initLabelStart);methodVisitor.visitVarInsn(ALOAD, 0);methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V" );methodVisitor.visitInsn(RETURN);Label initLabelEnd = new Label();methodVisitor.visitLabel(initLabelEnd);methodVisitor.visitLocalVariable("this", "Lorg/example/cn/entity/Student;", null, initLabelStart, initLabelEnd, 0);methodVisitor.visitMaxs(1, 1);methodVisitor.visitEnd();/// 定义setName方法methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "setName", "(Ljava/lang/String;)V", null, null);methodVisitor.visitCode();Label setNameLabel0 = new Label();methodVisitor.visitLabel(setNameLabel0);methodVisitor.visitVarInsn(ALOAD, 0);methodVisitor.visitVarInsn(ALOAD, 1);methodVisitor.visitFieldInsn(PUTFIELD, "org/example/cn/entity/Student", "name", "Ljava/lang/String;");Label setNameLabel1 = new Label();methodVisitor.visitLabel(setNameLabel1);methodVisitor.visitInsn(RETURN);Label setNameLabel2 = new Label();methodVisitor.visitLabel(setNameLabel2);methodVisitor.visitLocalVariable("this", "Lorg/example/cn/entity/Student;", null, setNameLabel0, setNameLabel2, 0);methodVisitor.visitLocalVariable("name", "Ljava/lang/String;", null, setNameLabel0, setNameLabel2, 1);methodVisitor.visitMaxs(2, 2);methodVisitor.visitEnd();/// 定义getName方法methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "getName", "()Ljava/lang/String;", null, null);methodVisitor.visitCode();Label getNameLabel0 = new Label();methodVisitor.visitLabel(getNameLabel0);methodVisitor.visitVarInsn(ALOAD, 0);methodVisitor.visitFieldInsn(GETFIELD, "org/example/cn/entity/Student", "name", "Ljava/lang/String;");methodVisitor.visitInsn(ARETURN);Label getNameLabel1 = new Label();methodVisitor.visitLabel(getNameLabel1);methodVisitor.visitLocalVariable("this", "Lorg/example/cn/entity/Student;", null, getNameLabel0, getNameLabel1, 0);methodVisitor.visitMaxs(1, 1);methodVisitor.visitEnd();classWriter.visitEnd();byte[] bytes = classWriter.toByteArray();

             将字节流数组生成class文件

    try{FileOutputStream   fos = new FileOutputStream("F:/profile/asm/Student.class");fos.write(bytes);fos.close();}catch (Exception e){e.fillInStackTrace();}

          用idea或其他反编译工具打开,可以看到,生成源码和预想要求完全吻合

 2、分析字节码

            编写适配器逻辑

    public class TargetClassAdapter   extends ClassAdapter {private final  static Logger LOGGER =         LoggerFactory.getLogger(TargetClassAdapter.class);public TargetClassAdapter(ClassVisitor cv) {super(cv);}/**访问类属性*/@Overridepublic FieldVisitor visitField(int access, String name, String desc, String signature, Object exceptions) {FieldVisitor fieldVisitor = cv.visitField(access, name, desc, signature, exceptions);LOGGER.info("visitField <--------> {} {} {}  {} {} ",access,name,desc,signature,exceptions);return  fieldVisitor;}/**访问类方法*/@Overridepublic MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {LOGGER.info("visitMethod <--------> {} {} {}  {} {} ",access,name,desc,signature,exceptions);MethodVisitor methodVisitor = cv.visitMethod(access, name, desc, signature, exceptions);return methodVisitor;}}

             访问类的属性、方法

        ClassReader classReader =   new ClassReader(bytes);ClassWriter classWriter  =  new ClassWriter(ClassWriter.COMPUTE_MAXS);setMethod(classWriter);ClassVisitor classVisitor = new TargetClassAdapter(classWriter);classReader.accept(classVisitor,ClassReader.SKIP_DEBUG);

           这可以将类的属性和方法全部获取

三、ASM应用

       在java的许多框架里面,都能找到ASM的身影,比如AOP编程就可以利visitMethod对指定方法就行拦截,做前置后置增强,还有比如常用的插件Lombok就是利用ASM添加的setter getter方法。MyBatis的Mapper接口实现是通过动态代理实现,现在可以使用ASM动态创建实现了字节码来实现。

  1、定义接口

         定义StudentMapper接口

package org.example.cn.mapper;
import java.util.List;public interface StudentMapper {List<String>  findStudentList();
}

2、生成实现类

        使用ASM生成实现类StudentMapperImpl

        ClassWriter classWriter = new ClassWriter(0);/// 参数列表第3个参数表示继承的父类,第4个参数表示实现的接口classWriter.visit(61, ACC_PUBLIC | ACC_SUPER, "org/example/cn/mapper/StudentMapperImpl", null, "java/lang/Object", new String[]{"org/example/cn/mapper/StudentMapper"});classWriter.visitSource("StudentMapperImpl.java", null);/// 定义构造器MethodVisitor  methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);methodVisitor.visitCode();Label initLabel0 = new Label();methodVisitor.visitLabel(initLabel0);methodVisitor.visitVarInsn(ALOAD, 0);methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V");methodVisitor.visitInsn(RETURN);Label initLabel1 = new Label();methodVisitor.visitLabel(initLabel1);/// 局部变量表,槽位的0处放的是this变量methodVisitor.visitLocalVariable("this", "Lorg/example/cn/mapper/StudentMapperImpl;", null, initLabel0, initLabel1, 0);methodVisitor.visitMaxs(1, 1);methodVisitor.visitEnd();/// 重写findStudentList方法methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "findStudentList", "()Ljava/util/List;", "()Ljava/util/List<Ljava/lang/String;>;", null);methodVisitor.visitCode();Label label0 = new Label();methodVisitor.visitLabel(label0);///  new ArrayList()methodVisitor.visitTypeInsn(NEW, "java/util/ArrayList");methodVisitor.visitInsn(DUP);methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/util/ArrayList", "<init>", "()V");methodVisitor.visitVarInsn(ASTORE, 1);Label label1 = new Label();methodVisitor.visitLabel(label1);/// 压栈methodVisitor.visitVarInsn(ALOAD, 1);/// 赋值methodVisitor.visitLdcInsn("\u9648\u7476");///调用add方法methodVisitor.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "add", "(Ljava/lang/Object;)Z");/// 出栈methodVisitor.visitInsn(POP);Label label2 = new Label();methodVisitor.visitLabel(label2);methodVisitor.visitVarInsn(ALOAD, 1);methodVisitor.visitLdcInsn("\u674e\u73b0");methodVisitor.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "add", "(Ljava/lang/Object;)Z");methodVisitor.visitInsn(POP);Label label3 = new Label();methodVisitor.visitLabel(label3);methodVisitor.visitLineNumber(11, label3);methodVisitor.visitVarInsn(ALOAD, 1);methodVisitor.visitLdcInsn("\u91d1\u6668");methodVisitor.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "add", "(Ljava/lang/Object;)Z");methodVisitor.visitInsn(POP);Label label4 = new Label();methodVisitor.visitLabel(label4);methodVisitor.visitLineNumber(12, label4);methodVisitor.visitVarInsn(ALOAD, 1);methodVisitor.visitInsn(ARETURN);Label label5 = new Label();methodVisitor.visitLabel(label5);methodVisitor.visitLocalVariable("this", "Lorg/example/cn/mapper/StudentMapperImpl;", null, label0, label5, 0);methodVisitor.visitLocalVariable("list", "Ljava/util/List;", "Ljava/util/List<Ljava/lang/String;>;", label1, label5, 1);methodVisitor.visitMaxs(2, 2);methodVisitor.visitEnd();classWriter.visitEnd();byte[] bytes = classWriter.toByteArray();

        字节码jjava源码

public class StudentMapperImpl implements StudentMapper {public List<String> findStudentList() {List<String> list = new ArrayList();list.add("陈瑶");list.add("李现");list.add("金晨");return list;}
}

3、加载实现类

          自定义类加载器加载StudentMapperImpl字节码

public class MyClassLoader  extends  ClassLoader{/// 类名全路径private final String   className;/// 字节码private final byte[]  bytes;public MyClassLoader( String  className,byte[]  bytes){this.className = className;this.bytes = bytes;}@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {return  defineClass(className,bytes,0,bytes.length);}
}

         加载类

    String className = "org.example.cn.mapper.StudentMapperImpl";MyClassLoader myClassLoader = new MyClassLoader(className, bytes);Class<?>  clz =  myClassLoader.loadClass(className);

4、调用实现类

    ◆ 方法1

        利用构造器直接反射创建实例

   /// Class<?> aClass = Class.forName("org.example.cn.mapper.StudentMapperImpl");StudentMapper studentMapper = (StudentMapper) clz.getConstructor().newInstance();List<String> studentList = studentMapper.findStudentList();System.out.println("studentList---"+studentList);
    ◆ 方法2

       动态代理实例化接口

    StudentMapper studentMapper = (StudentMapper) Proxy.newProxyInstance(myClassLoader, new Class[]{StudentMapper.class}, (proxy, method, args1) -> method.invoke(clz.getConstructor().newInstance(), args1));List<String> studentList = studentMapper.findStudentList();System.out.println("studentList---"+studentList);

        运行结果

   

      ASM 操作字节码功能强大,但是有一定难度,需要对jvm字节码比较熟悉,用起来才会游刃有余,这种黑科技更适合造轮子框架,平常业务开发基本上很难碰到合适的应用场景。 

这篇关于ASM字节码插装技术初探的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1061787

相关文章

字节面试 | 如何测试RocketMQ、RocketMQ?

字节面试:RocketMQ是怎么测试的呢? 答: 首先保证消息的消费正确、设计逆向用例,在验证消息内容为空等情况时的消费正确性; 推送大批量MQ,通过Admin控制台查看MQ消费的情况,是否出现消费假死、TPS是否正常等等问题。(上述都是临场发挥,但是RocketMQ真正的测试点,还真的需要探讨) 01 先了解RocketMQ 作为测试也是要简单了解RocketMQ。简单来说,就是一个分

【专题】2024飞行汽车技术全景报告合集PDF分享(附原数据表)

原文链接: https://tecdat.cn/?p=37628 6月16日,小鹏汇天旅航者X2在北京大兴国际机场临空经济区完成首飞,这也是小鹏汇天的产品在京津冀地区进行的首次飞行。小鹏汇天方面还表示,公司准备量产,并计划今年四季度开启预售小鹏汇天分体式飞行汽车,探索分体式飞行汽车城际通勤。阅读原文,获取专题报告合集全文,解锁文末271份飞行汽车相关行业研究报告。 据悉,业内人士对飞行汽车行业

金融业开源技术 术语

金融业开源技术  术语 1  范围 本文件界定了金融业开源技术的常用术语。 本文件适用于金融业中涉及开源技术的相关标准及规范性文件制定和信息沟通等活动。

AI(文生语音)-TTS 技术线路探索学习:从拼接式参数化方法到Tacotron端到端输出

AI(文生语音)-TTS 技术线路探索学习:从拼接式参数化方法到Tacotron端到端输出 在数字化时代,文本到语音(Text-to-Speech, TTS)技术已成为人机交互的关键桥梁,无论是为视障人士提供辅助阅读,还是为智能助手注入声音的灵魂,TTS 技术都扮演着至关重要的角色。从最初的拼接式方法到参数化技术,再到现今的深度学习解决方案,TTS 技术经历了一段长足的进步。这篇文章将带您穿越时

系统架构设计师: 信息安全技术

简简单单 Online zuozuo: 简简单单 Online zuozuo 简简单单 Online zuozuo 简简单单 Online zuozuo 简简单单 Online zuozuo :本心、输入输出、结果 简简单单 Online zuozuo : 文章目录 系统架构设计师: 信息安全技术前言信息安全的基本要素:信息安全的范围:安全措施的目标:访问控制技术要素:访问控制包括:等保

前端技术(七)——less 教程

一、less简介 1. less是什么? less是一种动态样式语言,属于css预处理器的范畴,它扩展了CSS语言,增加了变量、Mixin、函数等特性,使CSS 更易维护和扩展LESS 既可以在 客户端 上运行 ,也可以借助Node.js在服务端运行。 less的中文官网:https://lesscss.cn/ 2. less编译工具 koala 官网 http://koala-app.

Spring的设计⽬标——《Spring技术内幕》

读《Spring技术内幕》第二版,计文柯著。 如果我们要简要地描述Spring的设计⽬标,可以这么说,Spring为开发者提供的是⼀个⼀站式的轻量级应⽤开发框架(平台)。 作为平台,Spring抽象了我们在 许多应⽤开发中遇到的共性问题;同时,作为⼀个轻量级的应⽤开发框架,Spring和传统的J2EE开发相⽐,有其⾃⾝的特点。 通过这些⾃⾝的特点,Spring充分体现了它的设计理念:在

java线程深度解析(六)——线程池技术

http://blog.csdn.net/Daybreak1209/article/details/51382604 一种最为简单的线程创建和回收的方法: [html]  view plain copy new Thread(new Runnable(){                @Override               public voi

java线程深度解析(二)——线程互斥技术与线程间通信

http://blog.csdn.net/daybreak1209/article/details/51307679      在java多线程——线程同步问题中,对于多线程下程序启动时出现的线程安全问题的背景和初步解决方案已经有了详细的介绍。本文将再度深入解析对线程代码块和方法的同步控制和多线程间通信的实例。 一、再现多线程下安全问题 先看开启两条线程,分别按序打印字符串的

SSM项目使用AOP技术进行日志记录

本步骤只记录完成切面所需的必要代码 本人开发中遇到的问题: 切面一直切不进去,最后发现需要在springMVC的核心配置文件中中开启注解驱动才可以,只在spring的核心配置文件中开启是不会在web项目中生效的。 之后按照下面的代码进行配置,然后前端在访问controller层中的路径时即可观察到日志已经被正常记录到数据库,代码中有部分注释,看不懂的可以参照注释。接下来进入正题 1、导入m