42 不調用給定類的構造方法創建給定類的對象

前言

前幾天, 看 R大 的一篇文章的時候, 發現了一篇有趣的文章 : https://rednaxelafx.iteye.com/blog/850082

關於這篇文章的重點 反序列化 來創建對象的細節, 我也還是想了解了解 

我之前以爲的就是 序列化就是 先把對象的相關元數據 以及 相關的需要存儲的數據信息 序列華爲字節序列, 然後 反序列化的時候 讀取相關元數據信息, 創建給定的 對象, 然後 在讀取序列化的數據相關信息, set 到對象的相關字段上面, 然後 完成了 對象 到 字節序列的相互轉換 

但是 R大 這麼一說, 感覺 這個反序列化的過程 好像不是這麼簡單啊  

更多更高深的理解, 請參見 R大 的文章 

以下內容 相當於是 讀了 R大 的文章的一些讀後需要記錄的東西, 以便於加深印象吧 

以下代碼基於 : jdk 1.7_40 

 

測試代碼

import com.hx.test01.Test21Unsafe;
import sun.misc.Unsafe;

import java.io.*;

/**
 * Test07UnsafeAllocate
 *
 * @author Jerry.X.He <[email protected]>
 * @version 1.0
 * @date 2019/6/8 10:48
 */
public class Test07UnsafeAllocate {

    // Test07UnsafeAllocate
    public static void main(String[] args) throws Exception {

        Unsafe unsafe = Test21Unsafe.getUnsafe();

        // 1. create an object do not call constructor
//        Person p1 = new Person();
        Person p1 = new Person("xx", "12");
        Person p2 = (Person) unsafe.allocateInstance(Person.class);

        // 2. object deserialize
        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(buffer);
        oos.writeObject(p1);

        byte[] byteTransfered = buffer.toByteArray();
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(byteTransfered));
        Object p3 = ois.readObject();


        System.out.println("end ...");

    }

    /**
     * Person
     *
     * @author Jerry.X.He <[email protected]>
     * @version 1.0
     * @date 2019/6/8 10:52
     */
    private static class Person extends Creature implements Serializable {

        /**
         * attrs
         */
        private String name;
        private String age;

//        public Person() {
//            name = "xx";
//            age = "12";
//            System.out.println("person created ");
//        }

        public Person(String name, String age) {
            this.name = name;
            this.age = age;
            System.out.println("person created ");
        }
    }

    /**
     * Creature
     *
     * @author Jerry.X.He <[email protected]>
     * @version 1.0
     * @date 2019/6/8 11:22
     */
    private static class Creature {
        public Creature() {
            System.out.println("creature created ");
        }
    }

}

 

以上程序運行結果如下

creature created 
person created 
creature created 
end ...

p1 調用 Person 的構造方法, 打印了前兩句 

p2 沒有調用構造方法, 沒有輸出 

p3 調用的 Creature 的構造方法, 打印了第三句 

 

 

以上代碼, 主要有兩個部分 

1. 通過 unsafe. allocateInstance 來創建給定的對象, 而不初始化 

2. 具體的反序列化來創建給定的對象 

 

 

通過 unsafe. allocateInstance 來創建給定的對象, 而不初始化 

第一部分 是使用 Unsafe 的相關 api 來創建給一個對象, 但是並不初始化 

這部分 對應的代碼 應該是 

unsafe.cpp 

UNSAFE_ENTRY(jobject, Unsafe_AllocateInstance(JNIEnv *env, jobject unsafe, jclass cls))
  UnsafeWrapper("Unsafe_AllocateInstance");
  {
    ThreadToNativeFromVM ttnfv(thread);
    return env->AllocObject(cls);
  }
UNSAFE_END

 

jni.cpp 

JNI_ENTRY(jobject, jni_AllocObject(JNIEnv *env, jclass clazz))
  JNIWrapper("AllocObject");

#ifndef USDT2
  DTRACE_PROBE2(hotspot_jni, AllocObject__entry, env, clazz);
#else /* USDT2 */
  HOTSPOT_JNI_ALLOCOBJECT_ENTRY(
                                env, clazz);
#endif /* USDT2 */
  jobject ret = NULL;
  DT_RETURN_MARK(AllocObject, jobject, (const jobject&)ret);

  instanceOop i = alloc_object(clazz, CHECK_NULL);
  ret = JNIHandles::make_local(env, i);
  return ret;
JNI_END

