ASM——運(yùn)行時(shí)/編譯時(shí)動(dòng)態(tài)修改class源碼

簡述

最近在看阿里的ARouter的源碼,從git上clone下來之后,run起來發(fā)現(xiàn)項(xiàng)目運(yùn)行的效果和源碼有明顯區(qū)別。打個(gè)比方,源碼是這樣

boolean b = true;
System.out.println(b);

但是當(dāng)你跑起來之后去發(fā)現(xiàn)打印出來的false,打開編譯好的class文件卻發(fā)現(xiàn)編譯出來的class的代碼和源碼不一樣。經(jīng)過翻看ARouter的工程源碼,發(fā)現(xiàn)其實(shí)ARouter是利用了Gradle的 Transform API和ASM共同完成的編譯時(shí)修改源碼的功能。

Transform API的功能是讓你在java文件編譯成class文件之后對這些class文件進(jìn)行讀寫,發(fā)生在編譯時(shí),是Android的gradle打包插件自帶的功能,這里不詳細(xì)展開。本片文章主要是講解ASM的基本使用方法。有機(jī)會(huì)會(huì)出一個(gè)Transform + ASM插件教程。

ASM簡介

ASM 是一個(gè) Java 字節(jié)碼操控框架。它能夠以二進(jìn)制形式修改已有類或者動(dòng)態(tài)生成類。ASM 可以直接產(chǎn)生二進(jìn)制 class 文件,也可以在類被加載入 Java 虛擬機(jī)之前動(dòng)態(tài)改變類行為。ASM 從類文件中讀入信息后,能夠改變類行為,分析類信息,甚至能夠根據(jù)用戶要求生成新類。許多AOP框架以及動(dòng)態(tài)修改字節(jié)碼的庫的底層都是由ASM實(shí)現(xiàn)的,例如Spring AOP,cglib等等

一句話概括:ASM可以動(dòng)態(tài)的修改創(chuàng)建class文件,達(dá)到動(dòng)態(tài)修改java代碼的效果。

ASM 核心API

public abstract class ClassVisitor {
  // 實(shí)現(xiàn)的ASM的API版本。該字段的值必須為如下幾個(gè)之一:Opcodes.ASM4,ASM5,ASM6,ASM7
  protected final int api;
  // 該類的方法可以委托給子類
  protected ClassVisitor cv;
  // 構(gòu)造器 
  public ClassVisitor(final int api) {
    this(api, null);
  }
  // 構(gòu)造器
  public ClassVisitor(final int api, final ClassVisitor classVisitor) {
    if (api != Opcodes.ASM6 && api != Opcodes.ASM5 && api != Opcodes.ASM4 && api != Opcodes.ASM7) {
      throw new IllegalArgumentException();
    }
    this.api = api;
    this.cv = classVisitor;
  }
  /**
  * 訪問類頭部信息
  *
  * @param version
  *            類版本
  * @param access
  *            類訪問標(biāo)識(shí)符public等
  * @param name
  *            類名稱
  * @param signature
  *            類簽名(非泛型為NUll)
  * @param superName
  *            類的父類
  * @param interfaces
  *            類實(shí)現(xiàn)的接口
  */
  public void visit(
      final int version,final int access,
      final String name, final String signature,
      final String superName,final String[] interfaces) {
    if (cv != null) {
      cv.visit(version, access, name, signature, superName, interfaces);
    }
  }
  /**
  * 訪問類的源文件.
  *
  * @param source
  *            源文件名稱
  * @param debug
  *            附加的驗(yàn)證信息,可以為空
  */
  public void visitSource(final String source, final String debug) {
    if (cv != null) {
      cv.visitSource(source, debug);
    }
  }

  /**
  * 訪問與類對應(yīng)的模塊. ASM6之后才有的API
  *
  * @param name
  *            模塊名稱
  * @param access
  *            模式 ACC_MANDATED 等
  * @param version
  *            版本號
  */
  public ModuleVisitor visitModule(final String name, final int access, final String version) {
    if (api < Opcodes.ASM6) {
      throw new UnsupportedOperationException("This feature requires ASM6");
    }
    if (cv != null) {
      return cv.visitModule(name, access, version);
    }
    return null;
  }

