作者:董子龍
前言
記得那是2022年秋天的第一場雨,比2021年來的稍晚一些,在那個秋雨朦胧的下午,正在工位上奮筆疾書的我突然聽到了前面波哥對着手機聽筒說出來的"溫柔"的話語:說說你了解的spring-aop。話音剛落,aop這三個字便猶如一把利劍一樣狠狠的紮到了我的心上,讓我的腦海中頓時浮現了當年剛剛畢業被面試官"蹂躏"的凄慘畫面。曆經多年,直至現在,雖然日常工作中經常使用aop做一些業務功能的開發,但是如果讓我解釋"面向切面"這四個字的意思,我還是會"十臉懵逼",哈哈。那麼今天的文章,作為位元組碼增強技術系列承上啟下的第二篇,就讓我們以aop為馬,去追逐位元組碼的星光。
一、SpringAop與Cglib
1.1、aop重要概念
1.2、實作原了解析
Spring AOP的實作原理是基于動态織入的動态代理技術,而動态代理技術又分為Java JDK動态代理和CGLIB動态代理。具體使用哪一種需要根據 AopProxyFactory 接口的 createProxy 方法中的 AdvisedSupport 中的參數進行确定,預設情況下如果目标類是接口,則使用 jdk 動态代理技術,如果是非接口類,則使用 cglib 來生成代理。具體的源碼就不給大家一一貼出來了,大家可以去網上搜尋一些資料進行深入了解。這裡隻給大家貼一張生成代理對象的圖:
1.2.1、jdk動态代理
spring-aop中jdk代理的實作和我們平時自己實作動态代理開發是一緻的,是以此處不做詳細的介紹,隻是簡要給大家說明一下。使用做jdk動态代理時,代理類要實作InvocationHandler接口,目标類要實作接口,因為JDK提供的Proxy類将通過目标對象的類加載器ClassLoader和Interface,以及句柄(Callback)建立與目标類擁有相同接口的代理對象proxy,該代理對象将擁有目标類接口中的所有方法,同時代理類必須實作一個類似回調函數的InvocationHandler接口并重寫該接口中的invoke方法,當調用proxy的每個方法(如案例中的proxy#execute())時,invoke方法将被調用,利用該特性,可以在invoke方法中對目标對象方法執行的前後動态添加其他外圍業務操作,此時無需觸及目标對象的任何代碼,也就實作了外圍業務的操作與目标對象完全解耦合的目的。當然缺點也很明顯需要擁有接口,這也就有了後來的CGLIB動态代理了。
1.2.2、cglib動态代理
spring-aop中cglib代理的實作給大家貼一下源碼的路徑org.springframework.aop.framework.CglibAopProxy、org.springframework.aop.framework.CglibAopProxy#getProxy(java.lang.ClassLoader),感興趣的話大家可以自己去看一下源碼,接下來我會舉一個具體的示例,來看一下如何使用cglib建立代理對象。(注:cglibgithub位址:https://github.com/cglib/cglib)
1.2.2.1、引入jar包
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.5</version>
</dependency>
1.2.2.2、示例代碼
/**
* 建立目标對象
*/
public class Target {
public void execute(){
System.out.println("執行Target的execute方法...");
}
}
/**
* 建立cglib代理類
*/
public class CGLibProxy implements MethodInterceptor {
/**
* 被代理的目标類
*/
private Target target;
public CGLibProxy(Target target) {
super();
this.target = target;
}
/**
* 建立代理對象
* @return
*/
public Target createProxy(){
// 使用CGLIB生成代理:
// 1.聲明增強類執行個體,用于生産代理類
Enhancer enhancer = new Enhancer();
// 2.設定被代理類位元組碼,CGLIB根據位元組碼生成被代理類的子類
enhancer.setSuperclass(target.getClass());
// 3.//設定回調函數,即一個方法攔截
enhancer.setCallback(this);
// 4.建立代理:
return (Target) enhancer.create();
}
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
// 指定要執行被代理的方法
if("execute".equals(method.getName())) {
//調用前執行方法
System.out.println("調用前執行方法");
//調用目标對象的方法(執行A對象即被代理對象的execute方法)
Object result = methodProxy.invokeSuper(proxy, args);
//記錄日志資料(動态添加其他要執行業務)
System.out.println("調用前執行方法");
return result;
}
//如果不需要增強直接執行原方法
return methodProxy.invokeSuper(proxy, args);
}
}
/**
* 建立測試類
*/
public class CglibTest {
public static void main(String[] args) {
// 将cglib生成的代理類寫入到磁盤
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"D:\\test\\test");
CGLibProxy cgLibProxy = new CGLibProxy(new Target());
Target proxy = cgLibProxy.createProxy();
proxy.execute();
}
}
執行結果
從代碼看被代理的類無需接口即可實作動态代理,而CGLibProxy代理類需要實作一個方法攔截器接口MethodInterceptor并重寫intercept方法,類似JDK動态代理的InvocationHandler接口,也是了解為回調函數,同理每次調用代理對象的方法時,intercept方法都會被調用,利用該方法便可以在運作時對方法執行前後進行動态增強。關于代理對象建立則通過Enhancer類來設定的,Enhancer是一個用于産生代理對象的類,作用類似JDK的Proxy類,因為CGLib底層是通過繼承實作的動态代理,是以需要傳遞目标對象的Class,同時需要設定一個回調函數對調用方法進行攔截并進行相應處理,最後通過create()建立目标對象的代理對象。
1.2.2.3、原了解析
上圖是我們在CglibTest 的main方法中設定了将代理類寫入到磁盤之後生成的檔案,一共有三個。我們這裡重點看一下第二個檔案,下面是該class檔案的源碼,給大家貼一下,一些重要的地方我會加上注釋:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.test.excel.cglib;
import java.lang.reflect.Method;
import net.sf.cglib.core.ReflectUtils;
import net.sf.cglib.core.Signature;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Factory;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class Target$EnhancerByCGLIB$e7b1b1b0 extends Target implements Factory {
private boolean CGLIB$BOUND;
public static Object CGLIB$FACTORY_DATA;
private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
private static final Callback[] CGLIB$STATIC_CALLBACKS;
// 攔截器
private MethodInterceptor CGLIB$CALLBACK_0;
private static Object CGLIB$CALLBACK_FILTER;
// 被代理方法
private static final Method CGLIB$execute$0$Method;
// 代理方法
private static final MethodProxy CGLIB$execute$0$Proxy;
private static final Object[] CGLIB$emptyArgs;
private static final Method CGLIB$equals$1$Method;
private static final MethodProxy CGLIB$equals$1$Proxy;
private static final Method CGLIB$toString$2$Method;
private static final MethodProxy CGLIB$toString$2$Proxy;
private static final Method CGLIB$hashCode$3$Method;
private static final MethodProxy CGLIB$hashCode$3$Proxy;
private static final Method CGLIB$clone$4$Method;
private static final MethodProxy CGLIB$clone$4$Proxy;
static void CGLIB$STATICHOOK1() {
CGLIB$THREAD_CALLBACKS = new ThreadLocal();
CGLIB$emptyArgs = new Object[0];
// 代理類
Class var0 = Class.forName("com.test.excel.cglib.Target$EnhancerByCGLIB$e7b1b1b0");
// 被代理類
Class var1;
CGLIB$execute$0$Method = ReflectUtils.findMethods(new String[]{"execute", "()V"}, (var1 = Class.forName("com.test.excel.cglib.Target")).getDeclaredMethods())[0];
CGLIB$execute$0$Proxy = MethodProxy.create(var1, var0, "()V", "execute", "CGLIB$execute$0");
Method[] var10000 = ReflectUtils.findMethods(new String[]{"equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods());
CGLIB$equals$1$Method = var10000[0];
CGLIB$equals$1$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$1");
CGLIB$toString$2$Method = var10000[1];
CGLIB$toString$2$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$2");
CGLIB$hashCode$3$Method = var10000[2];
CGLIB$hashCode$3$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$3");
CGLIB$clone$4$Method = var10000[3];
CGLIB$clone$4$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$4");
}
final void CGLIB$execute$0() {
super.execute();
}
// 被代理的方法
public final void execute() {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
if (var10000 != null) {
// 調用攔截器
var10000.intercept(this, CGLIB$execute$0$Method, CGLIB$emptyArgs, CGLIB$execute$0$Proxy);
} else {
super.execute();
}
}
final boolean CGLIB$equals$1(Object var1) {
return super.equals(var1);
}
public final boolean equals(Object var1) {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
if (var10000 != null) {
Object var2 = var10000.intercept(this, CGLIB$equals$1$Method, new Object[]{var1}, CGLIB$equals$1$Proxy);
return var2 == null ? false : (Boolean)var2;
} else {
return super.equals(var1);
}
}
final String CGLIB$toString$2() {
return super.toString();
}
public final String toString() {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
return var10000 != null ? (String)var10000.intercept(this, CGLIB$toString$2$Method, CGLIB$emptyArgs, CGLIB$toString$2$Proxy) : super.toString();
}
final int CGLIB$hashCode$3() {
return super.hashCode();
}
public final int hashCode() {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
if (var10000 != null) {
Object var1 = var10000.intercept(this, CGLIB$hashCode$3$Method, CGLIB$emptyArgs, CGLIB$hashCode$3$Proxy);
return var1 == null ? 0 : ((Number)var1).intValue();
} else {
return super.hashCode();
}
}
final Object CGLIB$clone$4() throws CloneNotSupportedException {
return super.clone();
}
protected final Object clone() throws CloneNotSupportedException {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
return var10000 != null ? var10000.intercept(this, CGLIB$clone$4$Method, CGLIB$emptyArgs, CGLIB$clone$4$Proxy) : super.clone();
}
public static MethodProxy CGLIB$findMethodProxy(Signature var0) {
String var10000 = var0.toString();
switch(var10000.hashCode()) {
case -508378822:
if (var10000.equals("clone()Ljava/lang/Object;")) {
return CGLIB$clone$4$Proxy;
}
break;
case 539325408:
if (var10000.equals("execute()V")) {
return CGLIB$execute$0$Proxy;
}
break;
case 1826985398:
if (var10000.equals("equals(Ljava/lang/Object;)Z")) {
return CGLIB$equals$1$Proxy;
}
break;
case 1913648695:
if (var10000.equals("toString()Ljava/lang/String;")) {
return CGLIB$toString$2$Proxy;
}
break;
case 1984935277:
if (var10000.equals("hashCode()I")) {
return CGLIB$hashCode$3$Proxy;
}
}
return null;
}
public Target$EnhancerByCGLIB$e7b1b1b0() {
CGLIB$BIND_CALLBACKS(this);
}
public static void CGLIB$SET_THREAD_CALLBACKS(Callback[] var0) {
CGLIB$THREAD_CALLBACKS.set(var0);
}
public static void CGLIB$SET_STATIC_CALLBACKS(Callback[] var0) {
CGLIB$STATIC_CALLBACKS = var0;
}
private static final void CGLIB$BIND_CALLBACKS(Object var0) {
Target$EnhancerByCGLIB$e7b1b1b0 var1 = (Target$EnhancerByCGLIB$e7b1b1b0)var0;
if (!var1.CGLIB$BOUND) {
var1.CGLIB$BOUND = true;
Object var10000 = CGLIB$THREAD_CALLBACKS.get();
if (var10000 == null) {
var10000 = CGLIB$STATIC_CALLBACKS;
if (var10000 == null) {
return;
}
}
var1.CGLIB$CALLBACK_0 = (MethodInterceptor)((Callback[])var10000)[0];
}
}
public Object newInstance(Callback[] var1) {
CGLIB$SET_THREAD_CALLBACKS(var1);
Target$EnhancerByCGLIB$e7b1b1b0 var10000 = new Target$EnhancerByCGLIB$e7b1b1b0();
CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
return var10000;
}
public Object newInstance(Callback var1) {
CGLIB$SET_THREAD_CALLBACKS(new Callback[]{var1});
Target$EnhancerByCGLIB$e7b1b1b0 var10000 = new Target$EnhancerByCGLIB$e7b1b1b0();
CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
return var10000;
}
public Object newInstance(Class[] var1, Object[] var2, Callback[] var3) {
CGLIB$SET_THREAD_CALLBACKS(var3);
Target$EnhancerByCGLIB$e7b1b1b0 var10000 = new Target$EnhancerByCGLIB$e7b1b1b0;
switch(var1.length) {
case 0:
var10000.<init>();
CGLIB$SET_THREAD_CALLBACKS((Callback[])null);
return var10000;
default:
throw new IllegalArgumentException("Constructor not found");
}
}
public Callback getCallback(int var1) {
CGLIB$BIND_CALLBACKS(this);
MethodInterceptor var10000;
switch(var1) {
case 0:
var10000 = this.CGLIB$CALLBACK_0;
break;
default:
var10000 = null;
}
return var10000;
}
public void setCallback(int var1, Callback var2) {
switch(var1) {
case 0:
this.CGLIB$CALLBACK_0 = (MethodInterceptor)var2;
default:
}
}
public Callback[] getCallbacks() {
CGLIB$BIND_CALLBACKS(this);
return new Callback[]{this.CGLIB$CALLBACK_0};
}
public void setCallbacks(Callback[] var1) {
this.CGLIB$CALLBACK_0 = (MethodInterceptor)var1[0];
}
static {
CGLIB$STATICHOOK1();
}
}
從代理對象反編譯源碼可以知道,代理對象繼承于Target,攔截器調用intercept()方法,intercept()方法由自定義CGLibProxy實作,是以,最後調用CGLibProxy中的intercept()方法,進而完成了由代理對象通路到目标對象的動态代理實作。
1.2.3、jdk代理和cglib代理的總結
JDK動态代理:
.需要目标類實作接口
.生成的代理類是與目标類平級,實作了共同的接口
.使用反射的方式進行最終方法的調用,性能較低
CGLIB動态代理:
.不要求目标類實作接口
.生成的代理類是目标類的子類
.final方法不會出現在代理類中
.使用空間換時間的思想對最終的方法調用進行了優化,提升了運作時性能。
看到這裡,大佬們此時是不是心裡會産生個疑問:你的标題不是asm與cglib嗎,前面說了這麼多cglib代理和jdk代理,和asm有關系嗎?哈哈,其實雖然我們沒有再前面的内容中介紹asm的相關知識,但是其實字裡行間都透漏着asm,整體總結一句就是CGLIB包的底層是通過使用一個小而快的位元組碼處理架構ASM,來轉換位元組碼并生成新的類。
二、深入ASM
2.1、簡介
ASM 是一個 Java 位元組碼操控架構。它能被用來動态生成類或者增強既有類的功能。ASM 可以直接産生二進制 class 檔案,也可以在類被加載入 Java 虛拟機之前動态改變類行為。Java class 被存儲在嚴格格式定義的 .class 檔案裡,這些類檔案擁有足夠的中繼資料來解析類中的所有元素:類名稱、方法、屬性以及 Java 位元組碼(指令)。ASM 從類檔案中讀入資訊後,能夠改變類行為,分析類資訊,甚至能夠根據使用者要求生成新類。
與 BCEL 和 SERL 不同,ASM 提供了更為現代的程式設計模型。對于 ASM 來說,Java class 被描述為一棵樹;使用 “Visitor” 模式周遊整個二進制結構;事件驅動的處理方式使得使用者隻需要關注于對其程式設計有意義的部分,而不必了解 Java 類檔案格式的所有細節:ASM 架構提供了預設的"response taker"處理這一切。(官方文檔:https://asm.ow2.io/asm4-guide.pdf,中文文檔:https://cf.jd.com/pages/viewpage.action?pageId=1139356247 )
流程圖
2.2、核心api介紹
ASM架構中的核心類有以下幾個:
① ClassReader:該類用來解析編譯過的class位元組碼檔案。
② ClassWriter:該類用來重新建構編譯後的類,比如說修改類名、屬性以及方法,甚至可以生成新的類的位元組碼檔案。
③ ClassAdapter:該類也實作了ClassVisitor接口,它将對它的方法調用委托給另一個ClassVisitor對象。
下面會列舉每個核心類的幾個核心api供大家參考,後面如果想了解更多的内容,可以直接檢視asm的官方文檔。
2.2.1、ClassReader
構造方法
作用:擷取Class檔案,輸入源很多種,包括位元組流,IO流或者直接加載Class。
示例: public ClassReader(final InputStream inputStream)
accept方法
作用:解析位元組碼中常量池之後的所有元素
示例:public void accept(final ClassVisitor classVisitor, final int parsingOptions)
重要參數解析:parsingOptions(用于跳過讀取位元組碼時的一些資訊選項,有以下四種選擇可以選擇)
SKIP_CODE | 表示跳過代碼掃描,如果你隻需要隻是類的結構,就可以使用這個。 |
SKIP_DEBUG | 跳過調試資訊,ClassReader不會去通路調試資訊。 如果設定了這個标志,這些屬性既不會被解析也不會被通路(例如ClassVisitor.visitSource,MethodVisitor.visitLocalVariable, MethodVisitor.visitLineNumber MethodVisitor.visitParameter)。 這個會比較常用,當你不需要上面這些方法時候。 |
SKIP_FRAMES | 跳過堆棧映射幀.如果設定了這個标志,這些屬性既不會被解析也不會被通路(例如:MethodVisitor.visitFrame).這個标志當ClassWriter.COMPUTE_FRAME選項被使用時,它會避免通路将被忽略并從頭重新計算的幀。 |
EXPAND_FRAMES | 用來展開堆棧映射幀的标志,會降低性能。 |
2.2.2、ClassWriter
構造方法
作用:用來定義類的屬性
示例:public ClassWriter(final int flags)
重要參數解析:
flag == 0 | 自己計算棧幀和局部變量以及操作數堆棧的大小 ,也就是你要自己調用visitmax和visitFrame方法。 |
flag == ClassWriter. COMPUTE_MAXS | 局部變量和操作數堆棧部分的大小會為你計算,還需要調用visitFrame方法設定棧幀。 |
flag == ClassWriter.COMPUTE_FRAMES | 所有的内容都是自動計算的。 你不必調用visitFrame和visitmax |
visit方法
作用:用來定義類的屬性
示例:public final void visit(int version, int access, String name, String signature, String superName, String[] interfaces)
重要參數解析:
version | Java版本号,例如 V1_8 代表Java 8 |
access | Class通路權限,一般預設都是 ACC_PUBLIC | ACC_SUPER |
name | Class檔案名,例如:asm/User,包名加類名 |
signature | 類的簽名,除非你是泛型類或者實作泛型接口,一般預設null。 |
superName | 繼承的類,很明顯所有類預設繼承Object。例如:java/lang/Object ,如果是繼承自己寫的類Animal,那就是 asm/Animal |
interfaces | 實作的接口,例如實作自己寫的接口IPrint,那就是new String[]{"asm/IPrint"} |
visitMethod方法
作用:用來定義類的方法
示例:public final MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions)
重要參數解析:
access | 方法的通路權限,也就是public,private等 |
name | 方法名: 在Class中,有兩個特殊方法名。<init>和<clinit>,其他的方法都是正常對應Java方法名。 <init>代表的是類的執行個體構造方法初始化,也就是new 一個類時,肯定會調用的方法。 <clinit>代表的類的初始化方法,不同于<init>,它不是顯示調用的。因為Java虛拟機會自動調用<clinit>,并且保證在子類的<clinit>前調用父類的<clinit>。也就是說,Java虛拟機中,第一個被執行<clinit>方法的肯定是java.lang.Object。 注意:<init>和<clinit>執行的時機不一樣,<clinit>的時機早于<init>,<clinit>是在類被加載階段的初始化過程中調用<clinit>方法,而<init>方法的調用是在new一個類的執行個體的時候。 |
descriptor | 方法的描述符,就是位元組碼對代碼中方法的形參和傳回值的一個描述。其實就是一個一一對應的模闆 |
signature | 方法簽名,除非方法的參數、傳回類型和異常使用了泛型,否則一般為 null。 |
exceptions | 方法上的異常 |
visitField方法
作用:用來定義一個變量
示例:public final FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value)
重要參數解析:
access | 變量的通路權限,,也就是public,private等 |
name | 變量名 |
descriptor | 變量的描述符 |
signature | 變量的簽名,如果沒有使用泛型則為null |
value | 變量的初始值。這個字段僅作用于被final修飾的字段,或者接口中聲明的變量。其他預設為null,變量的指派是通過MethodVisitor 的 visitFieldInsn方法。 |
visitEnd方法
作用:用來通知Class已經使用完
示例:public final void visitEnd()
toByteArray方法
作用:傳回一個位元組數組
示例:public byte[] toByteArray()
2.2.3、ClassVisiter
看名字就能看出來,這是一個對Class檔案進行觀察(掃描)的工具類。因為它是一個抽象類,是以我們隻能對其進行繼承重寫。并且ClassVisitor所有的調用都是由ClassReader來進行回調,就是我們前面accept方法。而這個方法裡,執行了對ClassVisitor源碼掃描的回調。如下圖:
構造方法
示例:protected ClassVisitor(final int api, final ClassVisitor classVisitor)
參數解釋:
api:代表是ASM API的版本 ,預設填最新(ASM9)即可。編寫代碼和代碼讀取的版本号最好保持一緻,不然可能會有一些相容性的錯誤。
由于ClassVisitor所有的調用都是由ClassReader來進行回調,是以其他api咱們不做過多介紹。
2.3、ASM實踐
上面給大家介紹了asm的api,還有一些其他的api例如MethodVisitor、FieldVisitor等就不過多介紹了,還是那句話,想深入了解,就看一下官方文檔。但是光說不練假把式,接下來我們就對asm做一番實踐,由此來體會他的功能之強大。
2.3.1、引入jar
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>9.4</version>
</dependency>
2.3.2、生成類
我們自定義生成一個People的類
package com.test.excel.asm;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import java.io.FileOutputStream;
import static jdk.internal.org.objectweb.asm.ClassWriter.COMPUTE_FRAMES;
import static jdk.internal.org.objectweb.asm.ClassWriter.COMPUTE_MAXS;
import static jdk.internal.org.objectweb.asm.Opcodes.*;
/**
* 檔案描述
*
* @author dzl
* @date 2022-11-27 18:49
*/
public class AsmTest {
public static void main(String[] args) throws Exception {
testCreateAClass();
}
public static void testCreateAClass()throws Exception{
//建立一個類生成器,COMPUTE_FRAMES,COMPUTE_MAXS這2個參數能夠讓asm自動更新操作數棧
ClassWriter cw = new ClassWriter(COMPUTE_FRAMES|COMPUTE_MAXS);
//生成一個public的類,類路徑是com.study.Human
cw.visit(V1_8, ACC_PUBLIC,"com/test/excel/asm/People",null,"java/lang/Object",null);
//生成預設的構造方法: public People()
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC,"<init>","()V",null,null);
mv.visitVarInsn(ALOAD,0);
mv.visitMethodInsn(INVOKESPECIAL,"java/lang/Object","<init>","()V",false);
mv.visitInsn(RETURN);
mv.visitMaxs(0,0);//更新操作數棧
mv.visitEnd();//一定要有visitEnd
//生成成員變量
//1.生成String類型的成員變量:private String name;
FieldVisitor fv= cw.visitField(ACC_PRIVATE,"name","Ljava/lang/String;",null,null);
fv.visitEnd();//不要忘記end
//2.生成Long類型成員:private long age
fv=cw.visitField(ACC_PRIVATE,"age","J",null,null);
fv.visitEnd();
//3.生成Int類型成員:protected int no
fv=cw.visitField(ACC_PROTECTED,"no","I",null,null);
fv.visitEnd();
//4.生成靜态成員變量:public static long score
fv=cw.visitField(ACC_PUBLIC + ACC_STATIC,"score","J",null,null);
//5.生成常量:public static final String real_name = "Sand哥"
fv=cw.visitField(ACC_PUBLIC+ACC_STATIC+ACC_FINAL,"real_name","Ljava/lang/String;",null,"Sand哥");
fv.visitEnd();
//6.生成成員方法greet
mv=cw.visitMethod(ACC_PUBLIC,"greet","(Ljava/lang/String;)I",null,null);
mv.visitCode();
mv.visitIntInsn(ALOAD,0);
mv.visitIntInsn(ALOAD,1);
//6.1 調用靜态方法 System.out.println("Hello");
mv.visitFieldInsn(GETSTATIC,"java/lang/System","out","Ljava/io/PrintStream;");
// mv.visitLdcInsn("Hello");//加載字元常量
mv.visitIntInsn(ALOAD,1);//加載形參
mv.visitMethodInsn(INVOKEVIRTUAL,"java/io/PrintStream","println","(Ljava/lang/String;)V",false);//列印形參
//擷取類的byte數組
byte[] classByteData=cw.toByteArray();
//把類資料寫入到class檔案,這樣你就可以把這個類檔案打包供其他的人使用
FileOutputStream out = new FileOutputStream(AsmTest.class.getResource("/com/test/excel/asm/").getPath() + "People.class");
out.write(classByteData);
out.close();
}
}
運作結果:
2.3.3、修改已存在的類
假如我們現在有如下類,我們用asm做如下幾個修改:1.增加了一個phone字段 2.删除testA方法 3.将testC方法改成protected 4.新增一個getPhone方法。
/**
* 檔案描述
*
* @author dzl
* @date 2022-11-27 19:14
*/
public class ModifyFunction {
private int a;
public void testA(){
System.out.println("I am A");
}
public void testB(){
System.err.println("===>I am B");
}
public int testC(){
return a;
}
}
利用asm修改該類
package com.test.excel.asm;
import org.objectweb.asm.*;
import java.io.FileOutputStream;
import static jdk.internal.org.objectweb.asm.Opcodes.*;
import static org.objectweb.asm.Opcodes.ASM9;
/**
* 檔案描述
*
* @author dzl
* @date 2022-11-27 18:49
*/
public class AsmTest {
public static void main(String[] args) throws Exception {
testModifyCalss();
}
private static void testModifyCalss()throws Exception{
ClassReader cr = new ClassReader("com.test.excel.asm.ModifyFunction");
final ClassWriter cw=new ClassWriter(cr,0);
// cr.accept(cw, 0);//可以直接接受一個writer,實作複制
cr.accept(new ClassVisitor(ASM9,cw) {//接受一個帶classWriter的visitor,實作定制化方法拷貝或者屬性删除字段
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
System.out.println("visit method:"+name+"====> "+descriptor);
if("testA".equals(name)){//拷貝的過程中删除一個方法
return null;
}
if("testC".equals(name)){//将testC public方法變成protect
access=ACC_PROTECTED;
}
return super.visitMethod(access, name, descriptor, signature, exceptions);
}
@Override
public void visitEnd() {
//特别注意的是:要為類增加屬性和方法,放到visitEnd中,避免破壞之前已經排列好的類結構,在結尾添加新結構
//增加一個字段(注意不能重複),注意最後都要visitEnd
FieldVisitor fv = cv.visitField(ACC_PUBLIC, "phone", "Ljava/lang/String;", null, null);
fv.visitEnd();//不能缺少visitEnd
//增加一個方法
MethodVisitor mv=cv.visitMethod(ACC_PUBLIC,"getPhone","()Ljava/lang/String;",null,null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD,"com/test/excel/asm/ModifyFunction","phone","Ljava/lang/String;");
mv.visitInsn(IRETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();//不能缺少visitEnd
super.visitEnd();//注意原本的visiEnd不能少
}
},0);
//指定新生成的class路徑的生成位置,這個路徑你可以随便指定
FileOutputStream out = new FileOutputStream(AsmTest.class.getResource("/com/test/excel/asm/").getPath() + "ModifyFunction.class");
out.write(cw.toByteArray());
out.close();
}
}
運作結果:
2.3.4、實作方法注入
實作方法注入類似于我們的aop功能,本篇就不做過多介紹了,感興趣的大佬可以參考上一篇文章的demo。
三、總結
每次寫到這裡,心裡都感覺如釋重負了一下,因為正文總算是寫完了。作為本系列呈上啟下的第二篇,查了很多資料,看了很多文章,再寫分享的同時,自身也學到了很多東西。同時,如果文章中有不足或者描述不準确的内容,希望及時批評指正,萬分感謝。