论坛首页 Java企业应用论坛

殊途同归

浏览 17229 次
锁定老帖子 主题:殊途同归
该帖已经被评为精华帖
作者 正文
   发表时间:2004-08-11  
说到cache的例子,我就借花献佛,举一个Adrian Colyer的例子,感谢Adrian Colyer给我们奉献这么好的Caching implement。

我先按OO TDD步骤来进行:

1、首先简化Adrian Colyer原来的TestCase,在DataProvider的前后两次expensiveOperation操作,期望节省0.5s 。
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);;
	}

}


2、接着就是让程序能编译通过,并且使TestCase red。
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;
	}
}


3、此时,就应该考虑如何让TestCase green。
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;
                  }
	}
}


4、OK,测试通过了。接下去我们用相同的方法来处理DataProvider1,DataProvider2...
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;
                  }
	}
}


5、闻到代码味道了吧,开始重构,把相同的代码抽取出来,创建一个Abstract Class。
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);;
	}

}


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


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;
	}
}


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。
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;
	}
}


4、啊,测试也通过了。接下去我们再来处理DataProvider1,DataProvider2...
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;
	}
}


5、几个aspect差不多嘛,再重构。
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;
	}
}


6、收工,我们做了些什么呢?仅仅只是创建了一个aspect。

简单的例子可能给我们带来一些启示,但是走哪条道需要你自己选择,不是吗?
   发表时间:2004-08-11  
唉,看来你是彻底没有看明白偶在Log, Cache, AOP的常用谎言? (嗡嗡作响的AOP系列之二)里面骂的东西: Cache不是这么简单的, AOPer不要老是举这些玩具例子了, 何况Adrian的这个例子里, CacheAspect写比偶在那篇文章里面抄的要烂N倍, 更本不能重用, 代码也写得好糊烂, AOPer鼓吹的无侵入性也完全没有体现出来!! 按照前面某位大哥说的"现在还在捣鼓基础的log??",完全可以骂他"现在还在捣鼓这种骗小孩cache??"了,

不过看在你用代码说话的份上, 偶还是乐意和你唐僧一下, 然后让你一步一步地掉入偶的陷阱,

来看看偶的实现, 首先改写成偶喜欢的小可爱: 接口正切,
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();;
}


实现:
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();;
    }

}


稍微修改一下测试代码:
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);;
    }

}


绿油油的通过了


偶的问题来了, 首先Cache不是光Cache了东西就完事的, 记得擦屁股, 偶多加一个测试用例:
    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);;
    }


先修改你的AspectJ让它通过看看? (不过在修改以前, 先抄抄偶的那个CacheAspect, 因为后面的陷阱都和CacheAspect重用有关呢), 偶再来修改代码, 然后给你挖第2个, 第3个陷阱.......  
0 请登录后投票
   发表时间:2004-08-11  
既然都已经摆下陷阱,那就尽我所能吧!

下面这个aspect就可以让你的测试通过了,不过你的testcase code有点问题,我姑且认为assertTrue(200 == op1v2)中的op1v2应该是op1v3吧。需要说明的一点是:我把BogBasicHardWiredCache改名为SimpleCache。

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;
	}
}
0 请登录后投票
   发表时间:2004-08-11  
代码擂台?噢,我喜欢,搬个凳子来坐……

晚点来掺和一下,不过,我可不管什么Aspect不Aspect的,看着爽,用着舒心就行。
0 请登录后投票
   发表时间:2004-08-11  
后山 写道
下面这个aspect就可以让你的测试通过了,不过你的testcase code有点问题,我姑且认为assertTrue(200 == op1v2)中的op1v2应该是op1v3吧。需要说明的一点是:我把BogBasicHardWiredCache改名为SimpleCache。

-_-! 汗, 偶连test code也错......, 该打......

OK, 先来看看偶的实现是怎么改的, 只用加一句话:
    public void setFactor(int factor); {
        if(this.factor != factor); cacheManager.clearAll();;
        this.factor = factor;

    }

但是AOPer可以说: 虽然我们清除Cache的代码看起来比较多一些, 但是如果影响到Cache内容的触发点不只setFactor一处的话, 我们只要修改ponitcut的匹配就好了, 而你还得在N处地方加入clear cache的代码, 慢着, 实际应用的cache不是一次清除干净, 我们需要的是聪明的cache, 只要清除对应的Cache, 不应该一股脑清除掉.

在看smart cache clear以前, 先扔出第2个问题:CacheAspect如何做到聪明的Cache Key重用?
DataProvider多加了一个方法: cachedOperationTwo
    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);;
    }


这次先来给出偶的实现:
AbstractDataProvider.java
    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();;
    }


DataProviderOne.java
    public int expensiveOperationTwo(int x, int y); {
        try {
            Thread.sleep(1000);;
        } catch (InterruptedException e); {
            
        }
        return x * y * getFactor();;
    } 

但是由于AspectJ对于JoinPoint的上下文不了解, AOP号称的code re-use根本无法实现这种聪明的Cache Key重用, 看看你能如何写出简洁的实现?

第3个问题: smart cache clear, 等你解决了上面的这个陷阱, 偶再来说.
0 请登录后投票
   发表时间:2004-08-12  
后山,你是想展示cache还是想展示aspect?aspect的特点是不是分离的关注点与具体的属主类型无关?你的测试代码里关于横切的那部分我没看出来无关性。Readonly的测试代码可是跟具体的类密切相关的。
0 请登录后投票
   发表时间:2004-08-12  