  public void visitNestHost(final String nestHost) {
    if (api < Opcodes.ASM7) {
      throw new UnsupportedOperationException("This feature requires ASM7");
    }
    if (cv != null) {
      cv.visitNestHost(nestHost);
    }
  }
  /**
  * 這個(gè)其實(shí)并不是訪問外部類的回調(diào),而是訪問方法體中含有匿名內(nèi)部類的方法
  *
  * @param owner 為創(chuàng)建匿名類的類,當(dāng)然其也是一個(gè)enclosing class類型的類
  * @param name 創(chuàng)建匿名類的方法。
  * @param desc 創(chuàng)建匿名類的方法描述信息。
  * @return 返回一個(gè)注解值訪問器
  */
  public void visitOuterClass(final String owner, final String name, final String descriptor) {
    if (cv != null) {
      cv.visitOuterClass(owner, name, descriptor);
    }
  }
  /**
  * 訪問類的注解
  *
  * @param desc
  *            注解類的類描述
  * @param visible
  *            runtime時(shí)期注解是否可以被訪問
  * @return 返回一個(gè)注解值訪問器
  */
  public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible) {
    if (cv != null) {
      return cv.visitAnnotation(descriptor, visible);
    }
    return null;
  }
  /**
  * 訪問標(biāo)注在類型上的注解
  *
  * @param typeRef
  * @param typePath
  * @param desc
  * @param visible
  * @return
  */
  public AnnotationVisitor visitTypeAnnotation(
      final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
    if (api < Opcodes.ASM5) {
      throw new UnsupportedOperationException("This feature requires ASM5");
    }
    if (cv != null) {
      return cv.visitTypeAnnotation(typeRef, typePath, descriptor, visible);
    }
    return null;
  }
  /**
  * 訪問一個(gè)類的屬性
  *
  * @param attribute
  *            類的屬性
  */
  public void visitAttribute(final Attribute attribute) {
    if (cv != null) {
      cv.visitAttribute(attribute);
    }
  }

  public void visitNestMember(final String nestMember) {
    if (api < Opcodes.ASM7) {
      throw new UnsupportedOperationException("This feature requires ASM7");
    }
    if (cv != null) {
      cv.visitNestMember(nestMember);
    }
  }
  /**
  * 訪問內(nèi)部類信息
  * @param name
  * @param outerName
  * @param innerName
  * @param access
  */
  public void visitInnerClass(
      final String name, final String outerName, final String innerName, final int access) {
    if (cv != null) {
      cv.visitInnerClass(name, outerName, innerName, access);
    }
  }
  /**
  * 訪問類的字段
  * @param access
  * @param name
  * @param desc
  * @param signature
  * @param value
  * @return
  */
  public FieldVisitor visitField(
      final int access,
      final String name,
      final String descriptor,
      final String signature,
      final Object value) {
    if (cv != null) {
      return cv.visitField(access, name, descriptor, signature, value);
    }
    return null;
  }
  /**
  * 訪問類的方法
  * @param access
  * @param name
  * @param desc
  * @param signature
  * @param exceptions
  * @return
  */
  public MethodVisitor visitMethod(
      final int access,
      final String name,
      final String descriptor,
      final String signature,
      final String[] exceptions) {
    if (cv != null) {
      return cv.visitMethod(access, name, descriptor, signature, exceptions);
    }
    return null;
  }

 /**
  * 訪問類結(jié)束
  */
  public void visitEnd() {
    if (cv != null) {
      cv.visitEnd();
    }
  }
}

ClassVisitor 的調(diào)用必須是遵循下面的調(diào)用順序的:

 visit visitSource? visitOuterClass? ( visitAnnotation | visitAttribute )*
( visitInnerClass | visitField | visitMethod )*
visitEnd

圍繞著ClassVisitor ,還有兩個(gè)核心類: 后續(xù)的例子代碼中可以看到,我們必須先調(diào)用visit方法,這就因?yàn)閏lass是字節(jié)流的二進(jìn)制文件,而我們解析和生成也是要遵循一定的順序。ClassVisitor定義了我們需要操作的所有接口,并且ClassVisitor也可以接收一個(gè)ClassVisitor實(shí)例來構(gòu)造,有點(diǎn)類似于一個(gè)事件的filter,可以套很多層的filter來一層層處理邏輯。

