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

|| 我正在开发一个概念验证对象(反序列化)框架,理想情况下可以序列化任何Object并收集有关类本身的信息。我开始使用Reflection来实现它,以: 访问类型层次结构(超类,接口等) 查找该对象上的所有字段,并获取该字段中的所有值 序列化是“容易”的部分,可以递归地将此规则应用于对象,直到发现空或原始类型为止。现在,这就是我遇到的问题:反序列化。 从一个简单的对象\“ 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>
如果我没有默认的构造函数,并且所有其他接受参数的构造函数都不能接受\ null \作为输入值,则会引发某种异常,从而使我无法实例化该类,该怎么办通过反射? 问题是:“是否可以实例化某个类的“空对象”以在实例化后手动设置其字段而不调用其构造函数?\”。当然,我也愿意讨论其他策略。 谢谢。 编辑 一旦它成为概念验证环境,并且因此我不考虑安全性限制,我就找到了一种通过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本身无关的其他任何问题吗?     
已邀请:
无法可靠地完成。 假设您有以下课程:
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; }
}
首先,您遇到一个关于非默认构造函数的问题,该构造函数带有一个参数,并且在给定“ 5”时将引发异常。 其次,构造函数的参数可以(在这种情况下)定义最终实例字段的值,您无法在构建对象后可靠地控制该值(由于最终字段的内存模型语义可能会导致可见性问题)是因为该对象已经发布到其他线程,还是因为
SecurityManager
不允许您修改最终字段)。 最后,构造函数是私有的(或受保护或受包保护的东西)。如果安装了安全管理器,则它可能会完全阻止您在构造函数上尝试“ѭ7”,从而可以强制调用它。 因此,我要么按照您的建议直接删除项目,要么对框架可以(反)序列化的对象的特性进行一些限制。 最后要考虑的是,序列化不仅仅是保存和还原字段的过程。这是在设计课程时必须仔细计划和实施的事情。一个类必须设计为可序列化的。 回复编辑 调用您提供的代码“纯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时between11ѭ和
b
的值可能会改变。 因此,此类完全是线程安全的。在“正常”情况下,它不可能处于“ 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看不到对字段ѭ14write的写入,则可以打印null。为了使事情变得更有趣(并看它能得到多微妙的效果),让我们假设T2实际上看到了对字段
r
的写入。由于反序列化过程不提供同步,因此JVM可以随意对新反序列化的
Range
实例中的字段进行重新排序。因此,如果T2看不到对
a
b
的任何写入,则它可以打印\“ 0 <0 \”,或者如果对“ a <0 \”(仅看到对a的写入),则可以打印\“ 0 <0 \”。或\“ 1 <3 \”。根据Java语言规范,您可能无法预测结果(您唯一的保证是结果必须是这5种可能性之一)。 因此,重点是:您不可能对每个类都可靠地完成这项工作。我总是可以隐藏一个锁获取,并且您将无法跟踪它(没有进行认真的,严格的((不可能)?)字节码分析),因此该类的反序列化版本将不会在每个线程中均等地看到。 。您能看到可能出现的巨大问题吗? 总结一下... 这样的框架不存在。安全管理器(使用(7ѭ),代码的可移植性(使用
sun.misc.Unsafe
),多线程(
class Range
),无意义,无法使用的反序列化实例(
class StockQuoteProvider
)会遇到问题。这些只是我可以提出的前四个问题,并且如果不完全不假设要序列化的对象,就不能用纯Java代码解决。 因此,结论是您必须限制框架可以序列化的对象。换句话说,对象必须设计为可序列化的对象。 祝好运。     
有没有办法实例化一个   要设置的某个类的“空对象”   他们的字段在   实例化而不调用它   构造函数? 不,没有。这正是基于反射的框架或库通常要求它们使用的类遵守JavaBeans规范的原因,JavaBeans规范需要默认的构造函数。 解决此问题的一种方法是,对于没有默认构造函数的类,需要某种元数据(注释,xml),该元数据会告诉您使用什么值调用哪个构造函数。     
        使用反射,没有可靠的方法,尽管您可以根据已声明的构造函数的参数类型进行有根据的猜测。 您可以查看Objenesis。 您也可以考虑在运行时处理字节码。     

要回复问题请先登录注册