感觉很多介绍aop的文章拿来做对比的oo都是已经受到批判的用实现继承和类层次来组织代码的方法。而对现在基于接口正交的方法很少提及。
这就很难让人心服了。打落水狗谁不会呢?

不过这个cache确实可以引出很有趣的例子的。

read-only给的例子正是当cache和具体应用逻辑比较紧密地耦合的情况。对此,aop也许至少可以说:我们面对的不是这种紧耦合的情况,在情况没有这么复杂的更简单的情况,aop确实可以让代码更漂亮。

打击对手的软肋固然有效,但是也许没有强攻对手的长处更有说服力(毕竟,谁没有弱点呢?)

所以,我选择不打乱aop的脚步,与狼共舞,看看效果如何。


对贴主给的这个例子,太简单了,接口正交完全可以处理的嘛。

比如,

先假设我们要处理的是这样一个业务逻辑(btw,我很反感对business logic省略接口,上来就做class。)
interface Business{
  String f(String s);;
}


然后,不管事实上有BusinessImpl1, BusinessImpl2,...多少种不同的实现,我们只对Business接口实现cache的decorator.

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;
  }
}

我省略了构造函数等细节。(题外话,有人可能对final有看法,呵呵,我个人的喜好,是能final就final,没有足够的理由,绝对不允许别人继承我。要customize?有接口给你,decorator, brige, adapter,随你便。)

对这样一个decorator,如果你想cache某一个Business,就这样做:
Business createBusiness(...);{
  return new CachedBusiness(new BusinessImpl1(...););;
}


完全对外界透明。


如此,对aop给出的最简单的例子,我看不出它对这种接口正交的方法有什么优势。



然后,让我们把问题稍微复杂化一点:加入setFactor(),也就是说,有一个函数要清空cache。
如read-only所作,接口变成:

interface Business{
  String f(String s);;
  void setFactor(int i);;
}


我们的cache变成:

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


当然,我们可以更聪明点,记住上次的factor,然后只有当factor不同的时候才clear,这些都是小节了。

so far,仍然是没有看出aop对接口正交的优势。


好,我们再复杂一点:有不只一个函数要求cache。自然这些函数一般都要各自有自己的cache。

接口变成:

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。
}


啊呀,一下子问题复杂了。
我们面对的难点有:
1。可以只cache f1, 只cache f2,也可以两者都cache
2。f2有两个参数,如何决定cache的key。
3。对setFactor, invalidate(), g()这几个函数要有不同处理。
对1, 最好的办法是对cache f1, cache f2分别做一个类,然后就可以进行组合。这样,即使有10个可能要cache的函数,我们也不过做十个不同的类,然后用户用这个十个decorator自己组合就是了,比如:
new BusinessCache1(
  new BusinessCache2(
    new BusinessCache10(
      new BusinessImpl2();
    );
  );
);


对二,这绝对是跟f2的语义直接相关的,一个generic的cache是不可能知道如何组成key的。
对三,哎,麻烦,在BusinessCache1里面,我们要处理setFactor和invalidate,在BusinessCache2里面,则要处理invalidate和g()。
假如关系更加复杂一些,比如,有十个要cache的函数,有二十个可能要清除不同cache的其他函数,老天,这个组合可是很庞大的。

下面在我继续之前,让我先写一点代码,示意一下问题的麻烦程度:
interface Business{
	int f1(int a);;
	String f2(String b);;
	char f3(int a, String b, int c);;
	void invalidate1();;
	int invalidate12(int x);;
}

这个接口有f1, f2, f3三个要cache的费时操作。有invalidate1, invalidate12两个要清cache的函数。
下面对cache f1, cache f2分别写一个类:

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;
	}	
}


哎,闻到坏味道了。
cache的逻辑很相似,但是被重复在Cache1和Cache2两个类中。

清cache的操作也很相似,也是分别散落到了不同的地方。

如果我们总结一下,给cache写一下伪码,应该是这样:

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
 

对这样一个同样的逻辑在不同的地方用相似(注意,并非相同)的代码重复总是不舒服。

我想,这大概就是aop所要解决的一个具体例子吧。
在c++里面,我是无计可施了,即使用尽meta-programming, traits,我相信也是徒劳。也许可以找到些没有希望中的强大(但是扔然极为复杂)的heuristic的解决方案,但是,没有完美的解决办法。

同样的,用静态类型的java我相信也是达不到这个要求的。

幸好,java有dynamic proxy。它虽然损害了类型安全,有点overkill,但是处理这种复杂问题正好是强项。

让我们看看dynamic proxy如何解决问题:




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


呵呵,和伪码非常象。
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。
	/**
	 * 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;
	}


2、这个pointcut & advice与原来的很相似,主要的不同在于:参数数目和key生成策略。为了重用,必须要解决这两个问题。首先,让我们来着手解决参数数目,修改上面的pointcut & advice。
    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;
    }


3、根据expensiveOperationTwo来修改原来的expensiveOperation's 的pointcut & advice。
    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;
    }


4、两个方法的pointcut & advice越来越相似,为了把两个pointcut & advice合并,现在就需要我们解决另外一个问题:cache key的生成策略。把它抽取出来,生成一个方法。
    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;
    }


5、ok,最后合并这两个pointcut & advice。
	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;
    }	
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics