Spring(五)AOP

20 篇文章 0 订阅
5 篇文章 0 订阅


AOP概述

AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。AOP实际是GoF设计模式的延续,设计模式孜孜不倦追求的是调用者和被调用者之间的解耦,提高代码的灵活性和可扩展性,AOP可以说也是这种目标的一种实现。

在Spring中提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务(例如审计(auditing)和事务(transaction)管理)进行内聚性的开发。应用对象只实现它们应该做的——完成业务逻辑——仅此而已。它们并不负责(甚至是意识)其它的系统级关注点,例如日志或事务支持。

AOP主要是为了能将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。

AOP和OOP

AOP和OOP互为补充,面向对象编程将程序分解成各个层次的对象,而面向切面编程将程序运行过程分解成各个切面。可以简单地理解成:面向对象编程时从静态角度考虑程序结构,而面向切面编程则是从动态角度考虑程序运行过程。

OOP(面向对象编程)针对业务处理过程的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分。
而AOP则是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。这两种设计思想在目标上有着本质的差异。

例如:对于“雇员”这样一个业务实体进行封装,自然是OOP/OOD的任务,我们可以为其建立一个“Employee”类,并将“雇员”相关的属性和行为封装其中。而用AOP设计思想对“雇员”进行封装将无从谈起。同样,对于“权限检查”这一动作片断进行划分,则是AOP的目标领域。而通过OOD/OOP对一个动作进行封装,则有点不伦不类。可以认为,OOD/OOP面向名词领域,AOP面向动词领域。

传统的OOP编程里以对象为核心,整个软件系统由一系列相互依赖的对象组成,而这些对象将被抽象成一个个类,并允许通过类继承来管理类与类之间一般到特殊的关系。由于类可以继承,因此可以把具有相同功能或相同特性的属性抽象到一个层次分明的类结构体系中。OOP这种编程理念的提出本身是很强大的,其优势在业界是有目共睹的。但随着软件规模的增大,应用的不断升级优化,在应对某些场景需求方面,OOP也会显得不擅长。

比如说,越来越多的非业务需求(日志和验证等)加入后, 原有的业务方法急剧膨胀. 每个方法在处理核心逻辑的同时还必须兼顾其他多个关注点,这会导致代码异常混乱。在日志需求方面, 为了满足这个单一需求, 就不得不在多个模块(方法)里多次重复相同的日志代码。如果日志需求发生变化, 必须修改所有模块,这会导致代码分散,不利于维护。

针对这样的一些问题 ,很多人会认为一个定义良好的 OOP 的接应该也能够解决。定义良好的 OOP 的接口的确可以解决这样的一些问题。但是,对于 OOP 中的接口来说,它仍然需要我们在相应的模块中去调用该接口中相关的方法,这是 OOP 所无法避免的,并且一旦接口不得不进行修改的时候,所有事情会变得一团糟。AOP 则不会这样,你只需要修改相应的 Aspect,再重新编织(weave)即可。但是AOP 也不能代替 OOP。核心的需求仍然会由 OOP 来加以实现,而 AOP 将会和 OOP 整合起来,互补长短。



开启AOP之旅

这里写图片描述

在应用 AOP 编程时, 仍然需要定义公共功能, 但可以明确的定义这个功能在哪里, 以什么方式应用, 并且不必修改受影响的类. 这样一来横切关注点就被模块化到特殊的对象(切面)里。
AOP 的好处:
各步骤之间有良好的隔离性,每个事物逻辑位于一个位置, 代码不分散, 便于维护和升级。
源代码无关性。业务模块更简洁, 只包含核心业务代码。

AOP 术语

>切面(Aspect): 横切关注点(跨越应用程序多个模块的功能)被模块化的特殊对象。切面用于组织多个Advice,Advice放在切面中定义。

>增强处理(Advice): AOP框架在特定的切入点执行的增强处理。处理有“around”、“before”和“after”等类型。

>目标(Target): 被AOP框架进行增强处理的对象,也被称为被增强的对象。如果AOP框架采用的是动态AOP实现,该对象就是一个被代理的对象。

>代理(Proxy): AOP框架创建的对象,可以简单地理解成,代理就是对目标对象的增强的加强。Spring的AOP代理可以是JDK动态代理,也可以是cglib代理。前者为实现接口的目标对象的代理,后者为不实现接口的目标对象代理。

