AOP的底层实现--ASM

2023-10-27

在ASM的核心组件中,Opcodes接口定义了一些常量,尤其是版本号、访问标识符、字节码等信息。ClassReader用于读取Class文件,它的作用时进行Class文件的解析,并可以接受一个ClassVisitor,ClassReader会将解析过程中产生的类的部分信息,比如访问提示符、字段、方法逐个送入ClassVisitor,Class Visitor在接受到对应的类信息后,可以进行各自的处理。

ClassVisitor有一个重要的子类为ClassWriter,它负责进行Class文件的输出和形成。ClassVisitor在进行字段和方法的处理的时候,会委托给FieldVisitor和MethodVisitor进行处理。因此在类的处理过程中,会创建相应的FieldVisitor和MethodVisitor对象。FieldVisitor和FieldVisitor也各自有一个重要的子类,FieldWriter和MethodWriter。当classWriter进行字段和方法的处理时,也是依赖这两个类进行的。

示例1--ASM入门helloworld

/**
 * author:yinweicheng
 */
package edu.hrbeu.asm;

import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

import java.lang.reflect.InvocationTargetException;

/**
 * 继承classloader,方便立即被系统加载
 * 实现Opcodes,方便访问全局变量
 */
public class AsmHelloWorld extends ClassLoader implements Opcodes{
   /**
    *
    * @param args 程序参数
    * @throws InvocationTargetException 类加载异常
    * @throws IllegalAccessException 初始化实例失败
    */
   public static void main(final String[] args) throws InvocationTargetException, IllegalAccessException {
      ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
      cw.visit(V1_7, ACC_PUBLIC, "Example", null, "java/lang/Object", null);
      //设置类的基本信息
      MethodVisitor mw = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
      //生成example的构造函数
      mw.visitVarInsn(ALOAD, 0);
      mw.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
      mw.visitInsn(RETURN);
      mw.visitMaxs(0, 0);
      mw.visitEnd();
      //生成main主函数
      mw = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
      mw.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
      mw.visitLdcInsn("hello world!");
      mw.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V");
      mw.visitInsn(RETURN);
      mw.visitMaxs(0,0);
      mw.visitEnd();
      //生成class文件流的二进制表示
      byte[] code = cw.toByteArray();
      //将生成的文件流载入系统,并通过反射调用main方法
      AsmHelloWorld loader = new AsmHelloWorld();
      Class exampleClass = loader.defineClass("Example", code,0, code.length);
      exampleClass.getMethods()[0].invoke(null, new Object[]{null});
   }
}

既然我们可以通过asm生成字节码文件,我们当然能够通过asm重构class文件。例如当前有一个账户account,要想在不修改account的前提下,对用户先进行安全检查,然后在进行操作。

示例--asm实现面向切面编程

account:

 
/**
 * author:lenovo
 * create_date:2018/07/11
 * project:asm
 **/
package edu.hrbeu.asm;

/**
 *
 */
public class Account {
   /**
    *
    */
   public void operation() throws InterruptedException {
      Thread.sleep(1000);
      System.out.println("operation...");
   }
}

检查类:

/**
 * author:lenovo
 * create_date:2018/07/11
 * project:asm
 **/
package edu.hrbeu.asm;

/**
 * class_name:SecurityChecker
 * usage: 对account进行安全检查
 **/
public class SecurityChecker {
   /**
    * 进行安全检查的方法
    *
    * @return 是否安全
    */
   public static boolean checkSecurity() {
      System.out.println("SecurityChecker.checkSercurity...");
      if ((System.currentTimeMillis() & 0x1) == 0) {
         System.out.println("check fail");
         return false;
      } else
         return true;
   }
}

重构方法:

/**
 * author:lenovo
 * create_date:2018/07/12
 * project:asm
 **/
package edu.hrbeu.asm;

import jdk.internal.org.objectweb.asm.Label;
import jdk.internal.org.objectweb.asm.MethodVisitor;
import jdk.internal.org.objectweb.asm.Opcodes;

/**
 * class_name:AddSecurityCheckMethodAdapter
 * usage: 添加方法检查,将检查方法添加到运行方法上
 **/
