玩技术,Geeker
一个原创技术文章分享网站

初窥Spring之面向切面编程

AOP什么鬼?

在这篇《初窥Spring之依赖注入》中说到了Spring框架的两个关键点,其中一个就是AOP,那这个AOP是什么鬼呢?

AOP就是传说中的面向切面编程,那这货到底是什么鬼呢?

面向切面编程:是指在程序运行期间将某段代码,动态的切入到某个类的指定方法的指定位置。这种编程思想就是面向切面编程。

面向切面编程的目标:

  • 把横切关注点从业务逻辑中分离,独立模块化
  • 在不改变现有代码的前提下,动态的添加功能

这么说,你可能觉的上面说的这些还太概念化,书面化;那么现在我们就来考虑一个实际的业务场景:

一个项目起初开发的时候没有考虑日志功能,而是在最后想为每个业务方法加上记录日志的功能。如果遇到这样的情况,是不是真的要重新编写每一个业务方法,给它们加上日志功能呢?(好吧,这种事情我们曾经搞过~~~)

基于这种业务场景,怎么搞呢?

给它加上“监控”

还是上面说的那个业务场景,再结合《初窥Spring之依赖注入》中的例子,我们来实现在每次doJob开始时打印一条日志,doJob完成以后,再打印一条日志。我们最开始的做法可能是这样的:

public void doJob() throws Exception {
    // 开始
    System.out.println("Begin doJob...");

    // 纯粹的测试代码
    // 真正的doJob
    System.out.println(enginner.getDescription());

    // 结束
    System.out.println("End doJob...");
}

是的,这样的确能够很好的完成工作。你也知道,需求是经常变的,现在产品汪又说了,再添加上权限认证功能。好的,没问题,代码就可能变成下面这样子了:

public void doJob() throws Exception {
    // 开始
    System.out.println("Begin doJob...");
    System.out.println("Begin authorization authentication");

    // 纯粹的测试代码
    // 真正的doJob
    System.out.println(enginner.getDescription());

    // 结束
    System.out.println("End authorization authentication")
    System.out.println("End doJob...");
}

是的,功能完成的没问题。但是,你有没有想过,随着需求的增加与变化,你的代码可能变成这样:

public void doJob() throws Exception {
    // 开始
    System.out.println("Begin doJob...");
    System.out.println("Begin authorization authentication");
    System.out.println("各种与真正业务无关的前期操作......");

    // 纯粹的测试代码
    // 真正的doJob
    System.out.println(enginner.getDescription());

    // 结束
    System.out.println("各种与真正业务无关的后期操作......");
    System.out.println("End authorization authentication");
    System.out.println("End doJob...");
}

是的,你的代码没有问题,可以很好的工作;但是,你也不得不承认,你的代码越来越臃肿了,不可维护了。在这一坨代码中,真正与业务相关的,也就下面这一句:

// 真正的doJob
System.out.println(enginner.getDescription());

当其他人再来看这段代码时,可能看了半天,才发现,真正与业务相关的才这么一句。“Oh, My God, What the fuck!!!”。

使用Spring来搞呢?

是的,这样糟糕的代码,这样初级的代码,这样新手写的代码,是不允许出现在我们的项目中的。那怎么来改进呢?这就需要让我们来感受一下Spring中面向切面编程的魔力了。上面的功能需求,通过Spring的改造,对应的代码如下:

public void doJob() throws Exception {
    // 纯粹的测试代码
    // 真正的doJob
    System.out.println(enginner.getDescription());
}

// 新增日志类
public class Log {
    public void beforeDoJob() {
        System.out.println("Begin doJob...");
        System.out.println("Begin authorization authentication");
        System.out.println("各种与真正业务无关的前期操作......");
    }

    public void endDoJob() {
        // 结束
        System.out.println("各种与真正业务无关的后期操作......");
        System.out.println("End authorization authentication");
        System.out.println("End doJob...");
    }
}

// AOP配置
<bean id="job" class="com.jellythink.Job">
        <!-- 将juniorengineer通过构造函数的方式注入到Job对象中 -->
        <constructor-arg ref="juniorengineer" />
</bean>

<bean id="log" class="com.jellythink.Log" />

<aop:config>
    <aop:aspect ref="log">
        <aop:pointcut id="embark" expression="execution(* doJob(..))" />
        <aop:before pointcut-ref="embark" method="beforeDoJob" />
        <aop:after pointcut-ref="embark" method="endDoJob" />
    </aop:aspect>
</aop:config>

认真阅读上面的代码,你会发现,我在代码中并没有嵌入任何Log类的相关信息,只是在配置文件中进行了对应的配置,就可以完成相关的业务需求。你可能惊讶,你可能好奇,先收起你好奇的下巴,我们继续看。

Spring AOP招式详解