1、ClassReader 將class解析成byte 數(shù)組,然后會(huì)通過accept方法去按順序調(diào)用綁定對象(繼承了ClassVisitor的實(shí)例)的方法。可以視為一個(gè)事件的生產(chǎn)者。

2、ClassWriter 是ClassVisitor 的子類。直接可以通過toByteArray()方法以返回的byte數(shù)組形式構(gòu)建編譯后的class。可以視為一個(gè)事件的消費(fèi)者。

但是需要注意的是雖然ClassReader和ClassWriter 看起來像是對稱類例如InputStream和OutputStream但其實(shí)類結(jié)構(gòu)上并無關(guān)聯(lián),ClassWriter 繼承于ClassVisitor,而ClassReader 直接繼承于Object,只是提供解析class,并依次調(diào)用ClassVisitor對象。也就是說ClassReader的api和ClassWriter 的api基本沒有相關(guān)性。

另外補(bǔ)充一下,ASM中常見參數(shù)desc直譯是描述,但是作用其實(shí)時(shí)限定方法的輸入?yún)?shù)和返回參數(shù)類型,比如"()V"是無輸入無輸出,"(I)Ljava/lang/String;"是輸入int,返回String。

無中生有 ——利用ASM動(dòng)態(tài)創(chuàng)建一個(gè)類

由于是憑空創(chuàng)建,所以只需要ClassWriter 即可。

先上目標(biāo)代碼,我們的目的是創(chuàng)造一個(gè)下面的類

public class Student{
    public int age = 11;

    public int getAge() {
        return age;
    }
}

一個(gè)特別簡單的javabean類。

簡單說一下創(chuàng)建流程:

  1. 創(chuàng)建一個(gè)類需要先調(diào)用visit創(chuàng)建類的頭部信息。
  2. 分別調(diào)用visitMethod或visitField生成需要的創(chuàng)建的方法或者字段。
  3. 調(diào)用visitEnd結(jié)束類的創(chuàng)建
  4. 調(diào)用ClassWriter 的toByteArray將動(dòng)態(tài)生成的class轉(zhuǎn)為byte[]數(shù)組,可以用ClassLoader動(dòng)態(tài)載入,或者寫出成.class文件
    完整代碼:
public byte[] createNewClass() {
        //創(chuàng)建ClassWriter ,構(gòu)造參數(shù)的含義是是否自動(dòng)計(jì)算棧幀,操作數(shù)棧及局部變量表的大小
        //0:完全手動(dòng)計(jì)算 即手動(dòng)調(diào)用visitFrame和visitMaxs完全生效
        //ClassWriter.COMPUTE_MAXS=1:需要自己計(jì)算棧幀大小,但本地變量與操作數(shù)已自動(dòng)計(jì)算好,當(dāng)然也可以調(diào)用visitMaxs方法,只不過不起作用,參數(shù)會(huì)被忽略;
        //ClassWriter.COMPUTE_FRAMES=2:棧幀本地變量和操作數(shù)棧都自動(dòng)計(jì)算,不需要調(diào)用visitFrame和visitMaxs方法,即使調(diào)用也會(huì)被忽略。
        //這些選項(xiàng)非常方便,但會(huì)有一定的開銷,使用COMPUTE_MAXS會(huì)慢10%,使用COMPUTE_FRAMES會(huì)慢2倍。
        ClassWriter cw = new ClassWriter(0);
        //創(chuàng)建類頭部信息:jdk版本,修飾符,類全名,簽名信息,父類,接口集
        cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "asm/Student", null, "java/lang/Object", null);
        //創(chuàng)建字段age:修飾符,變量名,類型,簽名信息,初始值(不一定會(huì)起作用后面會(huì)說明)
        cw.visitField(Opcodes.ACC_PUBLIC , "age", "I", null, new Integer(11))
                .visitEnd();
        //創(chuàng)建方法:修飾符,方法名,類型,描述(輸入輸出類型),簽名信息,拋出異常集合
        // 方法的邏輯全部使用jvm指令來書寫的比較晦澀,門檻較高,后面會(huì)介紹簡單的方法
        MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "getAge", "()I", null, null);
        // 創(chuàng)建方法第一步
        mv.visitCode();
        // 將索引為 #0 的本地變量列表加到操作數(shù)棧下。#0 索引的本地變量列表永遠(yuǎn)是 this ,當(dāng)前類實(shí)例的引用。
        mv.visitVarInsn(ALOAD, 0);
        // 獲取變量的值,
        mv.visitFieldInsn(GETFIELD, "asm/Student", "age", "I");
        // 返回age
        mv.visitInsn(IRETURN);
        // 設(shè)置操作數(shù)棧和本地變量表的大小
        mv.visitMaxs(1, 1);
        //結(jié)束方法生成
        mv.visitEnd();
        //結(jié)束類生成
        cw.visitEnd();
        //返回class的byte[]數(shù)組
        return cw.toByteArray();
    }

