|
该帖已经被评为精华帖
|
|
|---|---|
| 作者 | 正文 |
|
时间:2004-08-06
大家好, 偶又回来了, 继续嗡嗡作响的AOP之旅, 废话少说, 先来看看AOP号称给可以带给我们的第一个好东东:
1. Modularized implementation of crosscutting concerns 嘿嘿, 一堆buzzword呢: modularity, crosscutting, concern, 偶来用土话解释一下吧, 就是号称可以把原来需要在N处代码里处理的问题, 移到一处地方来处理. 为了解释这个好处呢, AOP的鼓吹者通常会拿一个日志的例子来说明 (因为它最简单, 最容易实现), 给各位举一个简单的银行帐户的例子:
package readonly.aopsucks.test;
import junit.framework.TestCase;
import readonly.aopsucks.bank.Account;
import readonly.aopsucks.bank.impl.AccountImpl;
public class BankTest extends TestCase {
public void testDeposit() {
Account a = new AccountImpl(1000);
a.deposit(100);
assertEquals(1100, a.getBalance());
}
}
package readonly.aopsucks.bank;
public interface Account {
public void deposit(long amount);
public void withdraw(long amount);
public long getBalance();
}
package readonly.aopsucks.bank.impl;
import readonly.aopsucks.bank.Account;
public class AccountImpl implements Account {
private long balance;
public AccountImpl(long balance) {
this.balance = balance;
}
public void deposit(long amount) {
balance += amount;
}
public void withdraw(long amount) {
balance -= amount;
}
public long getBalance() {
return balance;
}
}
测试通过了, 但是我们想记录每个方法调用的时间怎么办呀? 每个方法开始的时候和结束的时候各加一句? 作为一个AOPer, 一定会大声的嘲笑你, No No 什么年代了还用这样粗劣的方法? 来看看AOP是怎么做的 (以下以aspectwerkz这个AOP实现为例子): 先写个Aspect (又一个buzzword?):
import java.util.Date;
import org.codehaus.aspectwerkz.joinpoint.JoinPoint;
public class LogAspect {
public void beforeCalled(JoinPoint joinPoint) {
System.out.println((new Date()) + ": before " + joinPoint.getTargetClass().getName() + "." + joinPoint.getSignature().getName()
+ " called");
}
public void afterCalled(JoinPoint joinPoint) {
System.out.println((new Date()) + ": after " + joinPoint.getTargetClass().getName() + "." + joinPoint.getSignature().getName()
+ " called");
}
}
组合pointcut, advice (又是2个buzzword?)
<aspectwerkz>
<system id="aopsucks">
<package name="readonly.aopsucks.aspect">
<aspect class="LogAspect">
<pointcut name="allMethod" expression="execution(* readonly.aopsucks.bank...*(..))"/>
<advice name="beforeCalled" type="before" bind-to="allMethod"/>
<advice name="afterCalled" type="after" bind-to="allMethod"/>
</aspect>
</package>
</system>
</aspectwerkz>
运行一下, 哇, 看起来好cool呀, 啧啧称奇 慢着, 回到现实世界来吧, 我们做的是实际应用, 不是这样的简单玩具, 象这种简单日志, 不就是一个触发器或拦截器的概念吗, 远古时代就有的东西, 改头换面, 加了一些buzzword, 又出来糊弄人...... 实际应用中AOP可以做复杂上下文环境的日志吗, NO! 别拿玩具出来骗小孩了! 接下去来看看AOP号称给可以带给我们的第2个好东东: 2. Once and only once, more code reuse. 这个倒没有什么buzzword了, 而且也是OOP追求的终极目标, 那么来看例子把, 还是上面那个银行帐户的例子, 加了一个列出每个月的帐务清单的方法.
public interface Account {
public String generateMonthlyReport(String month);
}
AOPer说这个方法可能比较耗时: 需要从数据库里查询一些记录, 然后封装成String返回, 我们用AOP给它加个Cache来提高性能吧:
package readonly.aopsucks.aspect;
import java.util.HashMap;
import java.util.Map;
import org.codehaus.aspectwerkz.joinpoint.JoinPoint;
import org.codehaus.aspectwerkz.joinpoint.MethodRtti;
public class CacheAspect {
private Map cache = new HashMap();
public Object cache(JoinPoint joinPoint) throws Throwable {
MethodRtti rtti = (MethodRtti)joinPoint.getRtti();
final Long key = new Long(calculateHash(rtti));
Object cachedValue = cache.get(key);
if (cachedValue == null) {
cachedValue = joinPoint.proceed();
cache.put(key, cachedValue);
}else{
System.out.println("using cache: " + cachedValue);
}
return cachedValue;
}
private long calculateHash(MethodRtti rtti) {
int result = 17;
result = 37 * result + rtti.getName().hashCode();
Object[] parameters = rtti.getParameterValues();
for (int i = 0, j = parameters.length; i < j; i++) {
result = 37 * result + parameters[i].hashCode();
}
return result;
}
}
<aspect class="CacheAspect" deployment-model="perInstance">
<pointcut name="cacheAble" expression="execution(* readonly.aopsucks.bank...generateMonthlyReport(..))"/>
<advice name="cache" type="around" bind-to="cacheAble"/>
</aspect>
AOPer: 看到没? 如果需要cache其他的方法, 那么我们修改一下pontcut的匹配符, 不用修改, 增加其他任何代码, 真正的reuse吧! 哇, 又看起来很cool? 再次回到现实生活来吧, 难道Cache的东西难道不需要刷新吗? 偶今天又往这个帐户里面存了一笔钱, 你的AOP Cache如何得知要清除对应的Key, 从而列出正确的当月清单来? 不同的Instance需要不同的Cache策略, 你的AOP Cache能做到? 实际应用的复杂性不是AOP这种简单的code reuse所能消除的! AOPer, 找些有实际意义的例子吧, 别老是拿这些幼稚的东东给偶们嘲笑了! 后记: 一个设计良好的OOP架构, 完全可以更简洁的实现上面这些幼稚的功能 ((比如xwork的拦截器), 更重要的是不需要你去了解一堆的buzzword 声明:JavaEye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
| 返回顶楼 | |
|
时间:2004-08-06
哈哈!很有意思!
说白了,就是看似解决了问题,其实根本没有解决问题。 说白了,只是提出一个问题解决方案,没有提出一个思路,一个框架,或者一个新技术所具有的底层理论! AOP只是分离关注点,但并没有减少关注点!不是吗?它的提出并不是说你可以不用考虑问题的解决!恰恰相反,为了整体框架的清晰,你还要理出更多的buzzword,定义更多的xml配置,然后将这些关注点“简单”的分离到独立的 部分。具体实现的复杂都一点都没有减少,该考虑的还是要考虑,该复杂的还是要复杂,一切都没有简单。相反,为了更好的分离关注点,有可能将本来较为简单的实现变得更为复杂。(难道大家没有受够传递多个参数的折磨吗?) 感觉,AOP不是让你更简单,而是更复杂。而这一切的一切都是为了 清晰的构架,可扩展的服务,简便的维护(建立在清楚了解AOP的基础上)。 投入与产出值得我们好好比较 |
|
| 返回顶楼 | |
|
时间:2004-08-06
这次我觉得readonly的论据不够有力度。
log和cache,你上面举的例子的确类似于hello world,拿它来嘲笑AOP,是不公平的。 aspect 和 pointcut也不能认为是buzzword,这就如同class, instance也会被不习惯oop的人认为是buzzword一样?这只是一些便于对话而引入的术语。 你的第一个log的例子,点出的是aspect中无法处理上下文的问题,这一点是正确的。这个观点gigix在4月份的javaeye交流上已经讲过。涉及到上下文的log的确有问题。 potian在上面一个帖子中说明,假若不考虑业务上下文, log的例子可以引申为profiling. 一个例子在这里: http://www.onjava.com/pub/a/onjava/2004/05/12/aop.html AOP 的目的,按照我的理解应该是透明的,便于分离用户。同样拿Cache的例子来说,涉及到业务层的cache,拿来做这个例子并不妥当,因为业务层cache往往会互相交织,难以处理(当然不是不可处理)。如果你把Cache应用在一些底层设施上,比如集群模式下的request session 复制,页面cache等等。 换句话说,你的枪头有点歪,只能说AOP不适合做某些事情,而这样的讨论,正好可以廓清一些误解,AOP不是万金油,就如同OOP一样,不是胡乱一切OO化就可以搞定一切的。 所以说Readonly不是反对的AOP,而是“嗡嗡作响的”AOP,不知道我这么理解对不对? JBOSS 4内部,已经大量使用了AOP,包括cluster 里面的状态复制,request session 复制等等。 |
|
| 返回顶楼 | |
|
时间:2004-08-06
谁要是认为AOP是buzzyword,你不玩就是了。反正我玩得很开心,我才不关心你怎么看。
|
|
| 返回顶楼 | |
|
时间:2004-08-06
曹晓钢 写道 这次我觉得readonly的论据不够有力度。
log和cache,你上面举的例子的确类似于hello world,拿它来嘲笑AOP,是不公平的。 不懂OOP的人会去学AOP? AOP鼓吹者既然号称其是OOP的进阶, 就不要拿Hello World这种东西出来嗡嗡作响, 找点有实际用途的出来反击我的愚蠢吧. 曹晓钢 写道 potian在上面一个帖子中说明,假若不考虑业务上下文, log的例子可以引申为profiling.
一个例子在这里: http://www.onjava.com/pub/a/onjava/2004/05/12/aop.html 按照那个文章里的例子, 那么AOP可以做得事情太多了: profiling, asynch, exception handle, lock ...... 但是, 到底有鬼东西阻止你用OOP不能优雅地实现这一类拦截器性质的东东? 曹晓钢 写道 所以说Readonly不是反对的AOP,而是“嗡嗡作响的”AOP,不知道我这么理解对不对?
bingo! 更准确的说是“嗡嗡作响的”AOPer, 在发出噪声以前, 先掌握好OOP吧. |
|
| 返回顶楼 | |
|
时间:2004-08-06
Readonly 写道 曹晓钢 写道 这次我觉得readonly的论据不够有力度。
log和cache,你上面举的例子的确类似于hello world,拿它来嘲笑AOP,是不公平的。 不懂OOP的人会去学AOP? AOP鼓吹者既然号称其是OOP的进阶, 就不要拿Hello World这种东西出来嗡嗡作响, 找点有实际用途的出来反击我的愚蠢吧. 这话也对,也不对。 Hello World并不就直接等于“初级”、“简单”、“弱智”。 哪怕是最复杂的技术,一般都会有一个类似Hello World的入门,以显示其容易掌握。这样也是引诱他人上钩的无奈之举,否则人家根本不感兴趣,你怎么办? 但是,这样的Hello World往往是一种误导,让学习它的人,以为这个技术就该这样使用,其实不然。 SUN为了说明EJB,搞了一个其大无比的Hello World,也就是petstore。结果他那样的使用EJB的方式,其蠢无比,后来被微软一脚踢爆,当然了,其实这只能说明petstore愚蠢,并不能说明EJB有问题。 而那些运行了Hello World就认为已经掌握了这种技术的人,早晚吃亏,也是意料中的事情。 我对AOP的看法是,这东西是超级牛刀,千万不要用它来杀鸡。在使用之前,要慎之又慎。 至于gigix的回复,我觉得与技术无关,没有必要。 |
|
| 返回顶楼 | |
|
时间:2004-08-06
Readonly 写道 曹晓钢 写道 potian在上面一个帖子中说明,假若不考虑业务上下文, log的例子可以引申为profiling.
一个例子在这里: http://www.onjava.com/pub/a/onjava/2004/05/12/aop.html 按照那个文章里的例子, 那么AOP可以做得事情太多了: profiling, asynch, exception handle, lock ...... 但是, 到底有鬼东西阻止你用OOP不能优雅地实现这一类拦截器性质的东东? 比如说,你要实现这样一个profiling 的tool,请问你会怎么做呢? 这个例子是一个非侵入性的好例子。面对一个你一无所知的老式系统,你如何使用拦截器? |
|
| 返回顶楼 | |
|
时间:2004-08-06
引用 比如说,你要实现这样一个profiling 的tool,请问你会怎么做呢?
很巧, 偶以前用过P6Spy, 看过一点它的代码, 以它里面讲的P6LogQuery为例子, 如果是一个良好的OO设计: 1. 应该Separate interfaces from implementation 2. 调用P6LogQuery的对象, 应该Dependency injection 3. 不应该用static方法 如果是这样的设计, 你要为P6LogQuery扩展一个profiling, 3分钟就写完了, 还有必要用AOP来捣鼓么? 再来看看它用AOP实现的InstrumentSQLAdvice, 需要对于JoinPoint了解的一清二楚, 这样丑陋的Advice还能re-use么, 比前面那个被我嘲笑的CacheAdvice的例子还要差N倍 (起码人家的Cache Key的生成策略还能重用). 引用 这个例子是一个非侵入性的好例子。面对一个你一无所知的老式系统,你如何使用拦截器?
OK, 如果你遇到的鬼东西是不可改动的旧系统, 如果你遇到的鬼东西是不想改动的腐烂代码, 如果你遇到的鬼东西是难以改动的垃圾设计, 那么偶承认错了, 对于Java而言, 除了修改ClassLoader, 偶无能为力...... |
|
| 返回顶楼 | |
|
时间:2004-08-06
我一直认为aop的最大用武之地在于遗留系统的处理上。只有对于这类系统,我们已经很难在代码级别上干些什么事情,只能靠外挂。
至于代码现成的情况,横切的那些东西如果对被切物有太多了解,耦合度那么大,就应当放在一起。而如果不需要对被切物有了解,或者了解较少,看来看去只有事务管理这一个看起来还比较方便,其他的都是toy级别的东西,对于企业级应用没有直接的帮助。 当然间接的也有,比如说那个所谓log能够起到一个profiling的作用,不过这类现成工具就很多,何必用aop来做。但aop的log也就仅限于此,难道在实际应用中我只用来记录函数进出时间?我要记的是函数里面干事的情况,如果用aop的方式来做,显然是个紧密耦合的东西。 Cache也一样。不是说拿来一个东西我就可以去cache,容量有限的情况下cache的乱七八糟的东西越多,命中率也越低。而根据cache命中和不命中时的访问代价以及cache策略,可以计算出一个阈值,如果命中率低于这个阈值,cache就没有意义。因此,在这里aop要做的不是说我把所有经手的东西都cache一下,而是要把经手的有cache价值的数据cache下来。而哪些数据有价值,这些信息则是紧耦合于应用逻辑的。 ............................. 另外,我一直在猜,还是没踩中。 |
|
| 返回顶楼 | |
|
时间:2004-08-06
只计一点,不计其它,眼光是不是应该看得更远一点。
说到cache的例子,我就借花献佛,举一个Adrian Colyer的例子: 一个DataProvider对象: [code:1] public class DataProvider { private int multiplicationFactor = 0; public DataProvider(int seed) { multiplicationFactor = seed; } public int expensiveOperation(int x) { try { Thread.sleep(1000); } catch (InterruptedException ex) {} return x * multiplicationFactor; } } [/code:1] 一个DataProvider的TestCase: [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); } protected void setUp() throws Exception { super.setUp(); provider = new DataProvider(100); } } [/code:1] 不知道如何让这个TestCase green? |
|
| 返回顶楼 | |










