答复: 不用构造方法也能创建对象

原帖:[url=http://www.iteye.com/topic/850027]不用构造方法也能创建对象[/url]

把之前我引用过的一段也贴上来:
[quote="RednaxelaFX"]嗯顺带推荐Effective Java, Second Edition的第74条
[quote]A second cost of implementing Serializable is that it increases the likelihood
of bugs and security holes. [color=red]Normally, objects are created using constructors;
serialization is an extralinguistic mechanism for creating objects. Whether
you accept the default behavior or override it, deserialization is a “hidden constructor”
with all of the same issues as other constructors.[/color] Because there is no
explicit constructor associated with deserialization, it is easy to forget that you
must ensure that it guarantees all of the invariants established by the constructors
and that it does not allow an attacker to gain access to the internals of the object
under construction. Relying on the default deserialization mechanism can easily
leave objects open to invariant corruption and illegal access (Item 76).[/quote][/quote]

在Java语言层面看,Java类的构造器只能通过两种方式调用,一个是通过new表达式,另一个是通过反射调用构造器。这两种方式对Java程序员来说都是“整体”的,但实际新建对象的动作分两步走:
1、创建出空对象(此时类型已经是正确的了),对应字节码是new
2、调用某个版本的构造器,对应字节码是invokespecial "<init>"。

默认的Java反序列化机制同样是分两步走,但变成:
1、创建出空对象(此时类型已经是正确的了);
2、调用用户定义的反序列化方法(readObject,如果有的话)或者调用默认反序列化方法。
这就是为什么反序列化可以看作是“隐藏的构造器”。

如果想自己试试去玩创建出空对象但却不调用构造器的,可以试试sun.misc.Unsafe.allocateInstance()
用Groovy控制台来演示一下:
Groovy Shell (1.7.2, JVM: 1.6.0_23)
Type 'help' or '\h' for help.
----------------------------------------------------------------
groovy:000> class Foo {
groovy:001> int value = 12345;
groovy:002> Foo() { println "foo ctor!" }
groovy:003> int getValue() { println "getValue"; value }
groovy:004> }
===> true
groovy:000> f1 = new Foo()
foo ctor!
===> Foo@10f0625
groovy:000> f1.value
getValue
===> 12345
groovy:000> f2 = sun.misc.Unsafe.theUnsafe.allocateInstance(Foo)
===> Foo@38fff7
groovy:000> f2.value
getValue
===> 0
groovy:000> quit

可以看到,创建f2指向的Foo对象时,构造器并没有被调用(没有输出"foo ctor!"),实例的状态(value)也并未按用户指定的值初始化(12345),整个对象的所有字段都处于默认状态(0或者null或者false之类)。

只是借这个话题用Unsafe举例说明Java对象的创建是分两步走、调用构造器只是其中一步。并不是说反序列化的时候就一定用了Unsafe哦,这个请注意区分 ^_^

实际上在Sun JDK的实现里,Java层面的反射类库与JVM层面的反射实现相互配合来完成反序列化。java.io.ObjectStreamClass通过跟反射方法/构造器调用类似的机制获取所谓的“序列化构造器”,在反序列化的时候调用这个版本的构造器。
创建这个“序列化构造器”时要在继承链里从最具体向最抽象的方向搜索,找出第一个不可序列化的类(没有实现Serializable接口的类),并找出它的无参构造器来调用。也就是说,反序列化的时候并不是完全不调用用户代码里声明的构造器,只是不调用实现了Serializable的类的而已。

关于构造器,之前还有别的讨论可以参考:
[url=http://rednaxelafx.iteye.com/blog/652719]实例构造器是不是静态方法?[/url]

======================================================================

用[url=http://rednaxelafx.iteye.com/blog/727938]以前介绍过的办法[/url],把原帖里的例子拿来做一下实验,可以更形象的说明问题。

原帖代码(稍微修改,去掉了包名):
import java.io.ByteArrayInputStream;   
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.Serializable;

public class TestClass implements Serializable {
private static final long serialVersionUID = 0L;
public TestClass() throws Exception {
throw new Exception("!!!");
}

public static void main(String[] args) throws Exception {
byte[] head = { -84, -19, 0, 5, 115, 114, 0 };
byte[] ass = { 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 120, 112 };
String name = TestClass.class.getName();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write(head);
baos.write(name.length());
baos.write(name.getBytes());
baos.write(ass);
baos.flush();
baos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));
TestClass o = (TestClass) ois.readObject();
ois.close();
System.out.println("Created: " + o);
System.in.read(); // 暂停进程以便有足够时间来dump class
}
}


MyFilter:
import sun.jvm.hotspot.tools.jcore.ClassFilter;
import sun.jvm.hotspot.oops.InstanceKlass;

public class MyFilter implements ClassFilter {
@Override
public boolean canInclude(InstanceKlass kls) {
String klassName = kls.getName().asString();
return klassName.startsWith("sun/reflect/GeneratedSerializationConstructorAccessor");
}
}


编译的时候用:
javac -classpath ".:$JAVA_HOME/lib/sa-jdi.jar" MyFilter TestClass


然后先运行:
java TestClass

别让它退出,用jps查出它的进程ID,然后用ClassDump得到class文件:
java -classpath ".:./bin:$JAVA_HOME/lib/sa-jdi.jar" -Dsun.jvm.hotspot.tools.jcore.filter=MyFilter sun.jvm.hotspot.tools.jcore.ClassDump 7566

这样就得到了./sun/reflect/GeneratedSerializationConstructorAccessor1.class文件。那么用javap就能查看到它的内容:
Classfile /D:/experiment/test_java_deserialize/sun/reflect/GeneratedSerializationConstructorAccessor1.class
Last modified 2010-12-23; size 1313 bytes
MD5 checksum 6d59fc9bb0c7d58458cdc76714829a0f
public class sun.reflect.GeneratedSerializationConstructorAccessor1 extends sun.reflect.SerializationConstructorAccessorImpl
minor version: 0
major version: 46
flags: ACC_PUBLIC

Constant pool: // ...
{
public sun.reflect.GeneratedSerializationConstructorAccessor1();
flags: ACC_PUBLIC

Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #36 // Method sun/reflect/SerializationConstructorAccessorImpl."<init>":()V
4: return

public java.lang.Object newInstance(java.lang.Object[]) throws java.lang.reflect.InvocationTargetException;
flags: ACC_PUBLIC

Exceptions:
throws java.lang.reflect.InvocationTargetException
Code:
stack=6, locals=2, args_size=2
0: new #6 // class TestClass
3: dup
4: aload_1
5: ifnull 24
8: aload_1
9: arraylength
10: sipush 0
13: if_icmpeq 24
16: new #22 // class java/lang/IllegalArgumentException
19: dup
20: invokespecial #29 // Method java/lang/IllegalArgumentException."<init>":()V
23: athrow
24: invokespecial #12 // Method java/lang/Object."<init>":()V
27: areturn
28: invokespecial #42 // Method java/lang/Object.toString:()Ljava/lang/String;
31: new #22 // class java/lang/IllegalArgumentException
34: dup_x1
35: swap
36: invokespecial #32 // Method java/lang/IllegalArgumentException."<init>":(Ljava/lang/String;)V
39: athrow
40: new #24 // class java/lang/reflect/InvocationTargetException
43: dup_x1
44: swap
45: invokespecial #35 // Method java/lang/reflect/InvocationTargetException."<init>":(Ljava/lang/Throwable;)V
48: athrow
Exception table:
from to target type
0 24 28 Class java/lang/ClassCastException
0 24 28 Class java/lang/NullPointerException
24 27 40 Class java/lang/Throwable
}


对应的Java代码(示意,里面的逻辑用Java无法直接表示,因为Java里new表达式同时包含了创建对象与调用构造器,而且这两个动作必须针对同一类型;而这里创建了TestClass的实例却调用了Object的无参构造器):
package sun.reflect;

public class GeneratedSerializationConstructorAccessor1
extends SerializationConstructorAccessorImpl {
public GeneratedMethodAccessor1() {
super();
}

public Object newInstance(Object[] args)
throws InvocationTargetException {
try {
// create an unitialized TestClass instance
TestClass temp = newUnitializedTestClassInstance(); // new #6 // class TestClass
// dup
// check parameters
if (args.length != 0) throw new IllegalArgumentException();
} catch (final ClassCastException | NullPointerException e) {
throw new IllegalArgumentException(e.toString());
}
// invoke Object() constructor
try {
invokeObjectConstructor(temp); // invokespecial #12 // Method java/lang/Object."<init>":()V
return temp; // areturn
} catch (Throwable t) {
throw new InvocationTargetException(t);
}
}
}

(注:上面代码为了省事用了[url=http://blogs.sun.com/darcy/entry/project_coin_multi_catch_rethrow]Java 7的multi-catch语法[/url])

可以留意一下一段有趣的注释:
package sun.reflect;

/** <P> Java serialization (in java.io) expects to be able to
instantiate a class and invoke a no-arg constructor of that
class's first non-Serializable superclass. This is not a valid
operation according to the VM specification; one can not (for
classes A and B, where B is a subclass of A) write "new B;
invokespecial A()" without getting a verification error. </P>

<P> In all other respects, the bytecode-based reflection framework
can be reused for this purpose. This marker class was originally
known to the VM and verification disabled for it and all
subclasses, but the bug fix for 4486457 necessitated disabling
verification for all of the dynamically-generated bytecodes
associated with reflection. This class has been left in place to
make future debugging easier. </P> */

abstract class SerializationConstructorAccessorImpl
extends ConstructorAccessorImpl {
}


一些相关的值得关注的方法和类有:
java.io.ObjectStreamClass.getSerializableConstructor()
sun.reflect.ReflectionFactory.newConstructorForSerialization()
sun.reflect.MethodAccessorGenerator.generateSerializationConstructor()
sun.reflect.SerializationConstructorAccessorImpl

======================================================================

上面用Sun JDK来演示了反序列化的一种可能的实现方式。事实上Sun JDK也是从1.4开始才采用这种方式的,之前使用的是别的方式。这种方式使用了无法通过校验(verification)的字节码序列,硬要说的话是与JVM规范冲突的;为了能执行它而不出错,HotSpot VM里专门开了后门。

请注意区分“规范”与实现之间的差异。
规范一般定得会比较紧,而实现则可能在许多地方“走捷径”,只要不让上面的用户能感知到走了捷径就没问题 ∩^_^∩
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值