通過以上代碼可以看出其實(shí)類以及字段的創(chuàng)建還是比較簡單的,難點(diǎn)在于方法的創(chuàng)建上。如果對于jvm指令集不熟悉基本抓瞎。這里介紹一個(gè)方法,先手寫目標(biāo)類,即Student的java文件,然后用javac編譯成class文件(或者用IDE編譯),找到編譯好的class文件,用javap -c Student打開class文件。輸出如下:

public class asm.Student {
  public int age;

  public asm.Student();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: aload_0
       5: bipush        10
       7: putfield      #2                  // Field age:I
      10: return

  public int getAge();
    Code:
       0: aload_0
       1: getfield      #2                  // Field age:I
       4: ireturn
}

可以看到j(luò)vm編譯時(shí)幫助Student補(bǔ)全了構(gòu)造方法Student(),著重看getAge的指令代碼

       0: aload_0
       1: getfield      #2                  // Field age:I
       4: ireturn

一共三條正好和生成方法的代碼對應(yīng)上

        mv.visitVarInsn(ALOAD, 0);
        mv.visitFieldInsn(GETFIELD, "asm/ASMDemo", "age", "I");
        mv.visitInsn(IRETURN);

當(dāng)沒有思路時(shí),可以用這參考這種辦法。
好了現(xiàn)在已經(jīng)生成了新的class的byte[],剩下的就是加載,驗(yàn)證了。
加載的代碼:

/**
  *用來加載byte[],由于defineClass不是public修飾的所以只能這樣寫。
  */
public class MyClassLoader extends ClassLoader {
  public Class getClassByBytes(byte[] bytes) {
        return defineClass(null, bytes, 0, bytes.length);
    }

  public static void main(String[] args) throws Exception {
        MyClassLoader myClassLoader = new MyClassLoader();
        Class classByBytes = myClassLoader.getClassByBytes(create());
        Object o = classByBytes.newInstance();
        Field field = classByBytes.getField("age");
        Object o1 = field.get(o);
        Method method = classByBytes.getMethod("getAge");
        Object o2 = method.invoke(o);
        System.out.println("Field age:  " + o1 );
        System.out.println("Method method :  " + o2);
    }
}

點(diǎn)擊運(yùn)行,然后你就會(huì)發(fā)現(xiàn)——華麗麗的報(bào)錯(cuò)了

Exception in thread "main" java.lang.InstantiationException: asm.CreateTest
    at java.lang.Class.newInstance(Class.java:427)
    at asm.ASMTest.main(ASMTest.java:20)
Caused by: java.lang.NoSuchMethodException: asm.CreateTest.<init>()
    at java.lang.Class.getConstructor0(Class.java:3082)
    at java.lang.Class.newInstance(Class.java:412)
    ... 1 more

asm.CreateTest.<init>()這個(gè)方法沒有找到,熟悉jvm的可能會(huì)知道其實(shí)<init>就是構(gòu)造函數(shù),構(gòu)造函數(shù)在jvm中會(huì)被重新命名成<init>。但是我們手寫的java文件時(shí)也沒有寫構(gòu)造函數(shù),為什么就可以呢?翻到上面貼出的用javac編譯出的Student文件,可以看到編譯時(shí)編譯器自動(dòng)幫我們加好了構(gòu)造函數(shù)。然后再把咱們自己生成的class文件的byte[]通過輸出流寫成class文件,在通過javap -c 查看:

 public class asm.Student{
  public int zero;

  public int getZero();
    Code:
       0: aload_0
       1: getfield      #11                 // Field zero:I
       4: ireturn
}

