本文转载自ASM的基础用法

导语

新闻里使用的热补丁修复方案是基于AspectJ,AspectJ是AOP的一种实现。

无意接触到一种小巧轻便的Java字节码操控框架ASM,它也能方便地生成和改造Java代码。

本文主要分为几个部分:

  1. 什么是ASM;
  2. 为什么要动态生成Java类;
  3. 为什么选择ASM;
  4. ASM中的核心类和核心方法;
  5. ASM示例;

什么是ASM?

ASM是一个Java字节码操控框架。它能被用来动态生成类或者增强既有类的功能。ASM可以直接产生二进制 class文件,也可以在类被加载入Java虚拟机之前动态改变类行为。

如果想了解Java虚拟机的工作过程可参考JVM原理浅析

为什么要动态生成Java类?

举个例子,目前有一个既有的银行管理系统,包括Bank、Customer、Account、Invoice等对象,现在要加入一个安全检查模块,对已有类的所有操作之前都必须进行一次安全检查。

然而 Bank、Customer、Account、Invoice 是代表不同的事务,派生自不同的父类,很难在高层上加入关于Security Checker的共有功能。对于没有多继承的Java来说,更是如此。

传统解决方案是使用装饰器模式,装饰器模式动态的将责任链附加到对象上,若要扩展功能,装饰者提供了比继承更加富有弹性的代替方案

装饰器模式可以在一定程度上改善耦合,而功能仍旧是分散的,每个需要Security Checker的类都必须派生一个Decorator,每个需要Security Checker的方法都要被包装(wrap)。

下面我们以 Account类为例看一下Decorator:

首先,有一个SecurityChecker类,其静态方法checkSecurity执行安全检查功能:

public class SecurityChecker {

    public static void checkSecurity() {
System.out.println("SecurityChecker.checkSecurity ...");
}
}

另一个是Account类:

public class Account {

    public void operation() {
System.out.println("operation...");
}
}

若想对operation加入对SecurityCheck.checkSecurity()调用,标准的Decorator需要先定义一个 Account类的接口:

public interface IAccount {

    void operation();
}

然后定义一个实现类:

public class AccountImpl implements IAccount {
@Override
public void operation() {
System.out.println("operation...");
}
}

定义一个AccountImpl类的Decorator,并包装operation方法:

public class AccountWithSecurityCheck implements IAccount {
private IAccount account; public AccountWithSecurityCheck(IAccount IAccount) {
this.account = IAccount;
} public void operation() {
SecurityChecker.checkSecurity();
account.operation();
}
}

最后的调用方式为:

public class Test {

    public static void main(String[] args) throws Exception {

        // 1.使用包装类
AccountWithSecurityCheck account = new AccountWithSecurityCheck(new AccountImpl());
account.operation();
}
}

在这个简单的例子里,改造一个类的一个方法还好,如果是变动整个模块,Decorator很快就会演化成另一个噩梦。动态改变Java类就是要解决AOP的问题,提供一种得到系统支持的可编程的方法,自动化地生成或者增强Java代码。

为什么选择ASM?

最直接的改造Java类的方法莫过于直接改写class文件。Java 规范详细说明了class文件的格式,直接编辑字节码确实可以改变Java类的行为。

还有一种比较理想且流行的方式是是使用java.lang.reflect.Proxy。我们仍旧使用以上的例子,给Account类加上checkSecurity功能。

首先,Proxy编程是面向接口的,Proxy并不负责实例化对象,和Decorator模式一样,要把Account定义成一个接口,然后在AccountImpl里实现Account接口,接着实现一个InvocationHandlerAccount方法被调用的时候,虚拟机都会实际调用这个InvocationHandler的invoke方法:

public class SecurityProxyInvocationHandler implements InvocationHandler {

    private Object proxyedObject;

    public SecurityProxyInvocationHandler(Object o) {
proxyedObject = o;
} @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (proxy instanceof IAccount && "operation".equals(method.getName())) {
SecurityChecker.checkSecurity();
}
return method.invoke(proxyedObject, args);
}
}

最后,在应用程序中指定InvocationHandler生成代理对象:

public class Test {

    public static void main(String[] args) throws Exception {

        // 2.使用代理
IAccount account = (IAccount) Proxy.newProxyInstance(
IAccount.class.getClassLoader(),
new Class[]{IAccount.class},
new SecurityProxyInvocationHandler(new AccountImpl()));
account.operation();
}
}

其不足之处在于:

  • Proxy是面向接口的,所有使用Proxy的对象都必须定义一个接口,而且用这些对象的代码也必须是对接口编程的:Proxy生成的对象是接口一致的而不是对象一致的:例子中Proxy.newProxyInstance生成的是实现IAccount接口的对象而不是AccountImpl的子类。这对于软件架构设计,尤其对于既有软件系统是有一定掣肘的。
  • Proxy毕竟是通过反射实现的,必须在效率上付出代价:有实验数据表明,调用反射比一般的函数开销至少要大10倍。而且,从程序实现上可以看出,对proxy class的所有方法调用都要通过使用反射的invoke方法。因此,对于性能关键的应用,使用proxy class是需要精心考虑的,以避免反射成为整个应用的瓶颈。

ASM能够通过改造既有类,直接生成需要的代码。增强的代码是硬编码在新生成的类文件内部的,没有反射带来性能上的付出。同时,ASM与Proxy编程不同,不需要为增强代码而新定义一个接口,生成的代码可以覆盖原来的类,或者是原始类的子类。它是一个普通的Java类而不是Proxy类,甚至可以在应用程序的类框架中拥有自己的位置,派生自己的子类。

