在没有可用的构造函数的情况下为现有的Serializable对象创建动态代理

我有一个对象的实例,需要为其创建代理以拦截其中一种方法:

  • 该对象实现了一个接口,但我需要代理完整类型,而不仅仅是实现该接口。
  • 我不知道对象的确切类型,只知道其接口类。
  • 没有可访问的公共构造函数。
  • 对象是可序列化的。
  • 我具有阅读该库代码的完全可访问性,但是没有能力对其进行任何更改。

所以我需要做的是这样的:

 TheObject obj = library.getObject();
 TheObject proxy = createProxyObject(obj);
 library.doSomethingWith(proxy);

在我看来,理论上这应该是可行的,因为对象是可序列化的,但是我找不到任何使用它的方法。

注意以下几点:我一直在尝试使用cglib,但我完全不依赖于此。如果可以在asm,javaassist或任何其他库中使用它,就可以了。

到目前为止,我使用cglib可以使用公共构造函数代理一个简单的对象:

public class SimpleObject {
  private String name;
  public void setName(String name) {
    this.name = name;
  }
  public String getName() {
    return name;
  }
  // return a random number
  public int getRandom() {
    return (int)(Math.random() * 100);
  }
}

public void testCglibEnhancer() throws Exception {
  SimpleObject object = new SimpleObject();
  object.setName("object 1");
  System.out.println(object.getName() + " -> " + object.getRandom());

  Enhancer enhancer = new Enhancer();
  enhancer.setSuperclass(object.getclass());

  // intercept getRandom and always return 32
  ProxyRefDispatcher passthrough = proxy -> object;
  MethodInterceptor fixedRandom = (obj,method,args,proxy) -> 32;
  enhancer.setCallbacks(new Callback[]{passthrough,fixedRandom});
  enhancer.setCallbackFilter(method -> method.getName().equals("getRandom") ? 1 : 0);

  SimpleObject proxy = (SimpleObject)enhancer.create();
  System.out.println(proxy.getName() + " -> " + proxy.getRandom()); // always 32
}

但是我一直无法使用没有公共构造函数的对象来复制它:

public static class ComplexObject implements Serializable {
  public static ComplexObject create() {
    return new ComplexObject();
  }
  private String name;
  private ComplexObject() {
  }
  public void setName(String name) {
    this.name = name;
  }
  public String getName() {
    return name;
  }
  public int getRandom() {
    return (int)(Math.random() * 100);
  }
}

ComplexObject proxy = (ComplexObject)enhancer.create();
// throws IllegalArgumentException: No visible constructors

由于该对象是可序列化的,因此我可以对其进行克隆:

public static <T extends Serializable> T cloneViaSerialization(T source) throws IOException,ClassnotFoundException {
  ByteArrayOutputStream bos = new ByteArrayOutputStream();
  ObjectOutputStream out = new ObjectOutputStream(bos);
  out.writeObject(source);

  ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
  ObjectInputStream in = new ObjectInputStream(bis);
  return (T)in.readObject();
}

public void testClone() throws Exception {
  ComplexObject object1 = ComplexObject.create();
  object1.setName("object 1");

  ComplexObject object2 = cloneViaSerialization(object1);
  object2.setName("object 2");

  System.out.println(object1.getName() + " -> " + object1.getRandom());
  System.out.println(object2.getName() + " -> " + object2.getRandom());
}

那么有什么办法可以让cglib(或任何库)使用这种方法?

ComplexObject object = library.getObject();
ObjectInputStream in = ... // serialised version of object

Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(object.getclass());
// add callbacks etc.

// note createFromSerialized is not a existing method of
// Enhancer - it is what I'm trying to synthesise somehow
ComplexObject proxy = (ComplexObject)enhancer.createFromSerialized(in);

谢谢

mihui1234 回答:在没有可用的构造函数的情况下为现有的Serializable对象创建动态代理

使其正常工作:

  1. 使用ASM创建派生类定义。
  2. 将此类定义添加到与目标相同的类加载器中 课。
  3. 将现有对象序列化为字节数组。
  4. 将派生的类名注入序列化的字节数组中。
  5. 使用标准反序列化结果字节 ObjectInputStream.readObject。

对于(1),我无法获得cglib或字节伙伴来创建所需的类,所以我切换到了ASM。

对于(2),我使用了一个自定义类加载器来加载包含目标类的整个jar文件。

这确实意味着我最终得到的是原始对象的克隆,而不是根据问题的代理,但这可以满足我的需要。

对于序列化技巧:

我创建了一些最小类示例,将它们序列化到磁盘上并比较了生成的二进制数据。

要注入类名称:

  1. 将完整的父对象序列化为字节数组。
  2. 为派生的类头手动创建一个字节数组(见下文)。

两个字节数组都包含流头数据(魔术,版本和初始对象类型),因此我为简化起见从(2)中获取数据,尽管我确实需要调整几个字节。

因此,只需创建所有(2)的流,然后创建所有(1)的流,除了前6个字节。

要为派生类创建字节数组,我只是查看了交付的Java源代码,并提出了以下内容:

/*
 * Returned array contains:
 * - stream header
 * - class header
 * - end block / class desc markers for next class
 */
private byte[] derivedClass(Class<?> clss) throws IOException {
  ByteArrayOutputStream baos = new ByteArrayOutputStream();
  ObjectOutputStream oos = new ObjectOutputStream(baos);

  ObjectStreamClass osc = ObjectStreamClass.lookup(clss);
  oos.writeUTF(osc.getName());
  oos.writeLong(osc.getSerialVersionUID());
  oos.write(SC_SERIALIZABLE);      // flags
  oos.writeShort(0);               // field count
  oos.writeByte(TC_ENDBLOCKDATA);
  oos.writeByte(TC_CLASSDESC);
  oos.flush();

  // header appears to write 0x77 (TC_BLOCKDATA) and 0x54 (???) for bytes 5 & 6
  // samples streamed from other files use 0x73 (TC_OBJECT) and 0x72 (TC_CLASSDESC)
  byte[] bytes = baos.toByteArray();
  bytes[4] = TC_OBJECT;
  bytes[5] = TC_CLASSDESC;

  return bytes;
}

尚未将其作为一般方法进行测试,但对于我的情况似乎很好。

本文链接:https://www.f2er.com/2424065.html

大家都在问