java字節碼增強調研筆記

字節碼增強,實質就是在編譯期或運行期進行<font color="#D46B08">字節碼插樁</font>,以便在運行期影響程序的<font color="#D46B08">執行行為</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

字節碼工具 java-proxy asm Javassist cglib bytebuddy
類創建 支持 支持 支持 支持 支持
實現接口 支持 支持 支持 支持 支持
方法調用 支持 支持 支持 支持 支持
類擴展 不支持 支持 支持 支持 支持
父類方法調用 不支持 支持 支持 支持 支持
優點 容易上手,<font color="red">簡單動態代理首選</font> <font color="red">任意字節碼插入,幾乎不受限制</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>

使用場景:

由于字節碼增強可以在完全不侵入業務代碼的情況下植入代碼邏輯,常見的場景:

  • <font color="red">動態代理;</font>
  • <font color="red">熱部署;</font>
  • <font color="red">調用鏈跟蹤埋點;</font>
  • <font color="red">動態插入log(性能監控);</font>
  • <font color="red">測試代碼覆蓋率跟蹤;</font>

java-proxy介紹

java動態代理基于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;
            }
        });

原味咖啡生成的字節碼

//將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插入字節碼是原始字節碼語法,上手難度高,原始語法不受框架限制,任意插入字節碼;
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;

打印類字節碼

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 將所有事件轉發給 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 參數,可以改變一個字段 或一個方 法的修飾字段或名字。另外,除了在轉發的方法調用中使用經過修改的參數之外,還 可以選擇根 本不轉發該調用。其效果就是相應的類元素被移除。

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,當時執行效率上比ASM要差,因為ASM上直接操作的字節碼。

工作步驟

bytecode (1).png

ClassPool容器

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

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(): 返回修改后的字節碼。需要注意的是一旦調用該方法,則無法繼續修改CtClass
  7. toClass(): 將修改后的CtClass加載至當前線程的上下文類加載器中,CtClass的toClass方法是通過調用本方法實現。需要注意的是一旦調用該方法,則無法繼續修改已經被加載的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 語句前插入代碼以確保語句能夠被執行,除非遇到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):反射調用字節碼生成類的方法。
//對于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的循環
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方式改寫字節碼
  • 所有的操作依賴DynamicType.Builder進行,創建不可變的對象

ElementMatchers(ElementMatcher)

  • 提供一系列的元素匹配的工具類(named/any/nameEndsWith等等)
  • ElementMatcher(提供對類型、方法、字段、注解進行matches的方式,類似于Predicate)
  • Junction對多個ElementMatcher進行了and/or操作

DynamicType(動態類型,所有字節碼操作的開始,非常值得關注)

  • Unloaded(動態創建的字節碼還未加載進入到虛擬機,需要類加載器進行加載)
  • Loaded(已加載到jvm中后,解析出Class表示)
  • Default(DynamicType的默認實現,完成相關實際操作)

Implementation(用于提供動態方法的實現)

  • FixedValue(方法調用返回固定值)
  • MethodDelegation(方法調用委托,支持兩種方式: Class的static方法調用、object的instance method方法調用)

Builder(用于創建DynamicType,相關接口以及實現后續待詳解)

  • MethodDefinition
  • FieldDefinition
  • AbstractBase

常用注解說明

注解 說明
@Argument 綁定單個參數
@AllArguments 綁定所有參數的數組
@This 當前被攔截的、動態生成的那個對象
@Super 當前被攔截的、動態生成的那個對象的父類對象
@Origin 可以綁定到以下類型的參數:Method 被調用的原始方法 Constructor 被調用的原始構造器 Class 當前動態創建的類 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 + " 喝掉原味咖啡";
    }
}

生成原味咖啡類字節碼

public class AnimalCoffee$ByteBuddy$ySIOBpxK implements AnimalCoffee, CoffeeNameAware {
    public boolean equals(Object var1) {
    public String toString() {
        return "原味咖啡";
    }
    //FixedValue.value 引用為靜態對象的引用
    public Object make() {
        return value$shoqck1;
    }
    public String getName() {
        return "原味咖啡";
    }
    //MethodDelegation.to 引用為靜態代理對象的方法
    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();
//創建有構造參數的對象   
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);
    }
}

摩卡咖啡代理類字節碼

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 參數來調用目標方法時,是無法動態修改參數的,如果想要動態修改參數,則需要用到
     * @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("執行對象:" + proxy);
        System.err.println("執行方法:" + method);
        if (Objects.nonNull(allArguments)) {
            for (Object argument : allArguments) {
                System.err.println("執行參數:" + argument);
                if (Objects.nonNull(argument)) {
                    System.out.println("執行參數對象:" + argument.getClass());
                }
            }
        }
        try {
            Object o = null;
            if (Objects.nonNull(allArguments)) {
                //可以修改參數: allArguments[1] = "cold";
                o = overrideCallable.call(allArguments);
            } else {
                o = callable.call();
            }
            System.err.println("執行結果:" + o);
            if (Objects.nonNull(o)) {
                System.out.println("執行結果對象:" + o.getClass());
            }
            return o;
        } finally {
            System.out.println("method: " + method + ",cost: " + (System.currentTimeMillis() - start));
        }
    }
}

靜態類攔截

使用bytebuddy的redefine機制可以對靜態方法進行代理或增強,但是redefine的前提是當前類未被加載過,因此你需要在你的程序中把握增強的時機(確保在類實際加載前執行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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容