public class AddSecurityCheckMethodAdapter extends MethodVisitor {
   /**
    * 继承构造方法
    *
    * @param mv :methodvisitor
    */
   public AddSecurityCheckMethodAdapter(final MethodVisitor mv) {
      super(Opcodes.ASM5, mv);
   }

   public void visitCode() {
      Label continueLabel = new Label();
      visitMethodInsn(Opcodes.INVOKESTATIC, "edu/hrbeu/asm/SecurityChecker", "checkSecurity", "()Z");
      visitJumpInsn(Opcodes.IFNE, continueLabel);
      visitInsn(Opcodes.RETURN);
      visitLabel(continueLabel);
      super.visitCode();
   }
}
/**
 * author:lenovo
 * create_date:2018/07/11
 * project:asm
 **/
package edu.hrbeu.asm;

import jdk.internal.org.objectweb.asm.ClassVisitor;
import jdk.internal.org.objectweb.asm.MethodVisitor;
import jdk.internal.org.objectweb.asm.Opcodes;

/**
 * class_name:AddSecurityCheckClassAdapter
 * useage: 添加类检查,重构运行方法
 **/
public class AddSecurityCheckClassAdapter extends ClassVisitor {
   /**
    * 继承构造方法
    *
    * @param classVisitor :定义类观察器
    */
   public AddSecurityCheckClassAdapter(final ClassVisitor classVisitor) {
      super(Opcodes.ASM5, classVisitor);
   }

   /**
    * @param access     : 访问标志
    * @param name       : 名称索引
    * @param desc       :描述符索引
    * @param signature  :标志位
    * @param exceptions :异常表
    * @return :返回MethodVisitor
    */
   public MethodVisitor visitMethod(final int access, final String name, final String desc,
                                    final String signature, final String[] exceptions) {
      MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
      MethodVisitor wrappedMv = mv;
      if (mv != null) {
         if (name.equals("operation")) {
            wrappedMv = new AddSecurityCheckMethodAdapter(mv);
         }
      }
      return wrappedMv;
   }
}
/**
 * author:lenovo
 * create_date:2018/07/11
 * project:asm
 **/
package edu.hrbeu.asm;

import jdk.internal.org.objectweb.asm.ClassReader;
import jdk.internal.org.objectweb.asm.ClassWriter;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * class_name:SecurityWeaveGenerator
 * usage:
 **/
public class SecurityWeaveGenerator {
   /**
    * 通过asm重构检查类account的class文件,IDEA的class文件路径与eclipse路径不同
    *
    * @param args args
    * @throws IOException 读写异常
    */
   public static void main(final String[] args) throws IOException {
      String classname = Account.class.getName();
      ClassReader cr = new ClassReader(classname);
      ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
      AddSecurityCheckClassAdapter classAdapter = new AddSecurityCheckClassAdapter(cw);
      cr.accept(classAdapter, ClassReader.SKIP_DEBUG);
      byte[] data = cw.toByteArray();
      File file = new File("out/production/asm/" + classname.replaceAll("\\.", "/") + ".class");
      FileOutputStream fout = new FileOutputStream(file);
      fout.write(data);
      fout.close();
   }
}

在对account的class文件进行重构后,在运行operation方法之前首先要运行checkSecurity方法,这样创建一个演示类运行operation方法查看输入结果。

/**
 * author:lenovo
 * create_date:2018/07/12
 * project:asm
 **/
package edu.hrbeu.asm;

/**
 * class_name:RunAccountMain
 * useage:
 **/
public class RunAccountMain {
   /**
    * @param args
    */
   public static void main(String[] args) {
      Account account = new Account();
      account.operation();
   }
}

输出结果:

