将代理附加到现有对象?

时间:2014-01-17 16:06:29

标签: java proxy javassist bytecode-manipulation

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

@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();
    }
}

你还有其他想法吗?操纵现有对象方法的最佳做法是什么?

3 个答案:

答案 0 :(得分:1)

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

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

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

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

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

答案 1 :(得分:1)

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

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

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

(*)缺陷

编辑此外,djcproxy很慢。

答案 2 :(得分:1)

如果可以替换对要代理的对象的所有引用,特别是如果对象已经存在但只有一个引用, 你可以做到以下几点:

  1. 获取对象类并使用Javassist创建代理类;
  2. 创建此类的实例(假设存在无参数构造函数)
  3. 将所有字段复制到该新实例
  4. 将原始引用替换为新对象的引用
  5. 如果存在复制构造函数,则可以使用它来创建现有对象的代理副本。 (您必须将其定义为X$Proxy(X x) {super(x);}