java字節(jié)碼增強調研筆記

字節(jié)碼增強,實質就是在編譯期或運行期進行<font color="#D46B08">字節(jié)碼插樁</font>备典,以便在運行期影響程序的<font color="#D46B08">執(zhí)行行為</font>诫欠。按照增強時機,可以分為<font color="red">編譯時增強</font>(Pluggable Annotation Processing)莱褒,<font color="red">運行時增強</font>(代理類击困,java-agent)。

屏幕快照 2022-04-19 上午11.42.08.png

實現方式:

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>


1649748593820-14ef8d6f-d376-415b-9341-b4de50327291.png

轉換鏈

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é)碼端朵。

工作步驟

bytecode (1).png

ClassPool容器

  1. getDefault (): 返回默認的ClassPool ,單例模式燃箭,一般通過該方法創(chuàng)建我們的ClassPool冲呢;
  2. appendClassPath(ClassPath cp), insertClassPath(ClassPath cp) : 將一個ClassPath加到類搜索路徑的末尾位置或插入到起始位置。通常通過該方法寫入額外的類搜索路徑招狸,以解決多個類加載器環(huán)境中找不到類問題敬拓;
  3. importPackage(String packageName):導入包;
  4. makeClass(String classname):創(chuàng)建一個空類裙戏,沒有變量和方法乘凸,后序通過CtClass的函數進行添加;
  5. get(String classname)累榜、getCtClass(String classname) : 根據類路徑名獲取該類的CtClass對象营勤,用于后續(xù)的編輯。

CtClass類

  1. debugDump;String類型信柿,如果生成冀偶。class文件,保存在這個目錄下渔嚷。
  2. setName(String name):給類重命名;
  3. setSuperclass(CtClass clazz):設置父類;
  4. addField(CtField f, Initializer init):添加字段(屬性)进鸠,初始值見CtField;
  5. addMethod(CtMethod m):添加方法(函數)形病;
  6. toBytecode(): 返回修改后的字節(jié)碼客年。需要注意的是一旦調用該方法,則無法繼續(xù)修改CtClass漠吻;
  7. toClass(): 將修改后的CtClass加載至當前線程的上下文類加載器中量瓜,CtClass的toClass方法是通過調用本方法實現。需要注意的是一旦調用該方法途乃,則無法繼續(xù)修改已經被加載的CtClass绍傲;
  8. writeFile(String directoryName):根據CtClass生成 .class 文件;
  9. defrost():解凍類耍共,用于使用了toclass()烫饼、toBytecode、writeFile()试读,類已經被JVM加載杠纵,Javassist凍結CtClass后;
  10. detach():避免內存溢出钩骇,從ClassPool中移除一些不需要的CtClass比藻。

Loader類加載器

  1. loadClass(String name):加載類

CtField字段

  1. CtField(CtClass type, String name, CtClass declaring) :構造函數铝量,添加字段類型,名稱银亲,所屬的類;
  2. CtField.Initializer constant():CtClass使用addField時初始值的設置;
  3. setModifiers(int mod):設置訪問級別慢叨,一般使用Modifier調用常量。

CtMethod方法

  1. insertBefore(String src):在方法的起始位置插入代碼群凶;
  2. insertAfter(String src):在方法的所有 return 語句前插入代碼以確保語句能夠被執(zhí)行插爹,除非遇到exception;
  3. insertAt(int lineNum, String src):在指定的位置插入代碼请梢;
  4. addCatch(String src, CtClass exceptionType):將方法內語句作為try的代碼塊赠尾,插入catch代碼塊src;
  5. setBody(String src):將方法的內容設置為要寫入的代碼,當方法被 abstract修飾時毅弧,該修飾符被移除气嫁;
  6. setModifiers(int mod):設置訪問級別,一般使用Modifier調用常量;
  7. invoke(Object obj, Object... args):反射調用字節(jié)碼生成類的方法够坐。
//對于setBody $0代表this $1寸宵、$2、...代表方法的第幾個參數
setBody("{$0.name = $1;}");

$符號含義

符號 含義
0,1, $2, ... this,第幾個參數
$args 參數列表. $args的類型是Object[].
$$ 所有實參.例如, m($$) 等價于 m(1,2,...)
$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);

參考

https://xie.infoq.cn/article/d367c19896e4cef6fbb661cf7

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末茎辐,一起剝皮案震驚了整個濱河市宪郊,隨后出現的幾起案子,更是在濱河造成了極大的恐慌拖陆,老刑警劉巖弛槐,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異依啰,居然都是意外死亡乎串,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進店門孔飒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來灌闺,“玉大人,你說我怎么就攤上這事坏瞄」鸲裕” “怎么了?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵鸠匀,是天一觀的道長蕉斜。 經常有香客問我,道長缀棍,這世上最難降的妖魔是什么宅此? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮爬范,結果婚禮上父腕,老公的妹妹穿的比我還像新娘。我一直安慰自己青瀑,他們只是感情好璧亮,可當我...
    茶點故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布萧诫。 她就那樣靜靜地躺著,像睡著了一般枝嘶。 火紅的嫁衣襯著肌膚如雪帘饶。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天群扶,我揣著相機與錄音及刻,去河邊找鬼。 笑死竞阐,一個胖子當著我的面吹牛缴饭,可吹牛的內容都是我干的。 我是一名探鬼主播馁菜,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼茴扁,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了汪疮?” 一聲冷哼從身側響起峭火,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎智嚷,沒想到半個月后卖丸,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡盏道,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年稍浆,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片猜嘱。...
    茶點故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡衅枫,死狀恐怖,靈堂內的尸體忽然破棺而出朗伶,到底是詐尸還是另有隱情弦撩,我是刑警寧澤,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布论皆,位于F島的核電站益楼,受9級特大地震影響,放射性物質發(fā)生泄漏点晴。R本人自食惡果不足惜感凤,卻給世界環(huán)境...
    茶點故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望粒督。 院中可真熱鬧陪竿,春花似錦、人聲如沸屠橄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至庸蔼,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間贮匕,已是汗流浹背姐仅。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留刻盐,地道東北人掏膏。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像敦锌,于是被迫代替她去往敵國和親馒疹。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,914評論 2 355

推薦閱讀更多精彩內容