D:\jdk1.8\bin\java.exe "-javaagent:E:\IDEA\IntelliJ IDEA 2018.1.2\lib\idea_rt.jar=10556:E:\IDEA\IntelliJ IDEA 2018.1.2\bin" -Dfile.encoding=UTF-8 -classpath D:\jdk1.8\jre\lib\charsets.jar;D:\jdk1.8\jre\lib\deploy.jar;D:\jdk1.8\jre\lib\ext\access-bridge-64.jar;D:\jdk1.8\jre\lib\ext\cldrdata.jar;D:\jdk1.8\jre\lib\ext\dnsns.jar;D:\jdk1.8\jre\lib\ext\jaccess.jar;D:\jdk1.8\jre\lib\ext\jfxrt.jar;D:\jdk1.8\jre\lib\ext\localedata.jar;D:\jdk1.8\jre\lib\ext\nashorn.jar;D:\jdk1.8\jre\lib\ext\sunec.jar;D:\jdk1.8\jre\lib\ext\sunjce_provider.jar;D:\jdk1.8\jre\lib\ext\sunmscapi.jar;D:\jdk1.8\jre\lib\ext\sunpkcs11.jar;D:\jdk1.8\jre\lib\ext\zipfs.jar;D:\jdk1.8\jre\lib\javaws.jar;D:\jdk1.8\jre\lib\jce.jar;D:\jdk1.8\jre\lib\jfr.jar;D:\jdk1.8\jre\lib\jfxswt.jar;D:\jdk1.8\jre\lib\jsse.jar;D:\jdk1.8\jre\lib\management-agent.jar;D:\jdk1.8\jre\lib\plugin.jar;D:\jdk1.8\jre\lib\resources.jar;D:\jdk1.8\jre\lib\rt.jar;D:\program2015\asm\out\production\asm;D:\program2015\asm\src\asm-all-3.3.1.jar;D:\program2015\asm\src\cglib-2.1.95.jar;D:\program2015\asm\src\org.objectweb.asm-3.2.0.jar edu.hrbeu.asm.RunAccountMain
SecurityChecker.checkSercurity...
operation...

Process finished with exit code 0

可以看到,在运行operation方法前首先运行了checkSecurity方法。相当于aop中的前置通知,那么怎么实现环绕通知呢?假设要对用户的操作时间进行统计,怎么在不修改account的基础上进行呢?

添加计算时间类:

/**
 * author:lenovo
 * create_date:2018/07/12
 * project:asm
 **/
package edu.hrbeu.surround;

/**
 * class_name:TimeStat
 * usage:
 **/
public class TimeStat {
   /**
    * 计时
    */
   static ThreadLocal<Long> t = new ThreadLocal<Long>();

   /**
    * 设置开始时间
    */
   public static void start() {
      t.set(System.currentTimeMillis());
   }

   /**
    *输出结束时间
    */
   public static void end() {
      long time = System.currentTimeMillis() - t.get();
      System.out.println(Thread.currentThread().getStackTrace()[2] + "spend:" + time);
   }
}
/**
 * author:lenovo
 * create_date:2018/07/12
 * project:asm
 **/
package edu.hrbeu.surround;

import jdk.internal.org.objectweb.asm.ClassVisitor;
import jdk.internal.org.objectweb.asm.MethodVisitor;
import jdk.internal.org.objectweb.asm.Opcodes;

/**
 * class_name:TimeStatClassAdapter
 * usage:
 **/
public class TimeStatClassAdapter extends ClassVisitor {

   public TimeStatClassAdapter(ClassVisitor classVisitor) {
      super(Opcodes.ASM5, classVisitor);
   }

   public MethodVisitor visitMethod(final int access, final String name, final String desc,
                                    final String signature, final String[] exceptions) {
      MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
      MethodVisitor wrappedMv = mv;
      if (mv != null) {
         if (name.equals("operation")) {
            wrappedMv = new TimeStatMethodAdapter(mv);
         }
      }
      return wrappedMv;
   }
}
/**
 * author:lenovo
 * create_date:2018/07/12
 * project:asm
 **/
package edu.hrbeu.surround;

import jdk.internal.org.objectweb.asm.MethodVisitor;
import jdk.internal.org.objectweb.asm.Opcodes;

/**
 * class_name:TimeStatMethodAdapter
 * usage:
 **/
public class TimeStatMethodAdapter extends MethodVisitor implements Opcodes {
   public TimeStatMethodAdapter(MethodVisitor mv) {
      super(Opcodes.ASM5, mv);
   }

   public void visitCode() {
      visitMethodInsn(Opcodes.INVOKESTATIC, "edu/hrbeu/surround/TimeStat", "start", "()V");
      super.visitCode();
   }

   public void visitInsn(int opcode) {
      if (opcode >= IRETURN && opcode <= RETURN) {
         visitMethodInsn(Opcodes.INVOKESTATIC, "edu/hrbeu/surround/TimeStat", "end", "()V");
      }
      mv.visitInsn(opcode);
   }
}

