字節(jié)碼增強,實質就是在編譯期或運行期進行<font color="#D46B08">字節(jié)碼插樁</font>备典,以便在運行期影響程序的<font color="#D46B08">執(zhí)行行為</font>诫欠。按照增強時機,可以分為<font color="red">編譯時增強</font>(Pluggable Annotation Processing)莱褒,<font color="red">運行時增強</font>(代理類击困,java-agent)。
實現方式:
asm广凸,Javassist, cglib阅茶,java-proxy,bytebuddy
字節(jié)碼工具 | java-proxy | asm | Javassist | cglib | bytebuddy |
---|---|---|---|---|---|
類創(chuàng)建 | 支持 | 支持 | 支持 | 支持 | 支持 |
實現接口 | 支持 | 支持 | 支持 | 支持 | 支持 |
方法調用 | 支持 | 支持 | 支持 | 支持 | 支持 |
類擴展 | 不支持 | 支持 | 支持 | 支持 | 支持 |
父類方法調用 | 不支持 | 支持 | 支持 | 支持 | 支持 |
優(yōu)點 | 容易上手谅海,<font color="red">簡單動態(tài)代理首選</font> | <font color="red">任意字節(jié)碼插入,幾乎不受限制</font> | <font color="red">java原始語法扭吁,字符串形式插入撞蜂,寫入直觀 </font> | bytebuddy看起來差不多 | <font color="red">支持任意維度的攔截,可以獲取原始類侥袜、方法蝌诡,以及代理類和全部參數</font> |
缺點 | 功能有限,不支持擴展 | 學習難度大枫吧,編寫代碼量大 | 不支持jdk1.5以上的語法浦旱,如泛型,增強for | 正在被bytebuddy淘汰 | 不太直觀九杂,學習理解有些成本颁湖,API非常多 |
常見應用 | spring-aop,MyBatis | cglib例隆,bytebuddy | Fastjson甥捺,MyBatis | spring-aop,EasyMock裳擎,jackson-databind | SkyWalking涎永,Mockito,Hibernate,powermock |
學習成本 | <font color="#389E0D">一星</font> | <font color="red">五星</font> | <font color="#389E0D">二星</font> | <font color="#D46B08">三星</font> | <font color="#D46B08">三星</font> |
使用場景:
由于字節(jié)碼增強可以在完全不侵入業(yè)務代碼的情況下植入代碼邏輯羡微,常見的場景:
- <font color="red">動態(tài)代理;</font>
- <font color="red">熱部署谷饿;</font>
- <font color="red">調用鏈跟蹤埋點;</font>
- <font color="red">動態(tài)插入log(性能監(jiān)控);</font>
- <font color="red">測試代碼覆蓋率跟蹤妈倔;</font>
java-proxy介紹
java動態(tài)代理基于InvocationHandler接口博投,代碼由ProxyGenerator.generateProxyClass生成,簡單直觀盯蝴。
<font color="red">生成的Proxy實現接口全部方法毅哗,內部調用InvocationHandler的invoke方法</font>
例子
動物咖啡接口
public interface AnimalCoffee {
Object make();
}
public interface CoffeeNameAware {
String getName();
}
生成一個原味咖啡
AnimalCoffee original = (AnimalCoffee) Proxy.newProxyInstance(AnimalCoffee.class.getClassLoader(), new Class<?>[]{AnimalCoffee.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Map<String, Object> result = new HashMap<>();
result.put("口味", "原味");
result.put("產地", "中國");
result.put("價格", "10¥");
return result;
}
});
原味咖啡生成的字節(jié)碼
//將hashCode,toString,equals方法忽略
public final class $Proxy4 extends Proxy implements AnimalCoffee {
private static Method m3;
public $Proxy4(InvocationHandler var1) throws {
super(var1);
}
public final Object make() throws {
try {
return (Object)super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m3 = Class.forName("AnimalCoffee").getMethod("make");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
ASM介紹
asm插入字節(jié)碼是原始字節(jié)碼語法,上手難度高捧挺,原始語法不受框架限制虑绵,任意插入字節(jié)碼;
ASM API 是基于 ClassVisitor 抽象類的。這 個 類中的每個方法都對應于同名的類文件結構部分闽烙。簡單的部分只需一個方法調 用就能 訪問翅睛,這個調用返回 void,其參數描述了這些部分的內容黑竞。有些部分的內容可以達到 任意長度捕发、 任意復雜度,這樣的部分可以用一個初始方法調用來訪問很魂,返回一個輔助的訪問者 類扎酷。 visitAnnotation、visitField 和 visitMethod 方法就是這種情況遏匆,它們分別返 回 AnnotationVisitor法挨、FieldVisitor 和 MethodVisitor.
ClassReader (讀取)→ ClassVisitor(鏈式修改)→ ClassWriter → (保存寫入)
//javap -v 查看
getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
打印類字節(jié)碼
ClassPrinter cp = new ClassPrinter();
ClassReader cr = new ClassReader("java.lang.Runnable");
cr.accept(cp, 0);
生成類-原味咖啡
ClassWriter classWriter = new ClassWriter(0);
FieldVisitor fieldVisitor;
MethodVisitor methodVisitor;
AnnotationVisitor annotationVisitor0;
classWriter.visit(V1_8, ACC_PUBLIC | ACC_SUPER, "OriginalAnimalCoffee", null, "java/lang/Object", new String[]{"AnimalCoffee"});
classWriter.visitSource("OriginalAnimalCoffee.java", null);
{
methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
methodVisitor.visitCode();
Label label0 = new Label();
methodVisitor.visitLabel(label0);
methodVisitor.visitLineNumber(12, label0);
methodVisitor.visitVarInsn(ALOAD, 0);
methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
methodVisitor.visitInsn(RETURN);
Label label1 = new Label();
methodVisitor.visitLabel(label1);
methodVisitor.visitLocalVariable("this", "LOriginalAnimalCoffee;", null, label0, label1, 0);
methodVisitor.visitMaxs(1, 1);
methodVisitor.visitEnd();
}
{
methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "make", "()Ljava/lang/Object;", null, null);
methodVisitor.visitCode();
Label label0 = new Label();
methodVisitor.visitLabel(label0);
methodVisitor.visitLineNumber(15, label0);
methodVisitor.visitTypeInsn(NEW, "java/util/HashMap");
methodVisitor.visitInsn(DUP);
methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/util/HashMap", "<init>", "()V", false);
methodVisitor.visitVarInsn(ASTORE, 1);
Label label1 = new Label();
methodVisitor.visitLabel(label1);
methodVisitor.visitLineNumber(16, label1);
methodVisitor.visitVarInsn(ALOAD, 1);
methodVisitor.visitLdcInsn("\u53e3\u5473");
methodVisitor.visitLdcInsn("\u539f\u5473");
methodVisitor.visitMethodInsn(INVOKEINTERFACE, "java/util/Map", "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", true);
methodVisitor.visitInsn(POP);
Label label2 = new Label();
methodVisitor.visitLabel(label2);
methodVisitor.visitLineNumber(17, label2);
methodVisitor.visitVarInsn(ALOAD, 1);
methodVisitor.visitLdcInsn("\u4ea7\u5730");
methodVisitor.visitLdcInsn("\u4e2d\u56fd");
methodVisitor.visitMethodInsn(INVOKEINTERFACE, "java/util/Map", "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", true);
methodVisitor.visitInsn(POP);
Label label3 = new Label();
methodVisitor.visitLabel(label3);
methodVisitor.visitLineNumber(18, label3);
methodVisitor.visitVarInsn(ALOAD, 1);
methodVisitor.visitLdcInsn("\u4ef7\u683c");
methodVisitor.visitLdcInsn("10\u00a5");
methodVisitor.visitMethodInsn(INVOKEINTERFACE, "java/util/Map", "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", true);
methodVisitor.visitInsn(POP);
Label label4 = new Label();
methodVisitor.visitLabel(label4);
methodVisitor.visitLineNumber(19, label4);
methodVisitor.visitVarInsn(ALOAD, 1);
methodVisitor.visitInsn(ARETURN);
Label label5 = new Label();
methodVisitor.visitLabel(label5);
methodVisitor.visitLocalVariable("this", "LOriginalAnimalCoffee;", null, label0, label5, 0);
methodVisitor.visitLocalVariable("result", "Ljava/util/Map;", "Ljava/util/Map<Ljava/lang/String;Ljava/lang/Object;>;", label1, label5, 1);
methodVisitor.visitMaxs(3, 2);
methodVisitor.visitEnd();
}
classWriter.visitEnd();
byte[] classByte = classWriter.toByteArray();
轉換(修改)類
byte[] oldCl = oldClassByte;
ClassReader cr = new ClassReader(oldCl); //讀取
ClassWriter cw = new ClassWriter(0);
cr.accept(cw, 0); //將old寫入cw
//新增一個字段
cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "LESS", "I", null, new Integer(-1)).visitEnd();
byte[] newCl = cw.toByteArray();
//引入一個 ClassVisitor:
ClassReader cr = new ClassReader(oldCl);
ClassWriter cw = new ClassWriter(0);
// cv 將所有事件轉發(fā)給 cw
ClassVisitor cv = new ClassVisitor(ASM4, cw) { };
cr.accept(cv, 0); //將old寫入cv
byte[] newCl2 = cw.toByteArray();
<font color="red">給出了與上述代碼相對應的體系結構幅聘,其中的組件用方框表示坷剧,事件用箭頭表示(其中的垂直時間線與程序圖中一樣)。</font>
轉換鏈
public class ChangeVersionAdapter extends ClassVisitor {
public ChangeVersionAdapter(ClassVisitor cv) {
super(ASM4, cv);
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
cv.visit(V1_5, access, name, signature, superName, interfaces);
}
}
// 和前面相比喊暖,這里是互相擁有,前面是單一擁有
byte[] b1 = oldClassByte;
ClassReader cr = new ClassReader(b1);
ClassWriter cw = new ClassWriter(cr, 0); ChangeVersionAdapter ca = new ChangeVersionAdapter(cw); cr.accept(ca, 0);
byte[] b2 = cw.toByteArray();
jdk-agent: instrument應用
public static void premain(String agentArgs, Instrumentation inst) {
inst.addTransformer(new ClassFileTransformer() {
public byte[] transform(ClassLoader l, String name, Class c,
ProtectionDomain d, byte[] b)
throws IllegalClassFormatException {
ClassReader cr = new ClassReader(b);
ClassWriter cw = new ClassWriter(cr, 0);
ClassVisitor cv = new ChangeVersionAdapter(cw);
cr.accept(cv, 0);
return cw.toByteArray();
}
});
}
移除類成員
用于轉換類版本的方法當然也可用于 ClassVisitor 類的其他方法撕瞧。例如陵叽,通過 改 變 visitField 和 visitMethod 方法的 access 或 name 參數,可以改變一個字段 或一個方 法的修飾字段或名字丛版。另外巩掺,除了在轉發(fā)的方法調用中使用經過修改的參數之外,還 可以選擇根 本不轉發(fā)該調用页畦。其效果就是相應的類元素被移除胖替。
public class RemoveMethodAdapter extends ClassVisitor {
private String mName;
private String mDesc;
public RemoveMethodAdapter( ClassVisitor cv, String mName, String mDesc) {
super(ASM4, cv);
this.mName = mName;
this.mDesc = mDesc;
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
if (name.equals(mName) && desc.equals(mDesc)) {
// 不要委托至下一個訪問器 -> 這樣將移除該方
return null;
}
return cv.visitMethod(access, name, desc, signature, exceptions);
}
}
javassist介紹
Javassit相比于ASM要簡單點,Javassit提供了更高級的API,當時執(zhí)行效率上比ASM要差独令,因為ASM上直接操作的字節(jié)碼端朵。
工作步驟
ClassPool容器
- getDefault (): 返回默認的ClassPool ,單例模式燃箭,一般通過該方法創(chuàng)建我們的ClassPool冲呢;
- appendClassPath(ClassPath cp), insertClassPath(ClassPath cp) : 將一個ClassPath加到類搜索路徑的末尾位置或插入到起始位置。通常通過該方法寫入額外的類搜索路徑招狸,以解決多個類加載器環(huán)境中找不到類問題敬拓;
- importPackage(String packageName):導入包;
- makeClass(String classname):創(chuàng)建一個空類裙戏,沒有變量和方法乘凸,后序通過CtClass的函數進行添加;
- get(String classname)累榜、getCtClass(String classname) : 根據類路徑名獲取該類的CtClass對象营勤,用于后續(xù)的編輯。
CtClass類
- debugDump;String類型信柿,如果生成冀偶。class文件,保存在這個目錄下渔嚷。
- setName(String name):給類重命名;
- setSuperclass(CtClass clazz):設置父類;
- addField(CtField f, Initializer init):添加字段(屬性)进鸠,初始值見CtField;
- addMethod(CtMethod m):添加方法(函數)形病;
- toBytecode(): 返回修改后的字節(jié)碼客年。需要注意的是一旦調用該方法,則無法繼續(xù)修改CtClass漠吻;
- toClass(): 將修改后的CtClass加載至當前線程的上下文類加載器中量瓜,CtClass的toClass方法是通過調用本方法實現。需要注意的是一旦調用該方法途乃,則無法繼續(xù)修改已經被加載的CtClass绍傲;
- writeFile(String directoryName):根據CtClass生成 .class 文件;
- defrost():解凍類耍共,用于使用了toclass()烫饼、toBytecode、writeFile()试读,類已經被JVM加載杠纵,Javassist凍結CtClass后;
- detach():避免內存溢出钩骇,從ClassPool中移除一些不需要的CtClass比藻。
Loader類加載器
- loadClass(String name):加載類
CtField字段
- CtField(CtClass type, String name, CtClass declaring) :構造函數铝量,添加字段類型,名稱银亲,所屬的類;
- CtField.Initializer constant():CtClass使用addField時初始值的設置;
- setModifiers(int mod):設置訪問級別慢叨,一般使用Modifier調用常量。
CtMethod方法
- insertBefore(String src):在方法的起始位置插入代碼群凶;
- insertAfter(String src):在方法的所有 return 語句前插入代碼以確保語句能夠被執(zhí)行插爹,除非遇到exception;
- insertAt(int lineNum, String src):在指定的位置插入代碼请梢;
- addCatch(String src, CtClass exceptionType):將方法內語句作為try的代碼塊赠尾,插入catch代碼塊src;
- setBody(String src):將方法的內容設置為要寫入的代碼,當方法被 abstract修飾時毅弧,該修飾符被移除气嫁;
- setModifiers(int mod):設置訪問級別,一般使用Modifier調用常量;
- invoke(Object obj, Object... args):反射調用字節(jié)碼生成類的方法够坐。
//對于setBody $0代表this $1寸宵、$2、...代表方法的第幾個參數
setBody("{$0.name = $1;}");
$符號含義
符號 | 含義 |
---|---|
|
this,第幾個參數 |
$args | 參數列表. $args的類型是Object[]. |
$$ | 所有實參.例如, m($$) 等價于 m( |
$cflow(...) | cflow變量 |
$r | 結果類型. 用于表達式轉換. |
$w | 包裝類型. 用于表達式轉換. |
$_ | 結果值 |
$sig | java.lang.Class列表元咙,代表正式入參類型 |
$type | java.lang.Class對象梯影,代表正式入參值. |
$class | java.lang.Class對象,代表傳入的代碼段. |
生成類-原味咖啡
ClassPool classPool = ClassPool.getDefault();
classPool.importPackage("java.util.Map");
classPool.importPackage("java.util.HashMap");
CtClass ctClass = classPool.makeClass("OriginalAnimalCoffee");
ctClass.addInterface(classPool.get("AnimalCoffee"));
ctClass.addMethod(CtMethod.make("public Object make() {\n" +
" Map result = new HashMap();\n" +
" result.put(\"口味\", \"原味\");\n" +
" result.put(\"產地\", \"中國\");\n" +
" result.put(\"價格\", \"10¥\");\n" +
" return result;\n" +
" }", ctClass));
byte[] classByte = ctClass.toBytecode();
ctClass.detach();
javassist常見錯誤
for (Header header : headers) {}
//高級語法導致庶香,換成1.5jdk的循環(huán)
for (int i = 0; i < headers.length; i++){}
javassist.CannotCompileException: [source error] ; is missing
toParams(String query, Map<String, Object> params){}
//泛型語法導致甲棍,換成1.5jdk的
toParams(String query, Map params){}
javassist.CannotCompileException: [source error] syntax error near "ery, Map<String, Obj"
pool.importPackage("java.util.ArrayList");
//引入依賴包
javassist.CannotCompileException: [source error] no such class: ArrayList
bytebuddy介紹
常用核心API
ByteBuddy
- 流式API方式的入口類
- 提供Subclassing/Redefining/Rebasing方式改寫字節(jié)碼
- 所有的操作依賴DynamicType.Builder進行,創(chuàng)建不可變的對象
ElementMatchers(ElementMatcher)
- 提供一系列的元素匹配的工具類(named/any/nameEndsWith等等)
- ElementMatcher(提供對類型、方法赶掖、字段感猛、注解進行matches的方式,類似于Predicate)
- Junction對多個ElementMatcher進行了and/or操作
DynamicType(動態(tài)類型,所有字節(jié)碼操作的開始,非常值得關注)
- Unloaded(動態(tài)創(chuàng)建的字節(jié)碼還未加載進入到虛擬機,需要類加載器進行加載)
- Loaded(已加載到jvm中后,解析出Class表示)
- Default(DynamicType的默認實現,完成相關實際操作)
Implementation(用于提供動態(tài)方法的實現)
- FixedValue(方法調用返回固定值)
- MethodDelegation(方法調用委托,支持兩種方式: Class的static方法調用、object的instance method方法調用)
Builder(用于創(chuàng)建DynamicType,相關接口以及實現后續(xù)待詳解)
- MethodDefinition
- FieldDefinition
- AbstractBase
常用注解說明
注解 | 說明 |
---|---|
@Argument | 綁定單個參數 |
@AllArguments | 綁定所有參數的數組 |
@This | 當前被攔截的奢赂、動態(tài)生成的那個對象 |
@Super | 當前被攔截的陪白、動態(tài)生成的那個對象的父類對象 |
@Origin | 可以綁定到以下類型的參數:Method 被調用的原始方法 Constructor 被調用的原始構造器 Class 當前動態(tài)創(chuàng)建的類 MethodHandle MethodType Field 攔截的字段 |
@DefaultCall | 調用默認方法而非super的方法 |
@SuperCall | 用于調用父類版本的方法 |
@Super | 注入父類型對象,可以是接口膳灶,從而調用它的任何方法 |
@RuntimeType | 可以用在返回值咱士、參數上,提示ByteBuddy禁用嚴格的類型檢查 |
@Empty | 注入參數的類型的默認值 |
@StubValue | 注入一個存根值轧钓。對于返回引用司致、void的方法,注入null聋迎;對于返回原始類型的方法,注入0 |
@FieldValue | 注入被攔截對象的一個字段的值 |
@Morph | 類似于@SuperCall枣耀,但是允許指定調用參數 |
生成類-原味咖啡
Map<String, Object> result = new HashMap<>();
result.put("口味", "原味");
result.put("產地", "中國");
result.put("價格", "10¥");
DynamicType.Unloaded<?> dynamicType = new ByteBuddy()
.subclass(AnimalCoffee.class)
//匹配make方法霉晕,返回固定值
.method(ElementMatchers.named("make")).intercept(FixedValue.value(result))
.method(ElementMatchers.named("equals")).intercept(FixedValue.value(true))
.method(ElementMatchers.named("toString")).intercept(FixedValue.value("原味咖啡"))
.defineMethod("drink", String.class, Modifier.PUBLIC).withParameter(String.class, "name")
//攔截drink方法庭再,委托給MyDrinkInterceptor
.intercept(MethodDelegation.to(new MyDrinkInterceptor()))
//實現CoffeeNameAware接口,getName返回固定值
.implement(CoffeeNameAware.class)
.method(ElementMatchers.named("getName"))
.intercept(FixedValue.value("原味咖啡"))
.make();
Class<?> type = dynamicType.load(AnimalCoffee.class.getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
.getLoaded();
AnimalCoffee original = (AnimalCoffee) type.newInstance();
System.out.println(original.make());
Assert.assertTrue(original.make() instanceof Map);
public class MyDrinkInterceptor {
@RuntimeType
public String drink(String name) {
return name + " 喝掉原味咖啡";
}
}
生成原味咖啡類字節(jié)碼
public class AnimalCoffee$ByteBuddy$ySIOBpxK implements AnimalCoffee, CoffeeNameAware {
public boolean equals(Object var1) {
public String toString() {
return "原味咖啡";
}
//FixedValue.value 引用為靜態(tài)對象的引用
public Object make() {
return value$shoqck1;
}
public String getName() {
return "原味咖啡";
}
//MethodDelegation.to 引用為靜態(tài)代理對象的方法
public String drink(String name) {
return delegate$fkonef0.drink(var1);
}
}
代理類-摩卡咖啡
DynamicType.Unloaded<?> dynamicType = new ByteBuddy()
.subclass(AnimalCoffee.class)
.method(ElementMatchers.isDeclaredBy(AnimalCoffee.class)
.or(ElementMatchers.isEquals())
.or(ElementMatchers.isToString()
.or(ElementMatchers.isHashCode()))
)
//委托代理
.intercept(MethodDelegation
.to(new AnimalCoffeeInterceptor(new MochaAnimalCoffee()))
)
//新增字段name
.defineField("name", String.class, Modifier.PUBLIC)
//實現CoffeeNameAware接口牺堰,返回字段name
.implement(CoffeeNameAware.class)
.method(ElementMatchers.named("getName"))
.intercept(FieldAccessor.ofField("name"))
//新增方法拄轻,設置字段name
.defineMethod("setName", Void.TYPE, Modifier.PUBLIC)
.withParameters(String.class)
.intercept(FieldAccessor.ofBeanProperty())
//新增構造函數,設置字段name
.defineConstructor(Modifier.PUBLIC)
.withParameters(String.class)
.intercept(MethodCall.invoke(Object.class.getConstructor()).andThen(
FieldAccessor.ofField("name").setsArgumentAt(0)
))
.make();
Class<?> type3 = dynamicType.load(AnimalCoffee.class.getClassLoader(), ClassLoadingStrategy.Default.INJECTION).getLoaded();
//創(chuàng)建有構造參數的對象
Object proxy = type3.getDeclaredConstructor(String.class).newInstance("摩卡咖啡");
System.out.println(((AnimalCoffee) proxy).make());
System.out.println(((CoffeeNameAware) proxy).getName());
public class MochaAnimalCoffee implements AnimalCoffee, CoffeeNameAware {
@Override
public Object make() {
Map<String, Object> result = new HashMap<>();
result.put("口味", "口感絲滑");
result.put("產地", "中國");
result.put("價格", "15¥");
return result;
}
@Override
public String getName() {
return null;
}
}
public class AnimalCoffeeInterceptor {
final AnimalCoffee animalCoffee;
public AnimalCoffeeInterceptor(AnimalCoffee animalCoffee) {
this.animalCoffee = animalCoffee;
}
@RuntimeType
public Object intercept(@This Object proxy, @Origin Method method, @AllArguments @RuntimeType Object[] args) throws Throwable {
String name = method.getName();
long start = System.currentTimeMillis();
if ("hashCode".equals(name)) {
return animalCoffee.hashCode();
} else if ("toString".equals(name)) {
return animalCoffee.toString();
} else if ("make".equals(name)) {
Object result = method.invoke(animalCoffee, args);
Map<String, Object> m = (Map<String, Object>) result;
m.put("熱量", "360 千卡");
m.put("顏色", "深棕色");
m.put("制造耗時", System.currentTimeMillis() - start);
return m;
}
return method.invoke(animalCoffee, args);
}
}
摩卡咖啡代理類字節(jié)碼
public class AnimalCoffee$ByteBuddy$ogesFMIU implements AnimalCoffee, CoffeeNameAware {
public String name;
//委托給AnimalCoffeeInterceptor
public Object make() {
return delegate$ntgsch1.intercept(this, cachedValue$v4z514W3$eafjf73, new Object[0]);
}
public String getName() {
return this.name;
}
public void setName(String var1) {
this.name = var1;
}
public AnimalCoffee$ByteBuddy$ogesFMIU(String var1) {
this.name = var1;
}
public AnimalCoffee$ByteBuddy$ogesFMIU() {
}
}
例子java-agent應用
//掛載運行應用的java-agent
assertThat(ByteBuddyAgent.install(), instanceOf(Instrumentation.class));
//攔截任何的方法伟葫,并且綁定參數到MorphingCallable
AgentBuilder.Transformer transformer = new AgentBuilder.Transformer() {
@Override
public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule javaModule) {
return builder.method(ElementMatchers.any())
.intercept(MethodDelegation.withDefaultConfiguration()
.withBinders(Morph.Binder.install(MorphingCallable.class))
.to(RequestInterceptor.class));
}
};
//對任何類都攔截轉換
ClassFileTransformer classFileTransformer =
new AgentBuilder.Default()
.type(ElementMatchers.any())
.transform(transformer)
.installOnByteBuddyAgent();
new Lottery().win();
System.out.println(new Lottery().say("ming", "hot"));
//移除
ByteBuddyAgent.getInstrumentation().removeTransformer(classFileTransformer);
public interface MorphingCallable<T> {
T call(Object... arguments);
}
public class RequestInterceptor {
/**
* @SuperCall 注解注入的 Callable 參數來調用目標方法時恨搓,是無法動態(tài)修改參數的,如果想要動態(tài)修改參數筏养,則需要用到
* @Morph 注解以及一些綁定操作,
* .intercept(MethodDelegation.withDefaultConfiguration().
* withBinders(Morph.Binder.install(MorphingCallable.class)).to(X.class)
*/
@RuntimeType
public static Object interceptor(@This Object proxy, @AllArguments Object[] allArguments, @Origin Method method,
@SuperCall Callable<?> callable, @Morph MorphingCallable<?> overrideCallable) throws Exception {
long start = System.currentTimeMillis();
System.err.println("執(zhí)行對象:" + proxy);
System.err.println("執(zhí)行方法:" + method);
if (Objects.nonNull(allArguments)) {
for (Object argument : allArguments) {
System.err.println("執(zhí)行參數:" + argument);
if (Objects.nonNull(argument)) {
System.out.println("執(zhí)行參數對象:" + argument.getClass());
}
}
}
try {
Object o = null;
if (Objects.nonNull(allArguments)) {
//可以修改參數: allArguments[1] = "cold";
o = overrideCallable.call(allArguments);
} else {
o = callable.call();
}
System.err.println("執(zhí)行結果:" + o);
if (Objects.nonNull(o)) {
System.out.println("執(zhí)行結果對象:" + o.getClass());
}
return o;
} finally {
System.out.println("method: " + method + ",cost: " + (System.currentTimeMillis() - start));
}
}
}
靜態(tài)類攔截
使用bytebuddy的redefine機制可以對靜態(tài)方法進行代理或增強斧抱,但是redefine的前提是當前類未被加載過,因此你需要在你的程序中把握增強的時機(確保在類實際加載前執(zhí)行redefine方法渐溶,我們選擇的加載處是在java agent的premain方法中)辉浦。同時需要通過使用bytebuddy的類型池機制代替直接獲取class,可參考的代碼如下:
TypePool.Resolution describe = TypePool.Default.ofSystemLoader().describe("AbstractCodec");
new ByteBuddy().redefine(describe.resolve(), ClassFileLocator.ForClassLoader.ofSystemLoader())
.method(ElementMatchers.named("checkPayload"))
.intercept(MethodDelegation.to(MyInterceptor.class))
.make().load(ClassLoader.getSystemClassLoader(), ClassLoadingStrategy.Default.INJECTION);