在序列化的问题域里面有一个常见的问题,就是反序列化时用何种方式来创建Java对象,因为反序列化的目的是把一段二进制流转化成一个对象。
在Java里面创建对象有几种方式:
1. 显式地调用new语句, 比如 DemoClass demo = new DemoClass()
2. 利用反射机制,通过Class对象的newInstance()方法,比如DemoClass demo = DemoClass.class.newInstance()。 但是有个前提就是必须提供无参的构造函数
3. 利用反射机制,利用Constructor对象来创建对象
这三种方式本质上都是一样的,都是常规的Java创建对象的new机制,不管是显式地还是隐式的。一个new操作,编译成指令后是3条
第一条指令的意思是根据类型分配一块内存区域
第二条指令是把第一条指令返回的内存地址压入操作数栈顶
第三条指令是调用类的构造函数
new机制有个问题就是:. 当类只提供有参的构造函数时,必须使用这个有参的构造函数。
那么问题来了,当反序列化的时候,不可能使用显示地new操作,因为肯定地根据传过来的类型动态地调用。利用newInstance肯定没戏了,因为不能确定这个类是否提供了无参构造函数。只能第三种,利用反射机制,使用Constructor对象来创建对象。
但是Consturctor对象有个约束,就是需要提供参数的类型列表,然后使用Constructor.newInstance方法需要传递相应个数的参数。
在反序列化这个场景下,可以这么做:先根据反射获得Constructor的参数类型列表,然后根据每种类型,构造一个对应的默认值的列表,然后调用Constructor.newInstance()方法。这样可以创建出一个具有默认值的对象。
但是问题又来了,万一这个类的构造函数做了一些特别的操作,比如判断传入的参数的值,如果参数值不符合规范就抛异常,那么创建对象就失败了
public static void testConstructor(){
try {
Class[] cls = new Class[] { int.class, int.class };
Constructor c = DemoClass.class.getDeclaredConstructor(cls);
DemoClass obj = (DemoClass) c.newInstance(0, 0);
System.out.println(obj.getValue1());
System.out.println(obj.getValue2());
} catch (Exception e) {
e.printStackTrace();
}
}
public static void testConstructorWityParameterTypes(){
try {
Constructor[] c = DemoClass.class.getDeclaredConstructors();
Type[] parameterTypes = c[0].getGenericParameterTypes();
// 判断type类型,依次设置默认值
DemoClass obj = (DemoClass) c[0].newInstance(0, 0);
System.out.println(obj.getValue1());
System.out.println(obj.getValue2());
} catch (Exception e) {
e.printStackTrace();
}
}
这里提出一种使用sun.misc.Unsafe方法解决这个由于有参构造函数引起的创建Java对象的问题。Unsafe有一个allocateInstance(Class)方法,这个方法只需要传入一个类型就可以创建Java对象了,不正好完美的解决了我们的问题吗?
new操作被解析成了3个步骤,而Unsafe.allocateInstance()方法值做了第一步和第二步,即分配内存空间,返回内存地址,没有做第三步调用构造函数。所以Unsafe.allocateInstance()方法创建的对象都是只有初始值,没有默认值也没有构造函数设置的值,因为它完全没有使用new机制,直接操作内存创建了对象。下面看一个完整的例子,包括如何获得Unsafe对象。
在Eclipse里面引用sun.misc.Unsafe类需要设置一下 Preference --> Java --> Compiler --> Errors/Warnings --> Forbidden reference ,从Error改成Warning
package com.zc.lock;
import java.lang.reflect.Field;
import sun.misc.Unsafe;
public class UnsafeUtility {
private static Unsafe unsafe;
static {
try {
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
unsafe = (Unsafe) f.get(null);
} catch (Exception e) {
}
}
public static Unsafe getUnsafe(){
return unsafe;
}
}
一个测试用例
package com.zc.lock.test;
public class DemoClass {
private int value1;
private int value2 = 10;
public DemoClass(int value1, int value2){
this.value1 = value1;
this.value2 = value2;
}
public int getValue1() {
return value1;
}
public void setValue1(int value1) {
this.value1 = value1;
}
public int getValue2() {
return value2;
}
public void setValue2(int value2) {
this.value2 = value2;
}
}
package com.zc.lock.test;
import java.lang.reflect.Constructor;
import java.lang.reflect.Type;
import sun.misc.Unsafe;
import com.zc.lock.UnsafeUtility;
public class Main {
public static void main(String[] args){
// testNewObject();
// testNewInstance();
// testConstructor();
// testConstructorWityParameterTypes();
// testUnsafeAllocateInstance();
}
public static void testUnsafeAllocateInstance(){
Unsafe unsafe = UnsafeUtility.getUnsafe();
try {
DemoClass obj = (DemoClass)unsafe.allocateInstance(DemoClass.class);
System.out.println(obj.getValue1());
System.out.println(obj.getValue2());
obj.setValue1(1);
obj.setValue2(2);
System.out.println(obj.getValue1());
System.out.println(obj.getValue2());
} catch (InstantiationException e) {
e.printStackTrace();
}
}
public static void testNewObject(){
DemoClass obj = new DemoClass(1,2);
System.out.println(obj.getValue1());
System.out.println(obj.getValue2());
}
public static void testNewInstance(){
try {
DemoClass obj = DemoClass.class.newInstance();
System.out.println(obj.getValue1());
System.out.println(obj.getValue2());
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
public static void testConstructor(){
try {
Class[] cls = new Class[] { int.class, int.class };
Constructor c = DemoClass.class.getDeclaredConstructor(cls);
DemoClass obj = (DemoClass) c.newInstance(0, 0);
System.out.println(obj.getValue1());
System.out.println(obj.getValue2());
} catch (Exception e) {
e.printStackTrace();
}
}
public static void testConstructorWityParameterTypes(){
try {
Constructor[] c = DemoClass.class.getDeclaredConstructors();
Type[] parameterTypes = c[0].getGenericParameterTypes();
// 判断type类型,依次设置默认值
DemoClass obj = (DemoClass) c[0].newInstance(0, 0);
System.out.println(obj.getValue1());
System.out.println(obj.getValue2());
} catch (Exception e) {
e.printStackTrace();
}
}
}