上面使用了Spring了的AOP编程,但是你对Spring的AOP编程“招式”可能还不太清楚,比如如何配置AOP编程啊,可以配置哪些AOP切面啊;对于这些“招式”,我继续进行详细的总结。

首先,我们来明白一下几个概念的定义:

  • 切面(Aspect):一个关注点的模块化,这个关注点可能会横切多个对象;比如我们将权限系统模块化,而这个模块化的权限系统会应用到多个对象的操作中;通俗的来说,就是我们定义的切面类,也就是上面代码中的Log类;
  • 连接点(Joinpoint):在程序执行过程中某个特定的点,比如某方法调用的时候或者处理异常的时候。在Spring AOP中,一个连接点总是表示一个方法的执行;
  • 通知(Advice):“切面”对于某个“连接点”所产生的动作;其中包括了aroundbeforeafter等不同类型的通知;许多AOP框架(包括Spring)都是以拦截器做通知模型,并维护一个以连接点为中心的拦截器链;
  • 切入点(Pointcut):匹配连接点的断言。通知和一个切入点表达式关联,并在满足这个切入点的连接点上运行(例如,当执行某个特定名称的方法时)。切入点表达式如何和连接点匹配是AOP的核心:Spring缺省使用AspectJ切入点语法;
  • 目标对象(Target Object):被一个或者多个切面所通知的对象。也被称做被通知(advised)对象。 既然Spring AOP是通过运行时代理实现的,这个对象永远是一个被代理(proxied)对象;
  • AOP代理(AOP Proxy):AOP框架创建的对象,用来实现切面契约(例如通知方法执行等等)。在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。

接下来,我们再细细的看下通知类型:

  • 前置通知(Before advice):在某连接点之前执行的通知,但这个通知不能阻止连接点之前的执行流程(除非它抛出一个异常);
  • 后置通知(After returning advice):在某连接点正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回;
  • 异常通知(After throwing advice):在方法抛出异常退出时执行的通知;
  • 最终通知(After (finally) advice):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出);
  • 环绕通知(Around Advice):包围一个连接点的通知,如方法调用。这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它自己的返回值或抛出异常来结束执行。
<aop:before pointcut-ref="myPointCut" method="doAccessCheck" />
<aop:after-returning pointcut-ref="myPointCut"  method="doAfterReturning" />
<aop:after-throwing pointcut-ref="myPointCut"  method="doAfterThrowing" />
<aop:around pointcut-ref="myPointCut" method="doAround" />
<aop:after pointcut-ref="myPointCut" method="doAfter" />

最后再来说说这个Spring Aspectj切入点指示符语法定义。例如这样的一个切入点表达式:

execution(* com.sample.service.impl..*.*(..))

execution()是最常用的切点函数,在Spring AOP中目前也只有执行方法这一个连接点;整个表达式可以分为五个部分:

  1. execution():表达式主体;
  2. 第一个*号:表示返回类型,*号表示所有的类型;
  3. 包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,com.sample.service.impl包、子孙包下所有类的方法;
  4. 第二个*号:表示类名,*号表示所有的类;
  5. *(..):最后这个星号表示方法名,*号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。

与Filter的区别

AOP(Aspect-Oriented Programming,面向切面编程)是一种编程思想,并不是一种具体的实现,谈到实现一般有Filter和代理模式两种常见的使用方式,Spring中的AOP也是封装代理模式完成的,可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。OOP利用封装、继承和多态把一切事物打造成对象结构,但是对于所有对象中都存在的一些公共行为,OOP就显得无能为力,也就是说OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。抽象和接口虽好,但对所有不相干的对象建立共同的接口或父类未免有些生硬,例如日志功能,日志代码几乎散布在所有的对象层次中,而它和散布到对象的核心功能毫无关系,对于其他类型的代码,如安全性、异常处理和透明的持续性也是如此。因此,为减少这种大量的重复代码,面向切面技术诞生了,AOP和OOP的关系好似JSP和Servlet的关系,以此之长,补彼之短。引用此处

总结

写完这篇文章时,已经晚上11点多了。作为技术人,总是有些东西让我感到不安,烦躁。而我总是通过写博客的方式来释放内心的烦躁,缓解内心的不安。精彩内容还在后面。。。。。。

晚安,各位!!!

果冻想-一个原创技术文章分享网站。

2017年6月1日 于呼和浩特。

打赏

未经允许不得转载:果冻想 » 初窥Spring之面向切面编程

分享到:更多 ()

评论 4

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
  1. #2

    最近在学lua 看看你的文章很nice 同内蒙人 加油!

    GPSGH2个月前 (06-05)回复
  2. #1

    果冻终于恢复更新了 T.T,等了好久……

    Liam1个月前 (06-15)回复

在这里玩技术,享受技术带来的疯狂

捐赠名单关于果冻