原创作者: Readonly   阅读:2218次   评论:2条   更新时间:2011-05-26    
大家好, 偶又回来了, 继续嗡嗡作响的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
评论 共 2 条 请登录后发表评论
2 楼 mangolms 2013-01-08 16:32
对于AOP了解不多,只想对本文后记说一句:先去了解下“xwork的拦截器”的实现原理。
1 楼 xdyl 2011-07-19 14:24
1.使用Aop的Log方式去监测性能也要看应用在什么样的场景下.
对于一个SCA的系统来说,不同的子系统之间经常会互相调用.
这些调用的时间统计用Aop来做是目前最合适的方式.

2.缓存的处理用Aop感觉有一些别扭.不过也不是完全不能实现.
  可是如果不是用做缓存,用来做参数检查和验证是完全有可能的.

发表评论

您还没有登录,请您登录后再发表评论

文章信息

Global site tag (gtag.js) - Google Analytics