ASM使用

使用javap -c命令查看Account类的字节码

5-9行表示的是一个的默认构造方法,是编译器为我们自动添加的。参考来自深入字节码 – 使用 ASM 实现 AOP

11-结束表示我们编写的operation方法。

aload_0:这个指令是LOAD系列指令中的一个,它的意思表示装载当前第0个元素到堆栈中。代码上相当于“this”。

invokespecial:这个指令是调用系列指令中的一个。其目的是调用对象类的方法。后面需要给上父类的方法完整签名。“#1”的意思是class文件常量表中第1个元素。值为:“java/lang/Object.””

ASM的基础用法的更多相关文章

  1. Golang 汇编asm语言基础学习

    Golang 汇编asm语言基础学习 一.CPU 基础知识 cpu 内部结构 cpu 内部主要是由寄存器.控制器.运算器和时钟四个部分组成. 寄存器:用来暂时存放指令.数据等对象.它是一个更快的内存. ...

  2. PropertyGrid控件由浅入深(二):基础用法

    目录 PropertyGrid控件由浅入深(一):文章大纲 PropertyGrid控件由浅入深(二):基础用法 控件的外观构成 控件的外观构成如下图所示: PropertyGrid控件包含以下几个要 ...

  3. logstash安装与基础用法

    若是搭建elk,建议先安装好elasticsearch 来自官网,版本为2.3 wget -c https://download.elastic.co/logstash/logstash/packag ...

  4. elasticsearch安装与基础用法

    来自官网,版本为2.3 注意elasticsearch依赖jdk,2.3依赖jdk7 下载rpm包并安装 wget -c https://download.elastic.co/elasticsear ...

  5. BigDecimal最基础用法

    BigDecimal最基础用法 用字符串生成的BigDecimal是不会丢精度的. 简单除法. public class DemoBigDecimal { public static void mai ...

  6. Vue组件基础用法

    前面的话 组件(Component)是Vue.js最强大的功能之一.组件可以扩展HTML元素,封装可重用的代码.根据项目需求,抽象出一些组件,每个组件里包含了展现.功能和样式.每个页面,根据自己所需, ...

  7. Smarty基础用法

    一.Smarty基础用法: 1.基础用法如下 include './smarty/Smarty.class.php';//引入smarty类 $smarty = new Smarty();//实例化s ...

  8. 前端自动化测试神器-Katalon的基础用法

    前言 最近由于在工作中需要通过Web端的功能进行一次大批量的操作,数据量大概在5000左右,如果手动处理, 完成一条数据的操作用时在20秒左右的话,大概需要4-5个人/天的工作量(假设一天8小时的工作 ...

  9. Bootstrap fileinput:文件上传插件的基础用法

    官网地址:http://plugins.krajee.com/ 官网提供的样例:http://plugins.krajee.com/file-input/demo 基础用法一 导入核心CSS及JS文件 ...

随机推荐

  1. 源码剖析ThreadPoolExecutor线程池及阻塞队列

    本文章对ThreadPoolExecutor线程池的底层源码进行分析,线程池如何起到了线程复用.又是如何进行维护我们的线程任务的呢?我们直接进入正题: 首先我们看一下ThreadPoolExecuto ...

  2. 一次小模块的使用过程-LC12S无线模块介绍

    前言: 最近帮人做了个小设备,使用了无线模块.触摸芯片,主要功能就是把触摸按键的信号无线传到控制继电器输出,MCU是STM8系列的芯片,其中使用过程中调试无线模块LC21S觉得挺好用的,就写了这篇文章 ...

  3. 2019 Multi-University Training Contest 2 I.I Love Palindrome String(回文自动机+字符串hash)

    Problem Description You are given a string S=s1s2..s|S| containing only lowercase English letters. F ...

  4. java大数函数(附官方文档链接)

    java文档 字段摘要 static BigInteger ONE           BigInteger 的常量 1. static BigInteger TEN           BigInt ...

  5. codeforces644B. Processing Queries (模拟)

    In this problem you have to simulate the workflow of one-thread server. There are n queries to proce ...

  6. Educational Codeforces Round 95 (Rated for Div. 2) B. Negative Prefixes (贪心,构造)

    题意:给你一串长度为\(n\)的序列,有的位置被锁上了,你可以对没锁的位置上的元素任意排序,使得最后一个\(\le0\)的前缀和的位置最小,求重新排序后的序列. 题解:贪心,将所有能动的位置从大到小排 ...

  7. css整理之-----------选择器

    背景 在20年初时总感觉自己的css 不够用,想把css 相关的东西整理下,去年一整年都比较忙,忙着就到2021了,今天趁着有点时间,先从选择器开始吧. 听说图片可以提升颜值.... 选择器 CSS选 ...

  8. 创建java文件和注释

    创建java文件和注释 一 创建java文件 在文件夹里创建txt文本文件,后将格式改为.java, 输入 1 public class Hello{ 2 public static void mai ...

  9. MySQL 索引的类型

    主键索引(PRIMARY KEY) # 主键 = 唯一键索引 + 非空 + 只能设置一个字段 # 创建表的时候创建主键索引 mysql> create table test(id int not ...

  10. 生成不带签名(BOM)的UTF8格式的XML

    生成XML的一种方法如下: using System.Xml; private void SaveXML(string savePath) { XmlWriterSettings setting = ...