天道酬勤,学无止境

将代理附加到现有对象?(Attach proxy to an existing object?)

问题

我的计划是编写一个基于注释的缓存框架来缓存方法的返回值。 当第一次使用特定参数调用方法时,缓存应存储方法返回值。 当使用相同的参数第二次调用相同的方法时,该方法应该从缓存中返回先前计算的结果,而不是再次执行其代码。 我的注释如下所示:

@Cached(cacheProvider = HashMapCacheProvider.class)
public Product getProduct(String productId){    
    // Scraping the product from a website ...
    return product;
}

目前我的小框架已经运行良好。 我正在使用 Javassist 创建包含注释方法的类的代理对象。 为了创建一个新的缓存对象,我使用以下代码:

public static <T> T newCachedInstance(Class<T> clazz)
    throws InstantiationException, IllegalAccessException {

    ProxyFactory factory = new ProxyFactory();
    factory.setSuperclass(clazz);
    factory.setFilter(new MethodFilter() {
        public boolean isHandled(Method m) {
            // ignore finalize()
            return !m.getName().equals("finalize");
        }
    });

    Class<T> c = factory.createClass();

    T proxy = c.newInstance();
    ((ProxyObject) proxy).setHandler(new CachedMethodHandler());
    return proxy;
}

问题是,我只能通过这种方法创建新的缓存对象,而不是通过它们的类的构造函数。 因此,我正在寻找一种将现有对象附加到缓存机制的解决方案。

这是我的问题:是否可以将代理附加到现有对象? 据我了解,如果不更新对该对象的所有现有引用,这应该是不可能的。

我的另一种方法是使用字节码操作来操作带注释的方法的代码,如下所示:

public class Hello {
    public void say() {
         System.out.println("Hello");
    }
}

public class Test {
    public static void main(String[] args) throws Exception {
        ClassPool cp = ClassPool.getDefault();
        CtClass cc = cp.get("Hello");
        CtMethod m = cc.getDeclaredMethod("say");
        m.insertBefore("{ System.out.println(\"Hello.say():\"); }");
        Class c = cc.toClass();
        Hello h = (Hello)c.newInstance();
        h.say();
    }
}

你还有别的想法吗? 操作现有对象的方法的最佳实践是什么?

回答1

在默认的 Java 虚拟机中,每个对象实例都存储在堆中,其字段数据与对其Class的引用一起存储在堆中(以及用于垃圾收集的小区域)。 您基本上是在询问是否可以重新定义此链接以指向另一个Class ,默认情况下这是不可能的。

但是,您可以假设使用sun.misc.Unsafe用子类覆盖此引用,只要此子类没有引入新字段。 然而,这个结果没有定义,我不建议尝试它,因为你的框架的用户可能会遇到非常微妙的错误。 此外, sun包层次结构不适用于可能破坏兼容性的公共使用。

Attach API 将提供另一种方式。 您可以使用 Java 代理在运行时重新定义类。 然而,这会影响一个类的所有实例,但从你的目的来看,这将是有意义的。

另一种可能性是使用 AspectJ 之类的东西在运行前重新定义类。

否则,您必须返回一个作为缓存代理的新实例,因为您显然已经这样做了。 这绝对没问题,是 Hibernate 等主要框架使用的一种方法。 请注意,javassist 比例如 cglib 慢得多,因为它直接读取类文件而不是使用反射访问以避免类加载。 这可能会在使用缓存时破坏性能。

回答2

如果您可以替换对要代理的对象的所有引用,特别是,如果该对象已经存在但只有一个对它的引用,您可以执行以下操作:

  1. 获取对象类并使用 Javassist 创建代理类;
  2. 创建这个类的一个实例(这假设有一个无参数的构造函数)
  3. 将所有字段复制到该新实例
  4. 用对新对象的引用替换原始引用

如果有复制构造函数,您可以使用它来创建现有对象的代理副本。 (您必须像X$Proxy(X x) {super(x);}一样定义它)

回答3

https://github.com/verhas/djcproxy 声称可以做到这一点。 不幸的是,它记录了一个使其线程不安全的设计缺陷。

https://javax0.wordpress.com/2016/02/03/creating-proxy-object-using-djcproxy/ 说:

该实现有一些流程(*),例如后期方法代理实例化实际上没有任何优势,但在代理的多线程执行的情况下,同一时间可能会受到伤害。

(*) 缺陷

编辑此外,djcproxy 很慢。

受限制的 HTML

  • 允许的HTML标签:<a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • 自动断行和分段。
  • 网页和电子邮件地址自动转换为链接。

相关推荐