>连接点(Joinpoint):程序执行的某个特定位置:如类某个方法调用前、调用后、方法抛出异常后等。连接点由两个信息确定:方法表示的程序执行点;相对点表示的方位。例如 ArithmethicCalculator#add() 方法执行前的连接点,执行点为 ArithmethicCalculator#add(); 方位为该方法执行前的位置

>切点(pointcut):可以插入增强处理的连接点。当某个连接点满足指定要求时。该连接点将添加增强处理,该连接点就变成切入点。每个类都拥有多个连接点:例如 ArithmethicCalculator 的所有方法实际上都是连接点,即连接点是程序类中客观存在的事务。AOP 通过切点定位到特定的连接点。类比:连接点相当于数据库中的记录,切点相当于查询条件。切点和连接点不是一对一的关系,一个切点匹配多个连接点,切点通过 org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。

>引入(Introduction):用来给一个类型声明额外的方法或属性(也被称为连接类型声明(inter-type declaration))。Spring允许引入新的接口(以及一个对应的实现)到任何被代理的对象。例如,你可以使用引入来使一个bean实现IsModified接口,以便简化缓存机制。

>织入(Weaving):把切面连接到其它的应用程序类型或者对象上,并创建一个被通知的对象。这些可以在编译时(例如使用AspectJ编译器),类加载时和运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。

通知类型

>前置通知(Before advice):在某连接点之前执行的通知,但这个通知不能阻止连接点之前的执行流程(除非它抛出一个异常)。

>后置通知(After returning advice):在某连接点正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回。

>异常通知(After throwing advice):在方法抛出异常退出时执行的通知。

>最终通知(After (finally) advice):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。

>环绕通知(Around Advice):包围一个连接点的通知,如方法调用。这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它自己的返回值或抛出异常来结束执行。

环绕通知是最常用的通知类型。和AspectJ一样,Spring提供所有类型的通知,我们推荐你使用尽可能简单的通知类型来实现需要的功能。例如,如果你只是需要一个方法的返回值来更新缓存,最好使用后置通知而不是环绕通知,尽管环绕通知也能完成同样的事情。用最合适的通知类型可以使得编程模型变得简单,并且能够避免很多潜在的错误。比如,你不需要在JoinPoint上调用用于环绕通知的proceed()方法,就不会有调用的问题。

使用AspectJ实现AOP

AspectJ是基于Java语言的AOP框架,提供了强大的AOP功能。Spring4.0的AOP与AspectJ进行了良好的集成。

AspectJ主要包括两部分:一个部分定义了如何表达、定义AOP编程中的语法规范;另一部分是工具部分,包括编译器、调试工具等。

>本地安装AspectJ

AspectJ是Eclipse下面的一个开源项目,下载地址:http://www.eclipse.org/aspectj/downloads.php,下载完成后得到一个aspectj-1.8.9.jar文件,打开命令行窗口,进入aspectj-1.8.9.jar文件所在路径,输入如下命令:

java -jar aspectj-1.8.9.jar


运行上面命令会跳出如下窗口,按提示选好安装路径,然后分别按提示添加CLASSPATH、PATH。

这里写图片描述

这里写图片描述

成功安装了Aspectj后,会在D:\developer\java\aspectj1.8路径下(Aspectj的安装路径)看到如下文件结构。

这里写图片描述

>bin路径下存放aj、aj5、ajc、ajdoc、ajbrowser等命令,其中ajc命令为常用命令,作用类似于9javac,用于对普通的java类进行编译时增强。

>doc路径下存放Aspectj的使用说明、参考手册、API文档等文档。

>lib路径下的4个jar包文件是Aspectj的核心类库。

>在eclipse里安装AspectJ

在eclipse里要使用AspectJ创建项目,要配置AspectJ插件AJDT 。首先,打开AJDT下载页: http://www.eclipse.org/ajdt/downloads/ ,看到如下页面。

这里写图片描述

找到对应eclipse版本的ajdt更新地址,将其复制到eclipse插件安装网址搜寻框。

这里写图片描述

点击”Next”下一步安装,安装完成后重启eclipse,点“New”新建项目,可以在新建对话框里看到Aspect项目相关选项。

这里写图片描述

这样就可在eclipse里创建AspectJ项目应用了。

>AspectJ应用示例

下面先写两个简单的Java类,用于模拟系统中的业务逻辑组件,实际应用好在哪个无论多少个类,AspectJ的处理方式都是一样的。