修改weaveGenerate类,重新覆写class文件,运行后的输出结果为:

D:\jdk1.8\bin\java.exe "-javaagent:E:\IDEA\IntelliJ IDEA 2018.1.2\lib\idea_rt.jar=11956:E:\IDEA\IntelliJ IDEA 2018.1.2\bin" -Dfile.encoding=UTF-8 -classpath D:\jdk1.8\jre\lib\charsets.jar;D:\jdk1.8\jre\lib\deploy.jar;D:\jdk1.8\jre\lib\ext\access-bridge-64.jar;D:\jdk1.8\jre\lib\ext\cldrdata.jar;D:\jdk1.8\jre\lib\ext\dnsns.jar;D:\jdk1.8\jre\lib\ext\jaccess.jar;D:\jdk1.8\jre\lib\ext\jfxrt.jar;D:\jdk1.8\jre\lib\ext\localedata.jar;D:\jdk1.8\jre\lib\ext\nashorn.jar;D:\jdk1.8\jre\lib\ext\sunec.jar;D:\jdk1.8\jre\lib\ext\sunjce_provider.jar;D:\jdk1.8\jre\lib\ext\sunmscapi.jar;D:\jdk1.8\jre\lib\ext\sunpkcs11.jar;D:\jdk1.8\jre\lib\ext\zipfs.jar;D:\jdk1.8\jre\lib\javaws.jar;D:\jdk1.8\jre\lib\jce.jar;D:\jdk1.8\jre\lib\jfr.jar;D:\jdk1.8\jre\lib\jfxswt.jar;D:\jdk1.8\jre\lib\jsse.jar;D:\jdk1.8\jre\lib\management-agent.jar;D:\jdk1.8\jre\lib\plugin.jar;D:\jdk1.8\jre\lib\resources.jar;D:\jdk1.8\jre\lib\rt.jar;D:\program2015\asm\out\production\asm;D:\program2015\asm\src\asm-all-3.3.1.jar;D:\program2015\asm\src\cglib-2.1.95.jar;D:\program2015\asm\src\org.objectweb.asm-3.2.0.jar edu.hrbeu.asm.RunAccountMain
operation...
edu.hrbeu.asm.Account.operation(Unknown Source)spend:1000

Process finished with exit code 0

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

AOP的底层实现--ASM 的相关文章