果然利用ASM生成的class里的確沒有構(gòu)造方法。ASM還是要比編譯器懶一些的,哈。既然沒有,咱們加上就行了。
先參考一下上面由java編譯成的class文件,其實(shí)構(gòu)造函數(shù)的代碼:

  public asm.Student();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: aload_0
       5: bipush        10
       7: putfield      #2                  // Field age:I
      10: return

簡單翻譯一下這6條指令:

  1. this變量入棧
  2. 執(zhí)行父類的<init>方法
  3. this再次入棧
  4. byte變量10入棧
  5. 給對象字段age賦值
  6. 方法結(jié)束

如此可以看到其實(shí)構(gòu)造函數(shù)最核心的指令就會(huì)調(diào)用父類的<init>方法(暫時(shí)不考慮字段賦值的事情)。現(xiàn)在基本能夠確定,我們手寫的構(gòu)造函數(shù)必須包含這三條指令

aload_0
invokespecial
return

然后和上面生成getAge方法類似的生成一個(gè)<init>方法即可,代碼如下:

        mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
        mv.visitCode();
        // aload_0
        mv.visitVarInsn(ALOAD, 0);
        // 獲取變量的值,
        mv.visitMethodInsn(INVOKESPECIAL,"java/lang/Object", "<init>", "()V", false);
        // 結(jié)束
        mv.visitInsn(IRETURN);
        // 設(shè)置操作數(shù)棧和本地變量表的大小
        mv.visitMaxs(1, 1);
        //結(jié)束方法生成
        mv.visitEnd();

然后再次運(yùn)行,發(fā)現(xiàn)已經(jīng)可以正常運(yùn)行,輸出如下

Field age:  0
Method method :  0

說好的11呢???哈,其實(shí)通過查看java文件編譯后的class就能發(fā)現(xiàn)全局變量的默認(rèn)值賦值其實(shí)是在構(gòu)造函數(shù)中進(jìn)行的,也就是說我們通過ASM創(chuàng)建字段時(shí)設(shè)置的默認(rèn)值沒起效果,WTF!再次修改<init>方法(類似getAge,不在添加詳細(xì)注釋)

mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
mv.visitVarInsn(ALOAD, 0);
mv.visitIntInsn(BIPUSH, 10);
mv.visitFieldInsn(PUTFIELD, "asm/Student", "age", "I");
mv.visitInsn(RETURN);
mv.visitMaxs(2, 1);
mv.visitEnd();

再次運(yùn)行

Field age:  10
Method method :  10

哈,完美運(yùn)行。那么可能有同學(xué)會(huì)問了,那么設(shè)置字段默認(rèn)值卵用沒有,為什么還有這個(gè)參數(shù)呢,其實(shí)也并不是一點(diǎn)用沒有,當(dāng)生成的字段時(shí)static時(shí),就會(huì)起作用。這里邊又會(huì)涉及到類的靜態(tài)變量加載時(shí)機(jī),<cinit>函數(shù)等等,這里就不展開細(xì)講了,否則篇幅該hold不住了。總結(jié)起來一句話:ASM只是工具,掌握jvm知識(shí)才是硬道理。

偷梁換柱——ASM修改已有的class

其實(shí)除了動(dòng)態(tài)生成class,還有一大部分需求是修改class,這里簡單介紹下最復(fù)雜的修改class的Method。其他的修改照葫蘆畫瓢就可以。
先上目標(biāo)效果,首先原始類還用咱們的Student:

public class Student{
    public int age = 11;

    public int getAge() {
        return age;
    }
}

目標(biāo)是在getAge里邊插入一句打印語句,即:

    public class Student{
        public int age = 11;

        public int getAge() {
            System.out.println("getAge");
            return age;
        }
    }

思路如下:

  1. 首先自定義一個(gè)ClassVisitor,重寫visitMethod,這樣就可以收到每個(gè)方法的回調(diào)
  2. 判斷方法名稱是不是getAge
  3. 如果是返回一個(gè)自定義的MethodVisitor
  4. 自定義的MethodVisitor重寫visitCode(訪問方法的第一個(gè)步驟)
  5. 添加相應(yīng)的邏輯
  6. 通過重寫visitMaxs修改操作數(shù)棧和局部變量表的大小(添加了邏輯可能會(huì)導(dǎo)致操作數(shù)棧和局部變量表的最大值增大)

