AOP原理与应用

主要内容
1. AOP概述
2. Test Code
3. AOP原理与实现
    3.1 原理分析
    3.2 设计实现
4. 总结

1. AOP概述

AOP(Aspect Oriented Programming),即面向切面编程。被认为是对面向对象编程OOP的一种极大补充,大量应用于处理一些具有横切逻辑的系统中。比如:事务、缓存、安全检查等等。

为什么需要AOP?有什么OOP解决不了的吗?

在OOP的世界中,一切是以对象为核心,我们所构建的系统就是若干个具有独立状态和行为的对象组成。但随着软件日益复杂,OOP渐渐无法很好的解决我们所面对的问题。比如上面提到的事务处理,不用AOP的思想当然也可以解决:

方案1: 对于一个事务的处理过程进行分析,我们知道每个事务都包含事务的开启,提交、回滚等操作。那么最简单粗暴的方法就是将事务开启、提交、回滚的代码进行Ctrl+C、Ctrl+V。

方案2: 方案1思路太low,需要高级点的,OOP中的抽象、继承、封装啊!那好,构造一个公共的基类,将事务开启、提交、回滚等操作封装在基类中,然后每一个需要处理事务的类都继承这个基类,进行相应的方法调用即可。这不就实现代码复用了嘛。。。

这样就解决问题了?继续。。。

随着系统的发展,需求新增、变更必然是相当频繁的。新需求来了:需要某些处于事务中的方法执行前加上请求者的身份验证的操作。

哎。。。继续改!于是乎在基类中新增身份验证的处理逻辑。同时修改子类中对应方法的处理逻辑。

然后。。。新需求又来了。。。

这就是痛点,有痛点就会有解决方案:AOP应运而生。

本文主要探究AOP的原理、实现AOP所用到的一些方法。

2. Test Code

public class AdvisorChainTest extends BaseTest{

    static class ProxyBuilder{
        public static Object buildProxy(Class<?> interfaces, Object target, List<MethodInterceptor> list){
            return Proxy.newProxyInstance(ProxyBuilder.class.getClassLoader(), 
                    new Class<?>[]{interfaces},  new DefaultInvocationHandler(target, list));
        }
    }

    @Test
    public void testAOP() {
        
        Set<String> exclusionMethodNames = new HashSet<String>();
        exclusionMethodNames.add("delete");//拦截delete方法
        
        //过滤方法的拦截器
        MethodFilterInterceptor filter = new MethodFilterInterceptor(exclusionMethodNames);
        
        //记录方法执行时间拦截器
        TimeLogInterceptor time = new TimeLogInterceptor();
        
        List<MethodInterceptor> list = Arrays.asList(filter,time);
        
        UserService user = new UserServiceImpl();
        UserService proxy = (UserService)ProxyBuilder.buildProxy(UserService.class,user,list);

        proxy.update(null);
        assertTrue(time.msg.contains("update execute time:"));
        
        try{
            proxy.delete(null);
            fail("method delete is not allowed!");
        }catch(Exception e){
            assertEquals(MethodNotAllowedException.class, e.getClass());
        }
    }
}

在测试用例中,实现了两个拦截器:TimeLogInterceptor,MethodFilterInterceptor;一个用于记录方法执行时间,另一个用于方法过滤。

3. AOP原理实现

3.1 原理分析

在上一个小节AOP概述中,已经分析出AOP要解决的问题都有一个共性:即所谓的横切逻辑。比如测试用例中的TimeLogInterceptor,用于统计方法执行的时间。对于这一需求,首先想到的使用代理的方法。当然,这里指的就是动态代理。实现思路如下:

通过代理对象拦截对目标方法的调用操作,在回调方法中添加需要的横切逻辑。


Proxy

还有一个问题:我是实现的横切逻辑不止一个的情况怎么办?难道在Proxy对象的回调方法中进行方法堆积吗?这不又回到了第一节中的方案1了。针对这种问题,一个比较好的处理思路是:责任链。结构图如下:

软件设计准则:在重要的过程上设置拦截接口,这是体现软件可扩展性的一种基本实现方式。

Proxy + Chain of Responsibility

3.2 设计实现

  1. 对每次方法调用进行抽象:MethodInvocation接口,拦截器接口:MethodInterceptor
/**
 * MethodInvocation代表方法的执行
 * 
 * @author wqx
 *
 */
public interface MethodInvocation {
    
    /**
     * 获取方法对象
     * 
     * @return
     */
    public Method getMethod();
    