随机推荐

  • Flutter 设置状态栏statusbar的背景颜色和文字颜色

    今天介绍如何设置状态栏的颜色和文字的颜色 本文采用通过在appbar里面的一个参数来设置 方法如下 可以定义一个基类 在基类之中进行整体的设定 也可以暴露方法 动态设置核心就是两行 很简单 return MaterialApp debugS
  • nginx负载配置,文件大小限制

    2019独角兽企业重金招聘Python工程师标准 gt gt gt user nobody worker processes 1 error log logs error log error log logs error log notic
  • Spring Data 与MongoDB 集成一:入门篇(开发环境搭建和简单CRUD)

    一 简介 SpringData 项目提供与MongoDB文档数据库的集成 二 SpringData 与MongoDB 配置及对应Jar包 1 安装mongodb 数据库 请查考 待补充 2 下载spring data 关联两个子项目 spr
  • 洛谷千题详解

    博主主页 Yu 仙笙 专栏地址 洛谷千题详解 目录 题目描述 输入格式 输出格式 输入输出样例 解析 C 源码 C 源码2 C 源码3 Pascal源码 Java源码
  • 设计模式--介绍

    一 什么是设计模式 设计模式 Design pattern 是一套被反复使用 多数人知晓的 经过分类编目的 代码设计经验的总结 使用设计模式是为了可重用代码 让代码更容易被他人理解 保证代码可靠性 毫无疑问 设计模式于己于他人于系统都是多赢
  • 软件开发的4种模型和4种方法

    软件开发模型 1 瀑布模型 适合需求明确的软件开发 2 演化模型 获取一组基本需求 快速给出版本 成为原型 用于对软件需求缺乏准认知的情况 不需要明确的需求 3 螺旋模型 结合瀑布模型和演化模型 综合两者优点 并增加风险分析 螺旋模型包括四
  • java base64转字图片、图片转base64字符串

    实现代码如下 Slf4j public class Base64ToFileImageTools base64字符串转化成图片 param imgData 图片编码 param imgFilePath 存放到本地路径 return thro
  • 2021 全球人工智能技术创新大赛 医学影像报告异常检测 TOP4方案

    2021 全球人工智能技术创新大赛 医学影像报告异常检测 TOP4方案 1 写在前面 在本次全球人工智能技术创新大赛赛道一的比赛中 我们团队水煮毛血旺以初赛第三 复赛第四的成绩进入了决赛 最终决赛排名第四 本文主要想记录一下这次比赛的方案以
  • docker创建python、jdk环境并保存镜像,运行容器

    在本地任一台可联网服务器上 创建容器 1 首先确定系统的版本 拉取镜像 docker pull centos 7 4 1708 2 创建容器 docker run i t centos 7 4 1708 bin bash 3 在容器中安装所
  • Quartus18.1 lite免费教育版下载及安装

    目录 一 下载 1 首先注册intel官网账号 2 进入下载界面找到Quartus18 1教育版 3 下载项目 二 安装 一 下载 intel官网 www intel cn 1 首先注册intel官网账号 随便在官网注册个账号 国内的邮箱也
  • 如何快速做一个微信自动拉群机器人 足够简单 足够粗暴

    wechaty 首先 wechaty了解一下 文档链接 https docs chatie io v zh 只需要6行代码 你就可以通过个人号搭建一个微信机器人功能 用来自动管理微信消息 是不是很简单很粗暴 const Wechaty re
  • STM32 HAL库 时钟芯片RX8025T IIC的读写操作,入过的坑

    我使用STM32本身的IIC外设 与时钟芯片RX8025T进行通信 时钟芯片RX8025特点 高精度 据说每个月时钟误差在1秒以内 做流量计这些需要精密控时的东西 完全够用了 一 时钟芯片RX8025T简单说明 重要 RX8025芯片有二种
  • 图论模型(Dijkstra算法和Floyd算法)

    图论模型 图论模型 Dijkstra算法 概念 带权邻接矩阵 代码 操作 Floyd算法 概念 代码 操作 Dijkstra算法 概念 Dijkstra算法能求一个顶点到另一顶点最短路径 它是由Dijkstra于1959年提出的 实际它能给
  • C++之获取网卡物理地址(MAC)

    ConsoleApplication1 cpp 定义控制台应用程序的入口点 include stdafx h include
  • QT5.15离线安装

    在线安装后 将所有的资源打成压缩包 拷贝至另外一台电脑 进行如下设置 1 解压 2 进入路径 QT Tools sdktool share qtcreator QtProject 3 修改QtCreator ini 将所有的绝对路径修改为实
  • 搜狐新闻算法原理

    转载 搜狐新闻推荐算法原理 导读 在当前这个移动互联网时代 各种信息内容爆炸 面对海量数据 用户希望在有限的时间和空间内 找到自己感兴趣的内容 这就是推荐需要解决的问题 接下来主要讲解新闻推荐的算法原理 01 新闻推荐算法架构 新闻算法的核
  • Python招聘网站爬虫:从招聘网站获取职位信息进行职位搜索和分析的完整指南

    目录 1 简介 1 1 什么是招聘网站爬虫 1 2 爬虫的法律和道德问题 2 准备工作
  • 第二个重要极限的证明 e怎么出来的

    第二个重要极限的证明 e怎么出来的 具体步骤是什么啊 匿名 浏览 4402 次 推荐于2017 05 24 13 27 43 最佳答案 1 对于数列 重要极限的 e 是定义出来的 2 对于函数 重要极限的 e 是推导出来的 请楼主耐心参看下
  • 【VAR

    以美国 GDP 和通货膨胀数据为例 1 数据集 下载数据我们需要从 FRED 数据库下载美国 GDP 和通货膨胀数据 并将它们存储在 CSV 文件中 可以在 FRED 网站 https fred stlouisfed org 搜索并下载需要
  • AOP的底层实现--ASM

    在ASM的核心组件中 Opcodes接口定义了一些常量 尤其是版本号 访问标识符 字节码等信息 ClassReader用于读取Class文件 它的作用时进行Class文件的解析 并可以接受一个ClassVisitor ClassReader