ASM的基础用法
本文转载自ASM的基础用法
导语
新闻里使用的热补丁修复方案是基于AspectJ,AspectJ是AOP的一种实现。
无意接触到一种小巧轻便的Java字节码操控框架ASM,它也能方便地生成和改造Java代码。
本文主要分为几个部分:
- 什么是ASM;
- 为什么要动态生成Java类;
- 为什么选择ASM;
- ASM中的核心类和核心方法;
- 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的基础用法的更多相关文章
- Golang 汇编asm语言基础学习
Golang 汇编asm语言基础学习 一.CPU 基础知识 cpu 内部结构 cpu 内部主要是由寄存器.控制器.运算器和时钟四个部分组成. 寄存器:用来暂时存放指令.数据等对象.它是一个更快的内存. ...
- PropertyGrid控件由浅入深(二):基础用法
目录 PropertyGrid控件由浅入深(一):文章大纲 PropertyGrid控件由浅入深(二):基础用法 控件的外观构成 控件的外观构成如下图所示: PropertyGrid控件包含以下几个要 ...
- logstash安装与基础用法
若是搭建elk,建议先安装好elasticsearch 来自官网,版本为2.3 wget -c https://download.elastic.co/logstash/logstash/packag ...
- elasticsearch安装与基础用法
来自官网,版本为2.3 注意elasticsearch依赖jdk,2.3依赖jdk7 下载rpm包并安装 wget -c https://download.elastic.co/elasticsear ...
- BigDecimal最基础用法
BigDecimal最基础用法 用字符串生成的BigDecimal是不会丢精度的. 简单除法. public class DemoBigDecimal { public static void mai ...
- Vue组件基础用法
前面的话 组件(Component)是Vue.js最强大的功能之一.组件可以扩展HTML元素,封装可重用的代码.根据项目需求,抽象出一些组件,每个组件里包含了展现.功能和样式.每个页面,根据自己所需, ...
- Smarty基础用法
一.Smarty基础用法: 1.基础用法如下 include './smarty/Smarty.class.php';//引入smarty类 $smarty = new Smarty();//实例化s ...
- 前端自动化测试神器-Katalon的基础用法
前言 最近由于在工作中需要通过Web端的功能进行一次大批量的操作,数据量大概在5000左右,如果手动处理, 完成一条数据的操作用时在20秒左右的话,大概需要4-5个人/天的工作量(假设一天8小时的工作 ...
- Bootstrap fileinput:文件上传插件的基础用法
官网地址:http://plugins.krajee.com/ 官网提供的样例:http://plugins.krajee.com/file-input/demo 基础用法一 导入核心CSS及JS文件 ...
随机推荐
- Django(中间件)
中间件的概念 中间件顾名思义,是介于request与response处理之间的一道处理过程,相对比较轻量级,并且在全局上改变django的输入与输出.因为改变的是全局,所以需要谨慎实用,用不好会影响到 ...
- Docker及其使用思维导图
学习Docker的使用. 包括Docker的一些命令,Docker容器的使用,Docker镜像的使用,Docker容器连接等内容. 各种思维导图下载地址 Docker使用思维导图及各各种命令 Dock ...
- linux系统资源限制———ulimit命令
简介 Linux ulimit命令用于控制shell程序的资源. ulimit为shell内建指令,可用来控制shell执行程序的资源 推荐:https://blog.csdn.net/skiwnc/ ...
- Cisco动态路由(rip)
接Cisco静态路由,讨论一下Cisco动态路由. 实验环境布置 命令布置动态路由 Router0: Router>enable Router#configure terminal Router ...
- 静态代理和jdk动态代理
要说动态代理,必须先聊聊静态代理. 静态代理 假设现在项目经理有一个需求:在项目现有所有类的方法前后打印日志. 你如何在不修改已有代码的前提下,完成这个需求? 我首先想到的是静态代理.具体做法是: 1 ...
- C# 使用PictureBox实现图片按钮控件
引言 我们有时候会在程序的文件夹里看见一些图标,而这些图标恰好是作为按钮的背景图片来使用的.鼠标指针在处于不同状态时,有"进入按钮"."按下左键"," ...
- Codeforces Round #604 (Div. 2) B. Beautiful Numbers(双指针)
题目链接:https://codeforces.com/contest/1265/problem/B 题意 给出大小为 $n$ 的一个排列,问对于每个 $i(1 \le i \le n)$,原排列中是 ...
- poj1787 Charlie's Change
Description Charlie is a driver of Advanced Cargo Movement, Ltd. Charlie drives a lot and so he ofte ...
- Kubernets二进制安装(18)之安装部署Heapster
1.下载heapster镜像 在运维主机(mfyxw50.mfyxw.com)执行 [root@mfyxw50 ~]# docker pull quay.io/bitnami/heapster:1.5 ...
- 关于st表的推导
#include <bits/stdc++.h> using namespace std; const int maxn=1e6+7; int st[maxn][32]; int a[ma ...