我的计划是编写一个基于注释的缓存框架,它缓存方法的返回值。当第一次使用特定参数调用方法时,缓存应该存储方法返回值。 当使用相同的参数第二次调用相同的方法时,该方法应该从缓存返回先前计算的结果,而不是再次执行其代码。 我的注释看起来像这样:
@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();
}
}
你还有其他想法吗?操纵现有对象方法的最佳做法是什么?
答案 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)
如果可以替换对要代理的对象的所有引用,特别是如果对象已经存在但只有一个引用, 你可以做到以下几点:
如果存在复制构造函数,则可以使用它来创建现有对象的代理副本。 (您必须将其定义为X$Proxy(X x) {super(x);}
)