代碼如下

public class MyMethodVisitor extends MethodVisitor {
    public MyMethodVisitor(MethodVisitor mv) {
        super(ASM5, mv);
    }


    @Override
    public void visitCode() {
        mv.visitCode();
        mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
        mv.visitLdcInsn("getAge");
        mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
    }

    @Override
    public void visitMaxs(int maxStack, int maxLocals) {
        super.visitMaxs(maxStack+1, maxLocals);
    }
}

public class MyClassVisitor extends ClassVisitor {
    public MyClassVisitor(ClassVisitor cv) {
        super(ASM5, cv);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        MethodVisitor methodVisitor = super.visitMethod(access, name, desc, signature, exceptions);
        if(name.equals("getAge")){
            return new MyMethodVisitor(methodVisitor);
        }else {
            return methodVisitor;
        }
    }
}

//修改測試代碼
public static void main(String[] args) throws Exception {
        ClassReader classReader = new ClassReader(createNewClass());
        ClassWriter classWriter = new ClassWriter(classReader, 0);
        ClassVisitor cv = new MyClassVisitor(classWriter);
        classReader.accept(cv,0);
        MyClassLoader myClassLoader = new MyClassLoader();
        Class classByBytes = myClassLoader.getClassByBytes(classWriter.toByteArray());
        Object o = classByBytes.newInstance();
        Field field = classByBytes.getField("age");
        Object o1 = field.get(o);
        Method method = classByBytes.getMethod("getAge");
        Object o2 = method.invoke(o);
        System.out.println("Field age:  " + o1);
        System.out.println("Method method :  " + o2);
    }

運(yùn)行:

getAge
Field age:  10
Method method :  10

注入的邏輯完美運(yùn)行!修改方法邏輯不僅僅可以在方法開始插入邏輯,包括方法結(jié)束時(shí),甚至方法體中間都可以,可以利用這種思路很方便的寫出一個(gè)AOP框架。

ASMifier

ASM由于是基于jvm指令集的所以比較晦澀。官方可能是考慮到大家都是比較菜的,提供了很多的工具類,這里只介紹一種我認(rèn)為最有用的:ASMifier。ASMifier最大的功能就是將一個(gè)java文件翻譯成ASM生成此文件的代碼。

ASMifier.main(new String[]{"asm.Student"});

運(yùn)行后,就可以在控制臺(tái)看見如何利用ASM生成Student類了,省了很大力氣。工具類很多就不一 一介紹了,推薦一個(gè)博客有興趣可以去看看:

https://blog.csdn.net/ljz2016/article/details/81363828

總結(jié)

ASM相對于一些其他的操作字節(jié)碼的框架偏底層了一些,只提供了一些低級api,要想熟練使用還是需要比較高的jvm知識(shí)的。但是作為其他操作字節(jié)碼的框架的底層實(shí)現(xiàn),還是非常有必要了解一下的。真實(shí)項(xiàng)目中如果對性能要求不是特別高的話,結(jié)合項(xiàng)目需求完全可以用其他高級庫代替ASM,例如cglib javassist。
突然想起來前兩年做過的一個(gè)需求:拿到一個(gè)類序列化之后的文件,然后在本地沒有這個(gè)類的情況下反序列化它。
當(dāng)時(shí)覺得這個(gè)需求真是扯淡,現(xiàn)在想想做反序列化時(shí)報(bào)出ClassNotFound這個(gè)錯(cuò)誤之前,其實(shí)已經(jīng)可以獲取類的包名,類名,簽名,以及字段詳情了。其實(shí)完全可以重寫反序列化方法,然后獲取到類的信息后動(dòng)態(tài)生成class文件,然后再加載到內(nèi)存中,之后再做正常的反序列化操作。兩年前的需求現(xiàn)在想出了解決方案,哈!

代碼地址:

https://github.com/fengao1004/ASM.git

參考:

https://blog.csdn.net/lijingyao8206/article/category/3276863

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,967評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,273評論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,870評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,742評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,527評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,010評論 1 322
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,108評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,250評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,769評論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,656評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,853評論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,371評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,103評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,472評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,717評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,487評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,815評論 2 372

推薦閱讀更多精彩內(nèi)容