`
RednaxelaFX
  • 浏览: 3015001 次
  • 性别: Icon_minigender_1
  • 来自: 海外
社区版块
存档分类
最新评论

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

    博客分类:
  • Java
阅读更多
原帖:不用构造方法也能创建对象

把之前我引用过的一段也贴上来:
RednaxelaFX 写道
嗯顺带推荐Effective Java, Second Edition的第74条
引用
A second cost of implementing Serializable is that it increases the likelihood
of bugs and security holes. 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.
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).


在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的类的而已。

关于构造器,之前还有别的讨论可以参考:
实例构造器是不是静态方法?

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

以前介绍过的办法,把原帖里的例子拿来做一下实验,可以更形象的说明问题。

原帖代码(稍微修改,去掉了包名):
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);
        }
    }
}

(注:上面代码为了省事用了Java 7的multi-catch语法

可以留意一下一段有趣的注释:
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里专门开了后门。

请注意区分“规范”与实现之间的差异。
规范一般定得会比较紧,而实现则可能在许多地方“走捷径”,只要不让上面的用户能感知到走了捷径就没问题 ∩^_^∩
分享到:
评论
6 楼 beritha 2011-10-11  
受教了
5 楼 liuleigang 2010-12-24  
引用

3 楼 ouchxp 昨天   引用
回1,2楼 看<深入JAVA虚拟机>
我也刚看个开头


<深入JAVA虚拟机>
记得确实对这些有描述
第七章吧,对象实例化,四种
new
反射
克隆
反序列化
4 楼 ouchxp 2010-12-23  
引用
throw new Exception("!!!"); 

膈应人.:twisted:
3 楼 ouchxp 2010-12-23  
回1,2楼 看<深入JAVA虚拟机>
我也刚看个开头
2 楼 xgj1988 2010-12-23  
还有就是希望给一个 地址。 专门讲 CLASS文件 JAVAP 之后的 的汇编代码的指令意思。
1 楼 xgj1988 2010-12-23  
看得似懂非懂的。看来需要了解一下VM 规范和JAVA规范。

相关推荐

Global site tag (gtag.js) - Google Analytics