|
锁定老贴子 主题:殊途同归
该帖已经被评为精华帖
|
|
|---|---|
| 作者 | 正文 |
|
时间:2004-08-11
说到cache的例子,我就借花献佛,举一个Adrian Colyer的例子,感谢Adrian Colyer给我们奉献这么好的Caching implement。
我先按OO TDD步骤来进行: 1、首先简化Adrian Colyer原来的TestCase,在DataProvider的前后两次expensiveOperation操作,期望节省0.5s 。 [code:1] public class CachingTest extends TestCase { private DataProvider provider; public void testExpensiveOperationCache() { long start100 = System.currentTimeMillis(); int op100 = provider.expensiveOperation(100); long stop100 = System.currentTimeMillis(); long start100v2 = System.currentTimeMillis(); int op100v2 = provider.expensiveOperation(100); long stop100v2 = System.currentTimeMillis(); long expectedSpeedUp = 500; // expect at least 0.5s quicker with cache assertTrue("caching speeds up return (100)", ((stop100 - start100) - (stop100v2 - start100v2)) >= expectedSpeedUp); assertEquals("cache returns correct value(100)",op100,op100v2); } /* * @see TestCase#setUp() */ protected void setUp() throws Exception { super.setUp(); provider = new DataProvider(100); } } [/code:1] 2、接着就是让程序能编译通过,并且使TestCase red。 [code:1] public class DataProvider { private int multiplicationFactor = 0; public DataProvider(int seed) { multiplicationFactor = seed; } /** * expensiveOperation is a true function (it always * returns the same output value for a given input * value), but takes a long time to compute the * answer. */ public int expensiveOperation(int x) { try { Thread.sleep(1000); } catch (InterruptedException ex) {} return x * multiplicationFactor; } } [/code:1] 3、此时,就应该考虑如何让TestCase green。 [code:1] public class DataProvider { [color=red]private Map result;[/color] private int multiplicationFactor = 0; public DataProvider(int seed) { multiplicationFactor = seed; result = new HashMap(); } /** * expensiveOperation is a true function (it always * returns the same output value for a given input * value), but takes a long time to compute the * answer. */ public int expensiveOperation(int x) { if (result.containsKey(new Integer(x))) { return Integer.parseInt(result.get(new Integer(x))); } else { try { Thread.sleep(1000); } catch (InterruptedException ex) {} result.put(new Integer(x), new Integer( x * multiplicationFactor)); return x * multiplicationFactor; } } }[/code:1] 4、OK,测试通过了。接下去我们用相同的方法来处理DataProvider1,DataProvider2... [code:1] public class DataProvider1 { private Map result; private int multiplicationFactor = 0; public DataProvider1(int seed) { multiplicationFactor = seed; result = new HashMap(); } /** * expensiveOperation is a true function (it always * returns the same output value for a given input * value), but takes a long time to compute the * answer. */ public int expensiveOperation(int x) { if (result.containsKey(new Integer(x))) { return Integer.parseInt(result.get(new Integer(x))); } else { try { Thread.sleep(1000); } catch (InterruptedException ex) {} result.put(new Integer(x), new Integer( x + multiplicationFactor)); return x + multiplicationFactor; } } } [/code:1] 5、闻到代码味道了吧,开始重构,把相同的代码抽取出来,创建一个Abstract Class。 [code:1] public class CachingTest extends TestCase { private AbstractDataProvider provider; public void testCachedOperation() { long start100 = System.currentTimeMillis(); int op100 = provider.cachedOperation((100); long stop100 = System.currentTimeMillis(); long start100v2 = System.currentTimeMillis(); int op100v2 = provider.cachedOperation((100); long stop100v2 = System.currentTimeMillis(); long expectedSpeedUp = 500; // expect at least 0.5s quicker with cache assertTrue("caching speeds up return (100)", ((stop100 - start100) - (stop100v2 - start100v2)) >= expectedSpeedUp); assertEquals("cache returns correct value(100)",op100,op100v2); } /* * @see TestCase#setUp() */ protected void setUp() throws Exception { super.setUp(); provider = new DataProvider(100); } } [/code:1] [code:1] public abstract class AbstractDataProvider { private Map result; private int multiplicationFactor = 0; public AbstractDataProvider(int seed) { multiplicationFactor = seed; result = new HashMap(); } public int cachedOperation(int x) { int ret = 0; Integer key = new Integer(x); if (result.containsKey(key)) { Integer val = (Integer) result.get(key); ret = val.intValue(); } else { ret = expensiveOperation(x); result.put(key,new Integer(ret)); } return ret; } /** * expensiveOperation is a true function (it always * returns the same output value for a given input * value), but takes a long time to compute the * answer. */ protected abstract int expensiveOperation(int x); } [/code:1] [code:1] public class DataProvider extends AbstractDataProvider { public DataProvider(int seed) { super(seed); } /** * expensiveOperation is a true function (it always * returns the same output value for a given input * value), but takes a long time to compute the * answer. */ protected int expensiveOperation(int x) { try { Thread.sleep(1000); } catch (InterruptedException ex) {} return x * multiplicationFactor; } } [/code:1] 6、All Right,终于实现一个简单的Caching机制。瞧瞧我们都有哪些改变: 其一,抽取了一个新的抽象类,子类都需要去扩展这个抽象类; 其二,增加了一个cachedOperation方法; 其三,expensiveOperation方法的访问方式修改为protected 下面,我们再试试AOP TDD,这个例子使用AspectJ: 1、首先简化Adrian Colyer原来的TestCase,在DataProvider的前后两次expensiveOperation操作,期望节省0.5s 。 2、接着就是让程序能编译通过,并且使TestCase red。 前两个步骤与原来的是完全相同的,第三步就有些不同了。 3、增加一个aspect,并让TestCase green。 [code:1] public aspect BogBasicHardWiredCache { private Map operationCache = new HashMap(); pointcut expensiveOperation(int x) : execution(* DataProvider.expensiveOperation(int)) && args(x); /** * caching for expensive operation */ int around(int x) : expensiveOperation(x) { int ret = 0; Integer key = new Integer(x); if (operationCache.containsKey(key)) { Integer val = (Integer) operationCache.get(key); ret = val.intValue(); } else { ret = proceed(x); operationCache.put(key,new Integer(ret)); } return ret; } } [/code:1] 4、啊,测试也通过了。接下去我们再来处理DataProvider1,DataProvider2... [code:1] public aspect BogBasicHardWiredCache1 { private Map operationCache = new HashMap(); pointcut expensiveOperation(int x) : execution(* DataProvider1.expensiveOperation(int)) && args(x); /** * caching for expensive operation */ int around(int x) : expensiveOperation(x) { int ret = 0; Integer key = new Integer(x); if (operationCache.containsKey(key)) { Integer val = (Integer) operationCache.get(key); ret = val.intValue(); } else { ret = proceed(x); operationCache.put(key,new Integer(ret)); } return ret; } } [/code:1] 5、几个aspect差不多嘛,再重构。 [code:1] public aspect BogBasicHardWiredCache pertarget(expensiveOperation(int)) { private Map operationCache = new HashMap(); pointcut expensiveOperation(int x) : execution(* expensiveOperation(int)) && args(x); /** * caching for expensive operation */ int around(int x) : expensiveOperation(x) { int ret = 0; Integer key = new Integer(x); if (operationCache.containsKey(key)) { Integer val = (Integer) operationCache.get(key); ret = val.intValue(); } else { ret = proceed(x); operationCache.put(key,new Integer(ret)); } return ret; } } [/code:1] 6、收工,我们做了些什么呢?仅仅只是创建了一个aspect。 简单的例子可能给我们带来一些启示,但是走哪条道需要你自己选择,不是吗? 声明:JavaEye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
|
|
| 返回顶楼 | |
|
时间:2004-08-11
唉,看来你是彻底没有看明白偶在Log, Cache, AOP的常用谎言? (嗡嗡作响的AOP系列之二)里面骂的东西: Cache不是这么简单的, AOPer不要老是举这些玩具例子了, 何况Adrian的这个例子里, CacheAspect写比偶在那篇文章里面抄的要烂N倍, 更本不能重用, 代码也写得好糊烂, AOPer鼓吹的无侵入性也完全没有体现出来!! 按照前面某位大哥说的"现在还在捣鼓基础的log??",完全可以骂他"现在还在捣鼓这种骗小孩cache??"了,
不过看在你用代码说话的份上, 偶还是乐意和你唐僧一下, 然后让你一步一步地掉入偶的陷阱, :twisted: 来看看偶的实现, 首先改写成偶喜欢的小可爱: 接口正切, [code:1] public interface DataProvider { public int expensiveOperation(int x); public int cachedOperation(int x); public void setFactor(int factor); public int getFactor(); public CacheManager getCacheManager(); public void setCacheManager(CacheManager cacheManager); } public interface CacheManager { public Object get(Object key); public void put(Object key, Object value); public void clear(Object key); public void clearAll(); } [/code:1] 实现: [code:1] public abstract class AbstractDataProvider implements DataProvider { private int factor; private CacheManager cacheManager; public int cachedOperation(int x) { Integer key = new Integer(x); Integer result = (Integer) cacheManager.get(key); if (result == null) { result = new Integer(expensiveOperation(x)); cacheManager.put(key, result); } return result.intValue(); } public void setFactor(int factor) { this.factor = factor; } public int getFactor() { return factor; } public CacheManager getCacheManager() { return cacheManager; } public void setCacheManager(CacheManager cacheManager) { this.cacheManager = cacheManager; } } public class DataProviderOne extends AbstractDataProvider { public int expensiveOperation(int x) { try { Thread.sleep(1000); } catch (InterruptedException e) { } return x * getFactor(); } } public class CacheManagerImpl implements CacheManager{ private HashMap cache = new HashMap(); public Object get(Object key) { return cache.get(key); } public void put(Object key, Object value) { cache.put(key, value); } public void clear(Object key) { cache.remove(key); } public void clearAll() { cache.clear(); } } [/code:1] 稍微修改一下测试代码: [code:1] public class DataProviderTest extends TestCase { private DataProvider provider; public void testCachedOperation() { long start100 = System.currentTimeMillis(); int op100 = provider.cachedOperation(100); long stop100 = System.currentTimeMillis(); long start100v2 = System.currentTimeMillis(); int op100v2 = provider.cachedOperation(100); long stop100v2 = System.currentTimeMillis(); long expectedSpeedUp = 500; // expect at least 0.5s quicker with cache assertTrue("caching speeds up return (100)", ((stop100 - start100) - (stop100v2 - start100v2)) >= expectedSpeedUp); assertEquals("cache returns correct value(100)",op100,op100v2); } protected void setUp() throws Exception { provider = new DataProviderOne(); CacheManagerImpl cm = new CacheManagerImpl(); provider.setCacheManager(cm); provider.setFactor(100); } } [/code:1] 绿油油的通过了 偶的问题来了, 首先Cache不是光Cache了东西就完事的, 记得擦屁股, 偶多加一个测试用例: [code:1] public void testClearCache() { int op1 = provider.cachedOperation(1); int op1v2 = provider.cachedOperation(1); assertTrue(100 == op1); assertTrue(100 == op1v2); provider.setFactor(200); int op1v3 = provider.cachedOperation(1); assertTrue(200 == op1v3); } [/code:1] 先修改你的AspectJ让它通过看看? (不过在修改以前, 先抄抄偶的那个CacheAspect, 因为后面的陷阱都和CacheAspect重用有关呢), 偶再来修改代码, 然后给你挖第2个, 第3个陷阱....... |
|
| 返回顶楼 | |
|
时间:2004-08-11
既然都已经摆下陷阱,那就尽我所能吧!
下面这个aspect就可以让你的测试通过了,不过你的testcase code有点问题,我姑且认为assertTrue(200 == op1v2)中的op1v2应该是op1v3吧。需要说明的一点是:我把BogBasicHardWiredCache改名为SimpleCache。 [code:1] public aspect SimpleCache { private Map operationCache = new HashMap(); pointcut expensiveOperation(int x) : execution(* DataProvider.expensiveOperation(int)) && args(x); before(DataProvider dp, int factor): target(dp) && args(factor) && call(void setFactor(int)) { int oldValue = dp.getFactor(); if (0 != oldValue && oldValue != factor) { operationCache.clear(); return; } } /** * caching for expensive operation */ int around(int x) : expensiveOperation(x) { int ret = 0; Integer key = new Integer(x); if (operationCache.containsKey(key)) { Integer val = (Integer) operationCache.get(key); ret = val.intValue(); } else { ret = proceed(x); operationCache.put(key,new Integer(ret)); } return ret; } } [/code:1] |
|
| 返回顶楼 | |
|
时间:2004-08-11
代码擂台?噢,我喜欢,搬个凳子来坐……
晚点来掺和一下,不过,我可不管什么Aspect不Aspect的,看着爽,用着舒心就行。 |
|
| 返回顶楼 | |
|
时间:2004-08-11
后山 写道 下面这个aspect就可以让你的测试通过了,不过你的testcase code有点问题,我姑且认为assertTrue(200 == op1v2)中的op1v2应该是op1v3吧。需要说明的一点是:我把BogBasicHardWiredCache改名为SimpleCache。
-_-! 汗, 偶连test code也错......, 该打...... OK, 先来看看偶的实现是怎么改的, 只用加一句话: [code:1] public void setFactor(int factor) { if(this.factor != factor) cacheManager.clearAll(); this.factor = factor; } [/code:1] 但是AOPer可以说: 虽然我们清除Cache的代码看起来比较多一些, 但是如果影响到Cache内容的触发点不只setFactor一处的话, 我们只要修改ponitcut的匹配就好了, 而你还得在N处地方加入clear cache的代码, 慢着, 实际应用的cache不是一次清除干净, 我们需要的是聪明的cache, 只要清除对应的Cache, 不应该一股脑清除掉. 在看smart cache clear以前, 先扔出第2个问题:CacheAspect如何做到聪明的Cache Key重用? DataProvider多加了一个方法: cachedOperationTwo [code:1] public void testCachedOperationTwo() { long start1_2 = System.currentTimeMillis(); int op1_2 = provider.cachedOperationTwo(1, 2); long stop1_2 = System.currentTimeMillis(); long start1_2v2 = System.currentTimeMillis(); int op1_2v2 = provider.cachedOperationTwo(2, 1); long stop1_2v2 = System.currentTimeMillis(); long expectedSpeedUp = 500; // expect at least 0.5s quicker with cache assertTrue("caching speeds up return", ((stop1_2 - start1_2) - (stop1_2v2 - start1_2v2)) >= expectedSpeedUp); assertTrue(200 == op1_2); assertTrue(200 == op1_2v2); } [/code:1] 这次先来给出偶的实现: AbstractDataProvider.java [code:1] public int cachedOperationTwo(int x, int y) { String key = "OP2" + x * y; Integer result = (Integer) cacheManager.get(key); if (result == null) { result = new Integer(expensiveOperationTwo(x, y)); cacheManager.put(key, result); } return result.intValue(); } [/code:1] DataProviderOne.java [code:1] public int expensiveOperationTwo(int x, int y) { try { Thread.sleep(1000); } catch (InterruptedException e) { } return x * y * getFactor(); } [/code:1] 但是由于AspectJ对于JoinPoint的上下文不了解, AOP号称的code re-use根本无法实现这种聪明的Cache Key重用, 看看你能如何写出简洁的实现? 第3个问题: smart cache clear, 等你解决了上面的这个陷阱, 偶再来说. |
|
| 返回顶楼 | |
|
时间:2004-08-12
后山,你是想展示cache还是想展示aspect?aspect的特点是不是分离的关注点与具体的属主类型无关?你的测试代码里关于横切的那部分我没看出来无关性。Readonly的测试代码可是跟具体的类密切相关的。
|
|
| 返回顶楼 | |
|
时间:2004-08-12
感觉很多介绍aop的文章拿来做对比的oo都是已经受到批判的用实现继承和类层次来组织代码的方法。而对现在基于接口正交的方法很少提及。
这就很难让人心服了。打落水狗谁不会呢? 不过这个cache确实可以引出很有趣的例子的。 read-only给的例子正是当cache和具体应用逻辑比较紧密地耦合的情况。对此,aop也许至少可以说:我们面对的不是这种紧耦合的情况,在情况没有这么复杂的更简单的情况,aop确实可以让代码更漂亮。 打击对手的软肋固然有效,但是也许没有强攻对手的长处更有说服力(毕竟,谁没有弱点呢?) 所以,我选择不打乱aop的脚步,与狼共舞,看看效果如何。 对贴主给的这个例子,太简单了,接口正交完全可以处理的嘛。 比如, 先假设我们要处理的是这样一个业务逻辑(btw,我很反感对business logic省略接口,上来就做class。) [code:1]interface Business{ String f(String s); }[/code:1] 然后,不管事实上有BusinessImpl1, BusinessImpl2,...多少种不同的实现,我们只对Business接口实现cache的decorator. [code:1]final class CachedBusiness implements Business{ private final Map cache = new HashMap(); private final Business real; public String f(String s){ final Object ret = cache.get(s); if(s==null){ final String r = real.f(s); cache.put(s, r); return r; } else return (String)ret; } }[/code:1] 我省略了构造函数等细节。(题外话,有人可能对final有看法,呵呵,我个人的喜好,是能final就final,没有足够的理由,绝对不允许别人继承我。要customize?有接口给你,decorator, brige, adapter,随你便。) 对这样一个decorator,如果你想cache某一个Business,就这样做: [code:1]Business createBusiness(...){ return new CachedBusiness(new BusinessImpl1(...)); }[/code:1] 完全对外界透明。 如此,对aop给出的最简单的例子,我看不出它对这种接口正交的方法有什么优势。 然后,让我们把问题稍微复杂化一点:加入setFactor(),也就是说,有一个函数要清空cache。 如read-only所作,接口变成: [code:1]interface Business{ String f(String s); void setFactor(int i); }[/code:1] 我们的cache变成: [code:1]final class CachedBusiness{ private final Map cache = new HashMap(); private final Business real; public String f(String s){ final Object ret = cache.get(s); if(s==null){ final String r = real.f(s); cache.put(s, r); return r; } else return (String)ret; } void setFactor(int i){ cache.clear(); real.setFactor(i); } }[/code:1] 当然,我们可以更聪明点,记住上次的factor,然后只有当factor不同的时候才clear,这些都是小节了。 so far,仍然是没有看出aop对接口正交的优势。 好,我们再复杂一点:有不只一个函数要求cache。自然这些函数一般都要各自有自己的cache。 接口变成: [code:1]interface Business{ String f1(String s); String f2(String s1, int s2); void setFactor(int);//这个函数影响f1的cache。 void invalidate(String s);//这个函数影响f1和f2的cache。 void g();//这个函数只应先f2的cache。 }[/code:1] 啊呀,一下子问题复杂了。 我们面对的难点有: 1。可以只cache f1, 只cache f2,也可以两者都cache 2。f2有两个参数,如何决定cache的key。 3。对setFactor, invalidate(), g()这几个函数要有不同处理。 对1, 最好的办法是对cache f1, cache f2分别做一个类,然后就可以进行组合。这样,即使有10个可能要cache的函数,我们也不过做十个不同的类,然后用户用这个十个decorator自己组合就是了,比如: [code:1] new BusinessCache1( new BusinessCache2( new BusinessCache10( new BusinessImpl2() ) ) )[/code:1] 对二,这绝对是跟f2的语义直接相关的,一个generic的cache是不可能知道如何组成key的。 对三,哎,麻烦,在BusinessCache1里面,我们要处理setFactor和invalidate,在BusinessCache2里面,则要处理invalidate和g()。 假如关系更加复杂一些,比如,有十个要cache的函数,有二十个可能要清除不同cache的其他函数,老天,这个组合可是很庞大的。 下面在我继续之前,让我先写一点代码,示意一下问题的麻烦程度: [code:1]interface Business{ int f1(int a); String f2(String b); char f3(int a, String b, int c); void invalidate1(); int invalidate12(int x); }[/code:1] 这个接口有f1, f2, f3三个要cache的费时操作。有invalidate1, invalidate12两个要清cache的函数。 下面对cache f1, cache f2分别写一个类: [code:1]final class CachedF1Business implements Business{ private final Map cache = new HashMap(); private final Business real; public int f1(int a){ final Integer k = new Integer(a); final Object ret = cache.get(k); if(ret==null){ final int r = real.f1(a); cache.put(k, new Integer(r)); return r; } else{ return ((Integer)ret).intValue(); } } public String f2(String b){return real.f2(b);} public char f3(int a, String b, int c){ return real.f3(a,b,c); } public void invalidate1(){ cache.clear(); real.invalidate1(); } public int invalidate12(int x){ cache.clear(); return real.invalidate2(x); } public CachedF1Business(Business r){ this.real = r; } } final class CachedF2Business implements Business{ private final Map cache = new HashMap(); private final Business real; public int f1(int a){ return real.f1(a); } public String f2(String b){ final String k = b; final Object ret = cache.get(b); if(ret==null){ final String r = real.f2(b); cache.put(k, r); return r; } else{ return (String)ret; } } public char f3(int a, String b, int c){ return real.f3(a,b,c); } public void invalidate1(){ real.invalidate1(); } public int invalidate12(int x){ cache.clear(); return real.invalidate2(x); } public CachedF1Business(Business r){ this.real = r; } }[/code:1] 哎,闻到坏味道了。 cache的逻辑很相似,但是被重复在Cache1和Cache2两个类中。 清cache的操作也很相似,也是分别散落到了不同的地方。 如果我们总结一下,给cache写一下伪码,应该是这样: [code:1]if method is cached key = create key from arguments v = cache.find(key) if(v==null) r = call the real expensive operation put r in cache return r else return v else if method needs to clear cache clear cache call the real operation and return the value. else delegate to the real operation[/code:1] 对这样一个同样的逻辑在不同的地方用相似(注意,并非相同)的代码重复总是不舒服。 我想,这大概就是aop所要解决的一个具体例子吧。 在c++里面,我是无计可施了,即使用尽meta-programming, traits,我相信也是徒劳。也许可以找到些没有希望中的强大(但是扔然极为复杂)的heuristic的解决方案,但是,没有完美的解决办法。 同样的,用静态类型的java我相信也是达不到这个要求的。 幸好,java有dynamic proxy。它虽然损害了类型安全,有点overkill,但是处理这种复杂问题正好是强项。 让我们看看dynamic proxy如何解决问题: [code:1]final class Caching implements InvocationHandler{ private final Map cache = new HashMap(); private final Object real; private final MethodPredicate target; private final KeyGen gen; private final MethodPredicate filter; private void invalidate(Method mtd){ if(filter.eval(mtd)){ cache.clear(); } } public Object invoke(Object proxy, Method mtd, Object[] args){ invalidate(mtd); if(target.eval(mtd)){ final Object key = gen.generate(mtd,args); final Object ret = cache.get(key); if(ret==null){ final Object r = mtd.invoke(real, args); if(r!=null){ cache.put(key, r); return r; } } } else{ return mtd.invoke(real, args); } } }[/code:1] 呵呵,和伪码非常象。 MethodPredicate, KeyGen都是接口。其中MethodPredicate负责选择要cache和要清空cache的方法,KeyGen用来生成cache key。 具体实现我就不写了,相信谁都写得出来。 这样,是不是也和aop的实现差不多呢? 当然,也许有的人会说:你这已经是aop了。 我的回答是:是吗?太好了。那么,难道aop就是interceptor么? |
|
| 返回顶楼 | |
|
时间:2004-08-12
怎么觉得有点糊里糊涂的。代码之前请写个简要的介绍和说明。
另外,对函数的cache?对类方法的cache有什么意义?也许某些情况下有用,不过这种说法放到这个例子里我也没看出来到底哪里在cache你的函数啊。 我希望能够更清晰简单的看懂代码。 |
|
| 返回顶楼 | |
|
时间:2004-08-12
ajoo, 你, 你, 竟然把偶准备好的陷阱一下子都扔出来了......,
这种做法和AOP相比, CachedBusiness仅仅是多写一堆delegate代码给Business, 即便使用dynamic proxy的代码也很清晰, 而AOP鼓吹的一堆buzzword却是偶这样愚蠢的脑袋所不能容忍的...... 不说了, 等看AOP怎么做吧, 或许没有深入了解AOP的偶们都是愚蠢的...... |
|
| 返回顶楼 | |
|
时间:2004-08-12
Readonly 写道 先扔出第2个问题:CacheAspect如何做到聪明的Cache Key重用? 重用还是有可能的,还是按TDD来实现。不过要说明一点是:我并没有任何否定OO的言论。 1、在aspect中加入一个新的pointcut & advice。 [code:1] /** * caching for expensive operation two */ pointcut expensiveOperationTwo(int x, int y) : execution(* DataProvider.expensiveOperationTwo(int, int)) && args(x, y); int around(int x, int y) : expensiveOperationTwo(x, y) { int ret = 0; String key = "OP2" + x * y; if (operationCache.containsKey(key)) { Integer val = (Integer) operationCache.get(key); ret = val.intValue(); } else { ret = proceed(x, y); operationCache.put(key,new Integer(ret)); } return ret; } [/code:1] 2、这个pointcut & advice与原来的很相似,主要的不同在于:参数数目和key生成策略。为了重用,必须要解决这两个问题。首先,让我们来着手解决参数数目,修改上面的pointcut & advice。 [code:1] int around(DataProvider dp): execution(* DataProvider.DataProvider.expensiveOperationTwo(..)) && target(dp) { int ret = 0; Object[] obj = thisJoinPoint.getArgs(); Object key = "OP2" + Integer.parseInt(String.valueOf(obj[0])) * Integer.parseInt(String.valueOf(obj[1])); if (operationCache.containsKey(key)) { Integer val = (Integer) operationCache.get(key); ret = val.intValue(); } else { ret = proceed(dp); operationCache.put(key,new Integer(ret)); } return ret; } [/code:1] 3、根据expensiveOperationTwo来修改原来的expensiveOperation's 的pointcut & advice。 [code:1] int around(DataProvider dp): execution(* DataProvider.expensiveOperation(..)) && target(dp) { int ret = 0; Object[] obj = thisJoinPoint.getArgs(); Object key = obj[0]; if (operationCache.containsKey(key)) { Integer val = (Integer) operationCache.get(key); ret = val.intValue(); } else { ret = proceed(dp); operationCache.put(key,new Integer(ret)); } return ret; } [/code:1] 4、两个方法的pointcut & advice越来越相似,为了把两个pointcut & advice合并,现在就需要我们解决另外一个问题:cache key的生成策略。把它抽取出来,生成一个方法。 [code:1] private Object getCacheKey(Object[] obj) { Object key = null; if (obj.length == 1) key = obj[0]; else if (obj.length == 2) key = "OP2" + Integer.parseInt(String.valueOf(obj[0])) * Integer.parseInt(String.valueOf(obj[1])); return key; } [/code:1] 5、ok,最后合并这两个pointcut & advice。 [code:1] pointcut expensiveOperation(DataProvider dp) : execution(* DataProvider.expensiveOperation*(..)) && target(dp); int around(DataProvider dp): expensiveOperation(dp) { int ret = 0; Object key = getCacheKey(thisJoinPoint.getArgs()); if (operationCache.containsKey(key)) { Integer val = (Integer) operationCache.get(key); ret = val.intValue(); } else { ret = proceed(dp); operationCache.put(key,new Integer(ret)); } return ret; } [/code:1] |
|
| 返回顶楼 | |







