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 <970655147@qq.com>
 * @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 <970655147@qq.com>
     * @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 <970655147@qq.com>
     * @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

 

 

完 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值