package com.afy.app.service;

public class Hello
{
    // 定义一个简单方法,模拟应用中的业务逻辑方法
    public void foo()
    {
        System.out.println("执行Hello组件的foo()方法");
    }
    // 定义一个addUser()方法,模拟应用中的添加用户的方法
    public int addUser(String name , String pass)
    {
        System.out.println("执行Hello组件的addUser添加用户:" + name);
        return 20;
    }
}

另外一个World组件类如下。

package com.afy.app.service;

public class World{
    // 定义一个简单方法,模拟应用中的业务逻辑方法
    public void bar(){
        System.out.println("执行World组件的bar()方法");
    }
}


这里的两个业务组件类定义了三个方法,用于模拟系统所包含三个业务逻辑方法,下面使用一个主程序来模拟系统调用两个业务组件的三个业务方法。

package com.afy.test;

import com.afy.app.service.Hello;
import com.afy.app.service.World;

public class AspectJTest{
    public static void main(String[] args){
        Hello hello = new Hello();
        hello.foo();
        hello.addUser("孙悟空" , "7788");
        World world = new World();
        world.bar();
    }
}


运行后程序输出如下。

这里写图片描述

现在假设客户要求在执行所有业务方法前先执行权限检查,如果使用传统的编程方式,开发者必须先定义一个权限检查的方法,然后由此打开每个业务逻辑方法,并修改业务方法的源代码,增加调用权限检查的方法,但这种方式需要对所有业务组件中的每个业务方法都进行修改,所以不仅容易引入新的错误,而且维护成本相当大。如果使用AspectJ的AOP支持,则只要添加如下特殊的”Java类”即可。

package com.afy.app.aspect;

public aspect AuthAspect {
    // 指定在执行org.crazyit.app.service包中任意类的、任意方法之前执行下面代码块
        // 第一个星号表示返回值不限;第二个星号表示类名不限;
        // 第三个星号表示方法名不限;圆括号中..代表任意个数、类型不限的形参
        before(): execution(* com.afy.app.service.*.*(..)){
            System.out.println("模拟进行权限检查...");
        }
}


运行程序,便可以看到神奇的事情发生了。

这里写图片描述

上面代码中不使用class、interface、enum等,而使用aspect,aspect不是Java支持的关键字,是AspectJ才能识别的关键字,AuthAspect不是一个Java类,代码主体里面不是方法,而是指定在执行某些类的某些方法之前,AspectJ会自动先调用该代码块中的代码。

从运行结果看,不需要对Hello.java、World.java等业务组件进行修改,但却能满足客户的需求。如果客户再次提出新需求,比如需要在执行所有业务方法之后增加记录日志的功能。这里我们再定义一个LogAspect,程序如下。

package com.afy.app.aspect;

public aspect LogAspect {
    //定义一个Pointcut,其名为logPointcut
    //该Pointcut代表了后面给出的切入点表达式,这样可服用该切入点表达式
    pointcut logPointcut() 
        : execution(* com.afy.app.service.*.*(..));
    after():logPointcut(){
        System.out.println("模拟记录日志...");
    }
}


运行程序,结果会是酱紫。

这里写图片描述

可以吧!程序中定义了一个Pointcut: logPointcut(),这种用法就是为后面的切入点表达式起个名字,方便后面复用这个切入点表达式,如果程序中有多个代码块需要使用该切入点表达式,这些代码都可以直接复用自定义的logPointcut,而不用重复编写繁琐的切入点表达式。

如果现在需要在业务组件的所有业务方法之前启动事务,并在方法执行结束时关闭事务,只要添加TxAspect.java代码即可。

package com.afy.app.aspect;

import org.aspectj.lang.ProceedingJoinPoint;

public aspect TxAspect {
    //指定执行Hello.sayHello()方法时执行下面的代码块
    Object around() : call(* com.afy.app.service.*.*(..)) {
        System.out.println("模拟开启事务......");
        //回调原来的目标方法
        Object rvt = proceed();
        System.out.println("模拟结束事务......");
        return rvt;
    }
}

看我们程序的运行效果。

这里写图片描述

很强大哦,有不有!代码指定proceed()代表回调原来的目标方法,这样位于proceed()之前的代码就会被添加在目标方法之前,位于proceed()代码之后的代码就会添加在目标 方法之后。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值