论坛首页 Java版 企业应用

殊途同归

浏览 10443 次
锁定老贴子 主题:殊途同归
该帖已经被评为精华帖
作者 正文
最后更新时间: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。

简单的例子可能给我们带来一些启示,但是走哪条道需要你自己选择,不是吗?
   
最后更新时间: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个陷阱.......  
   
0 请登录后投票
最后更新时间: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]
   
0 请登录后投票
最后更新时间:2004-08-11
代码擂台?噢,我喜欢,搬个凳子来坐……

晚点来掺和一下,不过,我可不管什么Aspect不Aspect的,看着爽,用着舒心就行。
   
0 请登录后投票
最后更新时间: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, 等你解决了上面的这个陷阱, 偶再来说.
   
0 请登录后投票
最后更新时间:2004-08-12
后山,你是想展示cache还是想展示aspect?aspect的特点是不是分离的关注点与具体的属主类型无关?你的测试代码里关于横切的那部分我没看出来无关性。Readonly的测试代码可是跟具体的类密切相关的。
   
0 请登录后投票
最后更新时间:2004-08-13
感觉很多介绍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么?
   
0 请登录后投票
最后更新时间:2004-08-12
怎么觉得有点糊里糊涂的。代码之前请写个简要的介绍和说明。

另外,对函数的cache?对类方法的cache有什么意义?也许某些情况下有用,不过这种说法放到这个例子里我也没看出来到底哪里在cache你的函数啊。

我希望能够更清晰简单的看懂代码。
   
0 请登录后投票
最后更新时间:2004-08-12
ajoo, 你, 你, 竟然把偶准备好的陷阱一下子都扔出来了......, 

这种做法和AOP相比, CachedBusiness仅仅是多写一堆delegate代码给Business, 即便使用dynamic proxy的代码也很清晰, 而AOP鼓吹的一堆buzzword却是偶这样愚蠢的脑袋所不能容忍的......

不说了, 等看AOP怎么做吧, 或许没有深入了解AOP的偶们都是愚蠢的......
   
0 请登录后投票
最后更新时间: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]
   
0 请登录后投票
论坛首页 Java版 企业应用

跳转论坛:
JavaEye推荐