static instanceOop alloc_object(jclass clazz, TRAPS) {
  KlassHandle k(THREAD, java_lang_Class::as_klassOop(JNIHandles::resolve_non_null(clazz)));
  Klass::cast(k())->check_valid_for_instantiation(false, CHECK_NULL);
  instanceKlass::cast(k())->initialize(CHECK_NULL);
  instanceOop ih = instanceKlass::cast(k())->allocate_instance(THREAD);
  return ih;
}

這部分 就不說細節了, 可以 看到這裏面 僅僅是分配了一個 給定的 Class 實例的空間 

 

 

具體的反序列化來創建給定的對象 

這部分 主要是從序列化和反序列化的相關實現 開始吧, 以下內容 都僅僅特定描述了 上面單元測試的相關代碼, 其他的一些 特殊情況以下內容是沒有考慮的 

ObjectOutputStream. writeObject 

 

以上 oos.writeObject 到 buffer 裏面的字節序列如下 

# 序列化的時候字節的概覽 
$streamMagic, $streamVersion, $TC_OBJECT, $TC_CLASSDESC, $classDesc, $TC_ENDBLOCKDATA, $superDesc[recursely] 
$serializeData 
	$TC_STRING, $len, $xx 
	$TC_STRING, $len, $12 
# 完整的字節序列 : -84 -19 0 5 115 114 0 52 99 111 109 46 104 120 46 116 101 115 116 48 56 82 101 100 110 97 120 101 108 97 70 88 46 84 101 115 116 48 55 85 110 115 97 102 101 65 108 108 111 99 97 116 101 36 80 101 114 115 111 110 93 109 79 61 -74 82 -101 30 2 0 2 76 0 3 97 103 101 116 0 18 76 106 97 118 97 47 108 97 110 103 47 83 116 114 105 110 103 59 76 0 4 110 97 109 101 113 0 126 0 1 120 112 116 0 2 49 50 116 0 2 120 120

# 拆解的各個字節的意義 
$streamMagic
-84 -19 
$streamVersion
0 5 

$TC_OBJECT
115 

$TC_CLASSDESC
114 
	$nameLen 
	0 52 
	$className : com.hx.test08RednaxelaFX.Test07UnsafeAllocate$Person 
	99 111 109 46 104 120 46 116 101 115 116 48 56 82 101 100 110 97 120 101 108 97 70 88 46 84 101 115 116 48 55 85 110 115 97 102 101 65 108 108 111 99 97 116 101 36 80 101 114 115 111 110 
	$serialVersionUid : 6732124144459225886 
	93 109 79 61 -74 82 -101 30 
	$flags : $SC_SERIALIZABLE 
	2 
	
	$fieldsLen 
	0 2 
	$typeOf 'Ljava/lang/String;'
	76 
	$ageLen 
	0 3 
	$fieldAge : age 
	97 103 101 
	$TC_STRING, $len, Ljava/lang/String;  
	116 0 18 76 106 97 118 97 47 108 97 110 103 47 83 116 114 105 110 103 59 
	
	$typeOf 'Ljava/lang/String;'
	76 
	$nameLen 
	0 4 
	$fieldAge : age 
	110 97 109 101
	$TC_REFERENCE $handleOf'Ljava/lang/String;'
	113 0 126 0 1 

$TC_ENDBLOCKDATA
120 
$superDesc[recursely] 
112 

$TC_STRING, $len, $xx 
116 0 2 49 50 
$TC_STRING, $len, $12 
116 0 2 120 120 

 

ObjectInputStream. readObject 

 

 ois.readObject 這邊基本上就是, 讀取給定的類的元數據的信息, 然後創建對象, 最後 apply序列化保存的數據 

 

1. 實例化對象的方式 

desc. newInstance 創建對象 

 

ObjectStreamClass. getSerializableConstructor : 獲取序列化構造器, 尋找的是 給定的 clazz 的第一個沒有實現 Serializeable 的 無參構造方法 

 

ReflectionFactory. newConstructorForSerialization : 運行時創建構造方法 

 

生成出來的構造方法邏輯如下 

可以 看出這裏ConstructorAccessor 和 R大 的字節碼, 僞代碼 是差不多的, 只是我這裏多了一個 Test07UnsafeAllocate.$Creature 的層級 

 

2. apply 對象的屬性的方式

使用的是 unsafe 的相關 api 來設置的給定的對象的屬性 

 

 

拿取運行時 .class 的方式 : 通過 HSDB 直接創建給定的 class, 默認是放到 當前用戶目錄, 就比如 : "C:\Users\Jerry.X.He"

        或者 是 "jdk1.x/bin" 下面, add at 2019.07.14

反編譯給定的字節碼文件 : javap -c foo.class

 

 

完 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章