`
RednaxelaFX
  • 浏览: 3052676 次
  • 性别: 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规范。

相关推荐

    9.java学习第九章——对象的创建和使用+构造方法+方法重载——作业的形式:创建对象在JVM中的存储状态(内存图).pdf

    - 构造方法的名字必须与类名相同,没有返回值类型(即使是void也不行)。 #### 2. 构造方法的使用 - 默认构造方法:如果没有显式定义任何构造方法,Java编译器将自动提供一个默认构造方法。 - 显式构造方法:可以...

    实验三:Java类与对象

    `Monkey`类中有默认构造方法和带参数的构造方法,后者允许我们在创建对象时立即设置属性值。 接着,我们学习了如何创建和使用对象。在Java中,通过`new`关键字和类的构造方法来创建对象。实验中,我们创建了两个`...

    构造方法JAVA构造方法

    这提供了更大的灵活性,使得可以根据不同需求创建对象。 ```java public class MyClass { private int myValue; // 默认构造器 public MyClass() { myValue = 0; } // 带参数的构造器 public MyClass...

    类和对象、构造方法总结

    在Java中,类是用来创建对象的模板,它包含变量(也称为属性或成员变量)和方法(函数)。例如,`Student.java`文件可能定义了一个名为`Student`的类,用于描述学生的基本信息和行为,如姓名、学号和学习方法。 ```...

    Java构造方法解析.pdf

    在面向对象编程中,构造方法是创建对象时必须考虑的一个重要概念。在Java语言中,构造方法用于初始化新创建的对象。其特殊之处在于它的名称与类名相同,并且它没有返回类型,不允许有return语句。构造方法在创建对象...

    构造方法11_2.zip

    在编程领域,构造方法是面向对象编程中的一个重要概念,它在创建对象时起着至关重要的作用。构造方法是一种特殊的方法,用于初始化新创建的对象。在Java等面向对象语言中,构造方法的名字必须与类名完全相同,且没有...

    类与对象、构造方法

    ●类的定义 ●对象的定义 ●类与对象 ●对象定义类的示例 ●实例变量 ●实例方法 ●实例成员的访问 ●对象的创建 ●构造方法 ●构造方法的特点 ●实例成员访问形式

    Java 构造方法

    - **参数化构造器**:当需要在创建对象时传入特定值来初始化对象时,我们会使用参数化的构造器。 - **构造器链**:通过使用`this`关键字,一个构造器可以调用同一类中的另一个构造器,以简化代码并避免重复。 - **...

    java中带有不同构造方法的程序内存分析

    当创建对象时,如果没有指定构造方法,系统会调用默认的无参构造方法。这种情况下,内存分配的过程通常是:在堆内存中为新对象分配空间,然后调用构造方法进行初始化。 然而,有时我们需要更复杂的初始化,这时就...

    java构造方法

    无参构造方法用于简单的初始化,而有参构造方法允许在创建对象时传递参数,这样可以在对象创建时设定更复杂的初始状态。 #### 四、构造方法的调用 构造方法在使用`new`关键字创建对象时被自动调用。此外,在一个类...

    java 构造方法的资源

    构造方法的主要任务是在创建对象时设置对象的初始状态,为对象成员变量赋值。当一个类被实例化时,Java会自动调用该类的构造方法来完成初始化工作。 二、构造方法的声明 构造方法的声明格式如下: ```java public ...

    类和对象,this关键字和构造方法

    - 创建对象通常通过使用`new`关键字和类的构造方法来完成。 - 例如: ```java Car myCar = new Car(); ``` 3. **访问成员**: - 一旦创建了对象,就可以使用`.`操作符来访问其成员变量和方法。 - 例如: ``...

    面向对象与Java实现(类、对象、构造方法

    当使用`new`关键字创建对象时,系统自动调用构造方法。 ```java public class Person { String name; int age; // 构造方法 public Person(String name, int age) { this.name = name; this.age = age; } ...

    PHP的类、对象、构造方法。

    构造方法是类的一个特殊方法,当创建类的新实例时自动调用。在PHP中,构造方法的名称是`__construct()`。我们可以利用构造方法来初始化对象的属性,如下所示: ```php class Person { public $name; private $age...

    46丨建造者模式:详解构造函数、set方法、建造者模式三种对象创建方式1

    在编程中,我们经常使用构造函数或set方法来创建对象,但当对象的构造过程变得复杂,尤其是涉及到多个可选参数时,传统的构造方式可能会导致代码可读性和维护性降低。这时,建造者模式就显得尤为重要。 构造函数...

    java四种创建对象的方式

    在反序列化过程中,Java会自动调用对象的无参构造方法来创建对象。为了使一个类可以被反序列化,该类需要实现Serializable接口。示例代码如下: ```java ObjectInputStream ois = new ObjectInputStream(new ...

    Java面向对象程序设计构造方法.pptx

    4. **参数可选**:构造方法可以带有参数,允许在创建对象时传递初始值,也可以没有参数,提供一个默认的构造方式。 在农业信息系统的人员信息管理部分,可能需要创建教师和学生类。例如,教师类(Teacher)包含姓名...

    第04章 面向对象(上) 06 构造方法

    在Java中,构造方法是一个特殊的方法,它在创建对象时被自动调用,用于初始化新创建的对象的状态。本章节我们将深入探讨Java中的构造方法及其重要性。 首先,构造方法的命名必须与类名完全相同,不包含任何返回类型...

    Java零基础-构造方法.md

    构造方法是一种特殊的方法,主要用于在创建对象时初始化对象的状态。它具有以下几个显著特征: 1. **名称**:构造方法的名称必须与类名完全相同。 2. **返回类型**:构造方法没有返回类型声明,即使是void也不行。 3...

Global site tag (gtag.js) - Google Analytics