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

相关推荐

    c#小程序

    2. 面向对象:支持类、接口、继承、多态等面向对象概念。 3. 垃圾回收:自动内存管理,通过垃圾回收机制来释放不再使用的内存。 4. 异常处理:使用try-catch-finally结构来处理运行时错误。 5. 枚举、委托和事件:...

    Delphi ping_delphi_Delphiping例子_

    1. **Delphi语言基础**:首先,需要了解Delphi的基本语法,包括对象导向编程概念,如类、对象、方法和属性。Delphi基于Pascal语言,所以熟悉Pascal语法也是必要的。 2. **Winsock组件**:在Delphi中实现网络通信,...

    ICE系列培训ppt二,AMI和AMD相关

    - **基本API**:包括创建和使用异步调用的基本构造。 - **AsynResult类**:代表异步调用的状态,可以用来检查调用是否完成或获取结果。 - **轮询方式的完成通知**:通过不断查询AsynResult来检测调用完成。 - **...

    Hibernate+Dwr实现权限管理

    通过构造函数、方法和属性,可以更好地组织代码,提高代码的可读性和可维护性。例如,创建一个User对象,包含登录名、密码等属性,以及获取和设置角色的方法。在用户登录后,可以通过DWR调用后台服务获取该用户的...

    基于Retinex模型与多尺度融合的低光照图像增强算法及其应用

    内容概要:本文介绍了一种基于Retinex模型和多尺度融合的低光照图像增强算法。首先,通过对原图像进行光照图分解并利用Retinex模型进行估计,再经过伽马矫正获得亮度均衡的图像。接着,为补偿伽马矫正当中的过曝细节丢失,进行了锐化处理以提升图像细节。最后,在多尺度融合金字塔模型下,根据不同输入图像的权重进行融合,从而得到最终的增强图像。文中还详细介绍了五个非参考图像质量评价指标(BRISQUE,CEIQ,ENIQA,NIQE,PIQE),用以评估算法的效果。 适合人群:从事计算机视觉、图像处理领域的研究人员和技术人员。 使用场景及目标:适用于需要在低光照条件下获取高质量图像的各种应用场景,如安防监控、自动驾驶、医疗影像等领域。目的是提高图像的亮度、对比度和细节,确保后续图像处理任务的有效性和准确性。 其他说明:该算法不仅提高了低光照环境拍摄照片的质量,也为其他计算机视觉应用提供了更好的图像素材,具有重要的社会和经济价值。

    scratch少儿编程逻辑思维游戏源码-奔跑吧!忍者.zip

    scratch少儿编程逻辑思维游戏源码-奔跑吧!忍者.zip

    基于人工蜂群算法的智能路径规划系统:全局搜索、鲁棒性强、灵活多用的路径规划解决方案

    内容概要:本文详细介绍了基于人工蜂群算法的路径规划系统。该算法模拟蜜蜂觅食行为,通过多个个体的并行搜索,实现了全局搜索能力强、鲁棒性和适应性强、适用范围广、算法设计灵活以及具有分布式计算能力等特点。文中还提供了简化的代码片段,展示了如何实现地图创建、保存和起始地点更改等功能,进一步解释了算法的具体实现方法。 适合人群:对路径规划算法感兴趣的科研人员、工程师和技术爱好者。 使用场景及目标:适用于复杂环境下的单目标或多目标路径规划问题,旨在帮助研究人员和开发者更好地理解和应用人工蜂群算法,提升路径规划系统的性能和效率。 其他说明:该算法不仅在理论上具有较高的研究价值,还在实际应用中展现了广泛的潜力,特别是在智能交通、机器人导航等领域。

    基于鲸鱼算法优化LSSVM回归模型:提高预测准确率与全局优化能力

    内容概要:本文介绍了如何使用鲸鱼算法优化最小二乘支持向量机(LSSVM)的回归预测模型。通过模拟鲸鱼群体的行为,优化LSSVM中的惩罚参数和核惩罚参数,提高了预测的准确性和可靠性。鲸鱼算法具有广泛的适用性、强大的全局优化能力和高效的计算特点,使其成为解决各类回归预测问题的有效工具。文中还提供了具体的Python代码实现,展示了从基本LSSVM预测到参数优化的具体步骤,并通过实验数据验证了优化后的模型在训练时间和预测精度上的显著优势。 适合人群:对机器学习、优化算法感兴趣的开发者和技术研究人员,尤其是希望深入了解和支持向量机优化的人群。 使用场景及目标:适用于需要提高回归预测准确性的应用场景,如金融预测、气象预报等领域。目标是通过优化模型参数,获得更高的预测精度和更快的计算速度。 其他说明:鲸鱼算法不仅在理论上具有优越性,在实际应用中也能显著提升模型性能。建议根据具体的数据规模调整算法参数,以达到最佳效果。

    scratch少儿编程逻辑思维游戏源码-超级猫.zip

    scratch少儿编程逻辑思维游戏源码-超级猫.zip

    scratch少儿编程逻辑思维游戏源码-超级马里奥世界 多人游戏.zip

    scratch少儿编程逻辑思维游戏源码-超级马里奥世界 多人游戏.zip

    scratch少儿编程逻辑思维游戏源码-丛林探险跑酷.zip

    scratch少儿编程逻辑思维游戏源码-丛林探险跑酷.zip

    【java】智能自助式停车场管理系统后台web管理服务器javaweb项目.zip

    【java】智能自助式停车场管理系统后台web管理服务器javaweb项目

    二阶系统PID控制器设计与仿真的灵活性及性能优化研究

    内容概要:本文详细介绍了二阶系统的PID控制器设计与仿真方法,展示了如何通过MATLAB进行系统建模和控制器参数调整。首先构建了一个典型的二阶系统作为例子,通过设置不同的PID参数(比例P、积分I、微分D),演示了如何优化系统的阶跃响应特性。文中还讨论了不同参数对系统稳定性的影响,以及如何应对非线性环节带来的挑战。此外,作者强调了PID控制器参数调整的重要性,并提供了几种实用技巧,如使用MATLAB内置工具pidTuner进行参数整定,以及尝试更换不同的被控对象来测试控制器的适应性和鲁棒性。 适合人群:自动化工程专业学生、从事工业控制系统设计的技术人员、对PID控制感兴趣的科研工作者。 使用场景及目标:① 学习如何利用MATLAB搭建二阶系统并设计PID控制器;② 掌握PID参数调整的基本方法及其对系统性能的影响;③ 提升解决实际工业控制问题的能力,特别是在面对复杂动态环境时。 阅读建议:读者可以通过跟随文中的步骤,在自己的环境中重现实验结果,从而加深对PID控制理论的理解。同时,鼓励读者尝试修改系统参数或引入新的干扰因素,进一步探索PID控制器的应用边界。

    少儿编程scratch项目源代码文件案例素材-扫雷.zip

    少儿编程scratch项目源代码文件案例素材-扫雷.zip

    少儿编程scratch项目源代码文件案例素材-圣诞老人VS机器人.zip

    少儿编程scratch项目源代码文件案例素材-圣诞老人VS机器人.zip

    基于AT89C51单片机交通灯课程设计

    【基于AT89C51单片机的交通灯系统】是电子工程领域中的一个经典实践项目,尤其适合初学者进行单片机编程和硬件控制的学习。AT89C51是一款广泛应用的8位微处理器,由美国Atmel公司生产,具有4KB的可编程Flash存储器,可以执行各种控制任务,包括交通灯系统的控制。 交通灯控制系统是城市交通管理的重要组成部分,通过红绿黄三色灯的变化来指示行人和车辆何时通行。在本项目中,交通灯系统采用AT89C51单片机作为核心控制器,通过编程实现红绿黄灯的定时切换,确保交通流畅且安全。 DSN(Design Suite Notation)文件,如`C51交通灯.DSN`,通常是在电路设计软件,如Keil uVision或Proteus中创建的工程文件。这种文件包含了整个项目的配置信息,包括源代码、元器件库、仿真设置等,使得开发者可以在虚拟环境中对交通灯系统进行仿真测试。Proteus是一款强大的电子电路仿真软件,可以直观地模拟硬件电路的行为,无需物理硬件即可验证设计的正确性。 数码管(7段显示器)是显示倒计时的关键部件。在这个项目中,数码管用于显示每个灯组的剩余时间,增强用户交互体验,使驾驶员和行人能够清晰了解何时转换灯色。AT89C51通过串行或并行接口与数码管连接,并通过特定的驱动程序代码控制数码管的显示内容。 编程方面,AT89C51使用C51语言编写,这是一种为8051系列单片机定制的C语言变体。代码中包含的详细注释对于初学者理解程序逻辑至关重要,通过注释可以学习如何设置定时器、中断服务子程序以及I/O端口操作,这些都是单片机编程的基础知识。 交通灯的控制通常基于定时器中断,例如,可以设置一个定时器在特定周期后触发中断,然后在中断服务程序中改变灯的状态。此外,为了实现数码管显示,可能需要用到移位寄存器和译码器等外围设备,这些都需要在代码中进行编程控制。 这个项目涵

    基于MATLAB的改进带记忆模拟退火算法求解TSP问题(多普勒型降温曲线)

    内容概要:本文介绍了一种基于MATLAB的改进带记忆模拟退火算法用于求解旅行商问题(TSP)。该算法引入了多普勒型降温曲线和记忆功能,使得算法在前期进行全局搜索而在后期进行精细调整。文中详细展示了算法的核心代码片段,如多普勒型降温曲线的实现和记忆功能的具体实现方式。此外,作者提供了对多个经典数据集(如att48、中国31/64/144城市数据)的测试结果,证明了该算法的有效性和优越性。同时,还给出了自定义数据集的测试方法和路径可视化的代码。 适合人群:对优化算法感兴趣的研究人员和技术爱好者,尤其是那些希望深入了解模拟退火算法及其应用的人群。 使用场景及目标:适用于需要解决复杂组合优化问题的场景,特别是涉及路径规划、物流配送等领域。目标是提供一种高效、稳定的解决方案,帮助用户快速获得高质量的解。 其他说明:本文不仅提供了完整的代码实现,还包括详细的解释和测试实例,便于读者理解和实践。对于想要进一步探索或修改算法的人来说,这是一个很好的起点。

    MMC-HVDC电能质量调节系统及其背靠背模块化多电平换流器在电网与粒子加速器中的应用

    内容概要:本文详细介绍了MMC-HVDC电能质量调节系统及其背靠背模块化多电平换流器(MMC)的工作原理和技术优势。MMC-HVDC系统主要用于保护敏感电网免受瞬态电压骤降的影响,通过内部能量存储和整流器控制线路电流,确保电网的稳定性。此外,该系统还具备无功功率补偿、低谐波失真和高冗余性的特点。文中特别提到MMC-HVDC在粒子加速器领域的应用和发展前景,强调了其在复杂环境中的适应性和可靠性。 适合人群:从事电力系统工程、电能质量管理、粒子加速器设计的研究人员和技术人员。 使用场景及目标:适用于需要解决瞬态电压骤降问题的电力系统,特别是在粒子加速器等对电能质量有较高要求的场合。目标是提高电网的稳定性和效率,减少设备损坏和系统不稳定性。 其他说明:文章还讨论了MMC-HVDC的设计和开发过程,包括模块化结构设计、能量存储优化和控制算法改进等方面的内容。

    少儿编程scratch项目源代码文件案例素材-侵略者.zip

    少儿编程scratch项目源代码文件案例素材-侵略者.zip

    scratch少儿编程逻辑思维游戏源码-暴徒危机.zip

    scratch少儿编程逻辑思维游戏源码-暴徒危机.zip

Global site tag (gtag.js) - Google Analytics