    /**
     *  获取参数
     * 
     * @return
     */
    public Object[] getParameters();
    
    /**
     * 执行下一个方法
     * 
     * @return
     * @throws Exception
     */
    Object executeNext() throws Exception;
    
}

public interface MethodInterceptor {
    
    Object invoke(MethodInvocation invocation) throws Exception;
    
}

  1. MethodInvocation的默认实现
/**
 * MethodInvocation的默认实现
 * 
 * @author wqx
 *
 */
public class DefaultMethodInvocation implements MethodInvocation {

    //目标对象
    private Object target;
    //代理
    private Object proxy;
    //目标方法
    private Method method;
    //参数
    private Object[] parameters;
    
    //拦截器链
    private List<?> interceptors;
    //当前执行的Interceptor的索引(范围:0-interceptors.size()-1),初始为-1
    private int currentIndex = -1;
    
    public DefaultMethodInvocation(Object target,Object proxy,Method method,Object[] parameters, List<?> interceptors){
        this.target = target;
        this.proxy = proxy;
        this.method = method;
        this.parameters = parameters;
        this.interceptors = interceptors;
    }
    
    @Override
    public Object executeNext() throws Exception {
        //判断拦截器链是否执行完
        if(this.currentIndex == this.interceptors.size() - 1){
            //如果执行完,直接执行目标方法
            method.setAccessible(true);
            return method.invoke(target, parameters);
        }
        Object interceptor = this.interceptors.get(++this.currentIndex);
        MethodInterceptor methodInterceptor = (MethodInterceptor)interceptor;
        return methodInterceptor.invoke(this);
    }
    
    //getter and seter
}
  1. 要实现动态代理,当然需要实现InvocationHandler接口啦
public class DefaultInvocationHandler implements InvocationHandler {

    private Object target;
    
    private List<MethodInterceptor> interceptorsChain;
    
    public DefaultInvocationHandler(Object target, List<MethodInterceptor> interceptorsChain){
        this.target = target;
        this.interceptorsChain = interceptorsChain;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        MethodInvocation methodInvocation;
        
        List<MethodInterceptor> chain = interceptorsChain;
        
        Object relVal;
        if(chain != null && !chain.isEmpty()){
            methodInvocation = new DefaultMethodInvocation(target,proxy,method,args,chain);
            relVal = methodInvocation.executeNext();
        }else{//直接调用目标方法
            relVal = method.invoke(target, args);
        }
        return relVal;
    }
}

在回调方法invoke中,我们首先判断拦截器链是否需要执行,如果需要执行拦截器链,那么就将这次方法调用信息封装成MethodInvocation,然后调用methodInvocation.executeNext()。在DefaultMethodInvocation的实现中可以看到,executeNext() 方法要做的就是查看拦截器链interceptors是否执行完毕,如果执行完了,那么调用目标方法method.invoke(target, parameters),如果拦截器链没有执行完,那么就获取下一个拦截器并执行。

//获取下一个拦截器
Object interceptor = this.interceptors.get(++this.currentIndex);
MethodInterceptor methodInterceptor = (MethodInterceptor)interceptor;
//执行拦截器的invoke方法
return methodInterceptor.invoke(this);

4. 总结

在如今的系统中,AOP的应用程度已经非常广泛。本文对AOP概念进行简单的阐述,并对AOP的原理进行了简单的实现。主要用到的方法是将横切逻辑在代理类的回调方法中实现,并通过责任链模式实现了软件设计中的一个重要基本特性:在重要过程中设置拦截接口。比如本文中提到的通过代理类进行方法调用。那么在方法调用这个过程中我们可能需要实现很多逻辑:统计调用次数;统计调用时间;添加黑白名单进行过滤等等。没有哪个框架可以Cover所有的需求,允许外置行为,这是框架基本的扩展方法。

完整源码

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容

  • 本博中关于spring的文章:Spring IOC和AOP原理,Spring事务原理探究,Spring配置文件属性...
    Maggie编程去阅读 4,095评论 0 34
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,591评论 18 139
  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,170评论 11 349
  • 唐朝诗人陈子昂千金买琴自我炒作 一夜成名人 唐代诗人自我推销的人太多了,比如说,李庆西《禅外禅》一书转引宋代尤袤《...
    盖伊里奇阅读 791评论 0 1
  • 我惧你,乍逢钟情惹猜忌;我惧你,碾转相思损心气;我惧你,脉脉情深添憔悴;我惧你,被空违誓旁人议。
    鱼嚣阅读 203评论 3 1