gpt4 book ai didi

java - 概念验证:如何在Java中使用反射来动态选择可用的构造函数

转载 作者:塔克拉玛干 更新时间:2023-11-02 08:25:36 24 4
gpt4 key购买 nike

我正在开发一个概念验证对象(反序列化)框架,理想情况下可以序列化任何Object并收集有关类本身的信息。我开始使用Reflection来实现它,以:

  • 访问类型层次结构(超类,接口等)
  • 查找该对象上的所有字段,并获取该字段中的所有值

  • 序列化是“简单”的部分,可以递归地将此规则应用于对象,直到找到null或原始类型为止。现在这就是我遇到的问题:反序列化。

    从一个简单的对象“Hello World”字符串开始,我进行了以下序列化:
    <object type="java.lang.String">
    <primitive name="count" type="int" value="11 />
    <primitive name="hash" type="int" value="0" />
    <primitive name="offset" type="int" value="0" />
    <array name="value" basetype="char">
    <value>H</value>
    <value>e</value>
    <value>l</value>
    ...
    <value>r</value>
    <value>l</value>
    <value>d</value>
    </array>
    </object>

    可以反序列化,因为String类具有默认构造函数,并且可以通过Reflection调用它,并且可以设置所有字段。现在,让我们假设我对一个对象进行了以下序列化:
    <object class="some-class-with-no-default-constructor">
    <object name="some-attrib-name" class="attrib-1-class">
    <primitive name="size" type="int" value="5" />
    ...
    </object>

    如果我没有默认的构造函数,并且所有其他接受参数的构造函数都不能接受“空”值作为输入,从而引发某种异常,因此无法通过反射实例化该类,该怎么办?

    问题是:“是否有一种实例化某个类的“空对象”以在实例化后手动设置其字段而不调用其构造函数的方法?”。当然,我也愿意讨论其他策略。

    谢谢。

    编辑

    一旦这是一个概念验证的环境,并且因此我不考虑安全性限制,便找到了一种通过Unsafe类实例化任何对象而不调用其构造函数的方法。
    public final class A {
    private final Object o;
    private A(final Object o) { if (o == null) throw new Error(); this.o = o; }
    public static A a() { return new A(new Object()); }
    public Object getO() { return o; }
    }

    上面显示的此类是在以下答案之一中提出的,可以使用以下代码对其进行实例化并正确设置最终值(当然,要提供安全性限制不适用):
    private static Unsafe getUnsafe() throws Exception {
    Field vDeclaredField = Unsafe.class.getDeclaredFields()[0];
    vDeclaredField.setAccessible(true);
    Unsafe vUnsafe = (Unsafe) vDeclaredField.get(null);
    vDeclaredField.setAccessible(false);
    return vUnsafe;
    }

    public static void main(String[] args) throws Exception {
    A objectA = (A) getUnsafe().allocateInstance(A.class);

    Field fieldO = A.class.getDeclaredField("o");
    boolean oldAccessibilityValue = fieldO.isAccessible();
    fieldO.setAccessible(true);
    Object objectOParameter = Arrays.asList(1,2,3,4); //could be any object
    fieldO.set(objectA, objectOParameter);
    fieldO.setAccessible(oldAccessibilityValue); //I personally prefer setting it to old value

    assert(objectOParameter.equals(objectA.getO()));
    }

    所以?你们还能看到与SecurityManager本身无关的其他任何问题吗?

    最佳答案

    这不能可靠地完成。
    假设您有以下 class :

    public final class A {
    private final Object o;
    private A(final Object o) { if (o == null) throw new Error(); this.o = o; }
    public static A a() { return new A(new Object()); }
    public Object getO() { return o; }
    }
    首先,您遇到一个关于非默认构造函数的问题,该构造函数带有一个参数,并且在给定 null时会引发异常。
    其次,构造函数的参数可以(在这种情况下)可以定义 最终实例字段的值,您无法在对象创建后对其进行可靠地控制(这可能是因为final字段的内存模型语义可能会导致可见性问题是因为该对象已经发布到其他线程,或者是因为 SecurityManager不允许您修改最终字段)。
    最后,构造函数是 私有(或受保护或受包保护的东西)。如果安装了安全管理器,则它可能会完全阻止您尝试在构造函数上使用 setAccessible(true),从而可以强制调用它。
    因此,我可以按照您的建议直接删除项目,或者对框架可以(反)序列化的对象的特性进行一些限制。
    最后要考虑的是,序列化不仅仅是保存和还原字段的过程。这是在设计 class 时必须仔细计划和实施的事情。 必须将类设计为可序列化的

    回复编辑
    将您提供的代码称为“纯Java”是不公平的,因为它使用的是非标准
    API“sun.misc.Unsafe”,它存在于Sun的实现中,但不能保证在所有实现中都存在。因此,代码取决于实现。
    在您编写的测试代码中,假定您具有有关该类的知识,即,您使用getDeclaredField(“o”)。无论如何,我认为这很容易解决。
    但是,我看到两个问题。
    您不得序列化系统资源
    首先,假设我有一个像这样的课:
    class StockQuoteProvider {
    private QuoteCache cache;
    private Thread quoteCacheUpdater;
    public StockQuoteProvider() {
    this.quoteCacheUpdater = ... // sets up a Thread that will use sockets to connect to Yahoo's stock quote provider and update the cache periodically
    this.quoteCacheUpdater.start();
    }
    public Quote getQuote(final String symbol) { return ... }
    }
    您如何可能序列化 Thread?序列化对象的语义是什么?如果线程处于IO操作的中间,例如从套接字读取,该怎么办?您将如何序列化套接字连接?这个不成立。这个班很正常。
    即使该类是完全线程安全的,也不应共享没有同步的反序列化实例
    让我们忘记语义,回到语言规范,然后再找到方法的问题。 ( 编辑:更改了类,以使观点更强)。考虑下面的类,它表示可变的整数范围:
    // Represents a range of integers, {a, a+1, ..., b}, in which a < b.
    class Range {
    private final Object lock = new Object();
    private int a;
    private int b;
    Range(final int a, final int b) { setAB(a, b); }
    final int[] getAB() { synchronized(lock) { return new int[]{a, b}; } }
    final void setAB(final int a, final int b) {
    if (!(a < b)) { throw new IllegalArgumentException("Invalid range"); }
    synchronized(lock) { this.a = a; this.b = b; }
    }
    @Override public String toString() {
    int[] ab = getAB();
    int a = ab[0];
    int b = ab[1];
    return a + " < " + b;
    }
    }
    一个非常简单,无辜的课堂,对吗?注意,数组int []用作getter的返回类型,因为如果我们使用几个getter,则两次调用getter时 ab的值可能会改变。
    因此,此类完全是线程安全的。在“正常”情况下,它不可能处于“a> = b”的状态。
    通过使用OP提出的反序列化技术,这种保证就消失了。假设OP给了我2种方法,即“Object serialize(Object o)”和“Object deserialize(Object o)”,它们使用了所提出的算法。以下伪代码将证明它不起作用:
    public class Test {
    public static Range r = null;
    public static void Main(final String[] args) {
    final Thread t1 = new Thread(new Runnable() {
    @Override void run() { r = deserialize(serialize(new Range(1, 3)); }
    });

    final Thread t2 = new Thread(new Runnable() {
    @Override void run() { System.out.println(r); }
    });

    t1.start();
    t2.start();
    }
    它会打印什么?首先,如果T2看不到对 r的写入,则 可以输出null 。为了使事情变得更有趣(并看它能获得多微妙的效果),让我们假设T2实际上看到了对字段 r的写入。由于反序列化过程不提供同步,因此JVM可以随意对新反序列化的 Range实例内部的字段进行重新排序。因此,如果T2看不到对 ab的任何写入,或者 “1 <0” (如果仅看到对a的写入),则 它可以打印“0 <0” ,或者 “0 <3” “1 <3” 。根据Java语言规范,您可能无法预测结果(您唯一的保证是结果必须是这5种可能性之一)。
    因此,重点是:您不可能对每个类都可靠地完成这项工作。我总是可以隐藏一个锁获取,并且您将无法跟踪它(没有进行一些认真的,刻骨的((不可能?)字节码分析)),因此该类的反序列化版本将不会在每个线程中均等地看到...您能看到可能出现的巨大问题吗?
    总结一下...
    这样的框架不存在。您将遇到安全管理器(使用 setAccessible(true)),代码可移植性(使用 sun.misc.Unsafe),多线程( class Range)以及无意义,无法使用的反序列化实例( class StockQuoteProvider)的问题。这些只是我可以提出的前4个问题,并且如果不完全不假设要序列化的对象,就不能用纯Java代码解决。
    因此,结论是您 必须限制框架将能够序列化的对象。换句话说,对象必须设计为可序列化的对象。
    祝好运。

    关于java - 概念验证:如何在Java中使用反射来动态选择可用的构造函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6358155/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com