AOP即面向切面编程,它通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。常用于日志记录,性能统计,安全控制,事务处理,异常处理等等。

中文名称:面向切面的编程

英文名称:Aspect Oriented Programming

正常程序执行流程都纵向执行流程:

  1. 在原有纵向执行流程中添加一个横切面
  2. 不需要修改原有程序代码,体现出程序的高扩展性

特点:

  • 高扩展性
  • 原有功能相当于释放了部分逻辑,让职责更加明确

用一句话来说面向切面的编程就是指:在原有纵向执行流程中,针对某一个或者某一些方法添加通知(前置通知和后置通知以及环绕通知),形成横切面过程就叫做面向切面编程。

1. AOP 面向切面编程术语

描述切面的常用术语有通知(advice)、切点(pointcut)和连接点(join point)等。下图展示了这些概念是如何关联在一起的。

1.1 通知 Advice

在AOP术语中,切面的工作被称为通知。 通知定义了切面是什么以及何时使用。除了描述切面要完成的工作,通知还解决了何时执行这个工作的问题。Spring切面可以应用5种类型的通知:

  • 前置通知(Before):在目标方法被调用之前调用通知功能;
  • 后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么;
  • 返回通知(After-returning):在目标方法成功执行之后调用通知;
  • 异常通知(After-throwing):在目标方法抛出异常后调用通知;
  • 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。

1.2 连接点(Join point)

我们的应用可能有数以千计的时机应用通知。这些时机被称为连接点。连接点是在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。

1.3 切点(Pointcut)

如果说通知定义了切面的“什么”和“何时”的话,那么切点就定义了“何处”。切点的定义会匹配通知所要织入的一个或多个连接点。我们通常使用明确的类和方法名称,或是利用正则表达式定义所匹配的类和方法名称来指定这些切点。有些AOP框架允许我们创建动态的切点,可以根据运行时的决策(比如方法的参数值)来决定是否应用通知。

1.4 切面(Aspect)

切面是通知和切点的结合。通知和切点共同定义了切面的全部内容——它是什么,在何时和何处完成其功能。

1.5 引入(Introduction)

引入允许我们向现有的类添加新方法或属性。例如,我们可以创建一个Auditable通知类,该类记录了对象最后一次修改时的状态。这很简单,只需一个方法,setLastModified(Date),和一个实例变量来保存这个状态。然后,这个新方法和实例变量就可以被引入到现有的类中,从而可以在无需修改这些现有的类的情况下,让它们具有新的行为和状态。

1.6 织入(Weaving)

织入是把切面应用到目标对象并创建新的代理对象的过程。切面在指定的连接点被织入到目标对象中。在目标对象的生命周期里有多个点可以进行织入:

  • 编译期:切面在目标类编译时被织入。这种方式需要特殊的编译器。AspectJ的织入编译器就是以这种方式织入切面的。
  • 类加载期:切面在目标类加载到JVM时被织入。这种方式需要特殊的类加载器(ClassLoader),它可以在目标类被引入应用之前增强该目标类的字节码。AspectJ 5的加载时织入(load-timeweaving,L TW)就支持以这种方式织入切面。
  • 运行期:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态地创建一个代理对象。Spring AOP就是以这种方式织入切面的。

2. Spring 对AOP的支持

Spring 提供了4种类型的AOP支持:

  1. 基于代理的经典Spring AOP(不推荐使用)
  2. 借助Spring AOP 命名空间,纯POJO切面
  3. @Aspect注解驱动的切面
  4. 注入Aspect切面

经典的Spring AOP十分笨重和过于复杂,它之间使用ProxyFactory Bean让人感觉厌烦。所以我们不推荐使用基于代理的经典的Spring AOP。

借助Spring的AOP命令空间,我们可以将存POJO转化为切面。实际上,这只是提供了满足切点条件时所要用的调用的方法。遗憾的是,这种技术需要XML的配置,但这的确是声明式地将对象转化为切面的简单方法。本质上还是使用Spring基于代理的AOP。

Spring借鉴了AspectJ的切面,已提供注解驱动的AOP。本质上它还是基于代理的AOP,但是编程模型机会与编写成熟的AspectJ注解切面完全一致。

AspectJ切面,提供了超过简单的方法调用(比如构造器或者属性拦截),AspectJ切面可以拦截对象更多的调用。

2. Spring在运行时通知对象

通过在代理类中包裹切面,Spring在运行期把切面织入到Spring管理的bean中。如下图所示,代理类封装了目标类,并拦截被通知方法的调用,再把调用转发给真正的目标bean。当代理拦截到方法调用时,在调用目标bean方法之前,会执行切面逻辑。

直到应用需要被代理的bean时,Spring才创建代理对象。如果使用的是ApplicationContext的话,在ApplicationContext从BeanFactory中加载所有bean的时候,Spring才会创建被代理的对象。因为Spring运行时才创建代理对象,所以我们不需要特殊的编译器来织入Spring AOP的切面。正如前面所探讨过的,通过使用各种AOP方案可以支持多种连接点模型。因为Spring基于动态代理,所以Spring只支持方法连接点。这与一些其他的AOP框架是不同的,例如AspectJ和JBoss,除了方法切点,它们还提供了字段和构造器接入点。Spring缺少对字段连接点的支持,无法让我们创建细粒度的通知,例如拦截对象字段的修改。而且它不支持构造器连接点,我们就无法在bean创建时应用通知。但是方法拦截可以满足绝大部分的需求。如果需要方法拦截之外的连接点拦截功能,那么我们可以利用Aspect来补充Spring AOP的功能。

3. 借助Spring AOP 命名空间,纯POJO切面

每个通知都需要实现接口或者类

前置通知
public class AfterAdvice implements AfterReturningAdvice {@Overridepublic void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {System.out.println("切点方法返回对象: "+o);System.out.println("切点方法对象:"+method+ ", 方法名:"+method.getName());if(method!=null && objects.length>0)System.out.println("切点方法参数:"+objects);elseSystem.out.println("切点所在方法没有参数.");System.out.println("切点方法对象:"+o1);System.out.println("---------后置通知--------");System.out.println();}
}环绕通知
public class AroundAdvice implements MethodInterceptor {@Overridepublic Object invoke(MethodInvocation methodInvocation) throws Throwable {System.out.println("---------执行前置--------");Object proceed = methodInvocation.proceed();System.out.println("---------后置通知--------");return proceed;}
}后置通知
public class BeforeAdvice implements MethodBeforeAdvice {@Overridepublic void before(Method method, Object[] objects, Object o) throws Throwable {System.out.println("切点方法对象:"+method+ ", 方法名:"+method.getName());if(method!=null && objects.length>0)System.out.println("切点方法参数:"+objects);elseSystem.out.println("切点所在方法没有参数.");System.out.println("切点方法对象:"+o.getClass());System.out.println("---------执行前置--------");}
}异常通知 只有当切点报异常后,才能触发异常通知
public class ThrowAdvice implements ThrowsAdvice{public void afterThrowing(Method method, Object[] objects, Object target, Exception ex) throws Throwable{//Do something with ExceptionSystem.out.println("执行异常 通过Schema-Based的方法执行异常: "+ex.getMessage());}public void afterThrowing(Exception ex) throws Throwable{//Do something with ExceptionSystem.out.println("执行异常 通过Schema-Based的方法执行异常2: "+ex.getMessage());}
}

定义切点(需要被切入的类)

public class Worker {public void doSomething(String name){switch (name){case "watch":System.out.println("----------watch TV---------");break;default:break;}}public void doNoCutSomething(String name){int i=1/0;switch (name){case "watch":System.out.println("----------watch TV---------");break;default:break;}}
}

配置Spring配置文件在<aop:config>中配置

<!--定义可以通过注解的形式扫描包--><context:component-scan base-package="com.hust"/><!--配置通知类对象,在切面中引入--><bean id="beforeAdvice" class="com.hust.edu.advice.BeforeAdvice"/><bean id="afterAdvice" class="com.hust.edu.advice.AfterAdvice"/><bean id="aroundAdvice" class="com.hust.edu.advice.AroundAdvice"/><bean id="worker" class="com.hust.edu.pointcut.Worker"/><bean id="mythrows" class="com.hust.edu.advice.ThrowAdvice"></bean><!--配置切面--><aop:config><!--配置切点--><aop:pointcut id="pointcut" expression="execution(* com.hust.edu.pointcut.Worker.*(..))" /><!--配置前置通知--><aop:advisor advice-ref="beforeAdvice" pointcut-ref="pointcut"/><!--配置后置通知--><aop:advisor advice-ref="afterAdvice" pointcut-ref="pointcut"/><!--配置异常通知--><aop:advisor advice-ref="mythrows" pointcut-ref="pointcut"/><!--配置环绕通知--><!--<aop:advisor advice-ref="aroundAdvice" pointcut-ref="pointcut"></aop:advisor>--></aop:config><aop:aspectj-autoproxy expose-proxy="true"></aop:aspectj-autoproxy>

表达式(在配置切面的切点中定义切点):

execution(* com.hust.edu.pointcut.Worker.doSomething(String))
  • 通配符 * 匹配任意方法名、类名、一级包名
  • 如果要匹配任意类型方法参数(…)

4. 使用AspectJ实现切面(XML配置方式)

新建构造通知类

public class MyParamAdvice implements ThrowsAdvice{public void myExeception(Exception e){System.out.println("---------执行异常通知--------");System.out.println(e.getMessage());}public void beforeCall(String name,int time){System.out.println("name: "+name+" time: "+time);System.out.println("---------执行前置--------");}public void beforeCal2(String name){System.out.println("name: "+name);System.out.println("---------执行前置--------");}public void afterCall(){System.out.println("---------执行后置--------");}public void afterCallReturning(){System.out.println("---------后置通知Returning--------");}public Object aroundCall(ProceedingJoinPoint joinPoint) throws Throwable {System.out.println("---------执行环绕前置--------");Object proceed = joinPoint.proceed();System.out.println("---------执行环绕后置--------");return proceed;}
}

定义切点(需要被切入的类)

public class ParamWorker {public void doSomething(String name,int time) throws Exception{//抓取异常通知//int i=5/0;switch (name){case "watch":System.out.println("----------watch TV---------");break;default:break;}}public void doSomething(String name) throws Exception{//抓取异常通知//int i=5/0;switch (name){case "watch":System.out.println("----------watch TV---------");break;default:break;}}public void doNoCutSomething(String name) throws Exception{switch (name){case "watch":System.out.println("----------watch TV---------");break;default:break;}}
}

配置Spring配置文件在<aop:config>中配置

    <bean id="myParamthrows" class="com.hust.edu.advice.MyParamAdvice"></bean><!--AOP配置--><aop:config><aop:pointcut id="parampointcut" expression="execution(* com.hust.edu.pointcut.ParamWorker.doSomething(String,int)) and args(name,time)" /><aop:pointcut id="parampointcut2" expression="execution(* com.hust.edu.pointcut.ParamWorker.doSomething(String)) and args(name)" /><!--配置切面--><aop:aspect ref="myParamthrows"><!--<aop:after-throwing method="myExeception" pointcut-ref="parampointcut" throwing="e"/>--><aop:before method="beforeCall" pointcut-ref="parampointcut" arg-names="name,time" /><aop:before method="beforeCal2" pointcut-ref="parampointcut2" arg-names="name" /><!--<aop:after method="afterCall" pointcut-ref="pointcut"/>--><!--<aop:after-returning method="afterCallReturning" pointcut-ref="pointcut"/>--><!--<aop:around method="aroundCall" pointcut-ref="pointcut"/>--></aop:aspect></aop:config><bean id="paramWorker" class="com.hust.edu.pointcut.ParamWorker"/>

配置Spring配置文件(重点配置参数注入)

  1. <aop:after> 后置通知:是否出现异常都执行
  2. <aop:after:returning> 后置通知:只有当切点正常执行时才执行后置通知
  3. <aop:after><aop:after:returning><aop:after:throwing>的执行顺序和配置顺序有关
  4. execution(* xxx(String,int)) and args(name,age)
  5. 其中args()括号里面的名称是自定义的顺序和切点方法参数的顺序一致
  6. <aop:before arg-names="name,age"> arg-names的名称来源于expression=""中args(),名称必须一样。args()有几个参数,arg-names里面必须由几个参数。并且必须和通知方法参数名一样

4. 使用AspectJ实现切面(注解配置方式)

新建通知类

@Component
@Aspect
public class BetterMyAdvice {@Pointcut("execution(* com.hust.edu.pointcut.Worker.doSomething(String)) && args(name)")public void doSomething(String name){}@AfterThrowing(value = "doSomething(String)", throwing = "e")public void myExeception(Exception e){System.out.println("---------执行异常通知--------");System.out.println(e.getMessage());}@Before(value = "doSomething(String) && args(name)")public void beforeCall(String name){System.out.println("name: "+name);System.out.println("---------执行前置--------");}@After(value = "doSomething(String) && args(name)")public void afterCall(String name){System.out.println("name: "+name);System.out.println("---------执行后置--------");}@AfterReturning(value = "doSomething(String) && args(name)")public void afterCallReturning(String name){System.out.println("name: "+name);System.out.println("---------后置通知Returning--------");}@Around(value = "doSomething(String) && args(name)")public Object aroundCall(ProceedingJoinPoint joinPoint,String name) throws Throwable {System.out.println("---------执行环绕前置--------");Object proceed = joinPoint.proceed();System.out.println("---------执行环绕后置--------");System.out.println("name: "+name);return proceed;}
}

定义切点(需要被切入的类)

@Component
public class Worker {@Pointcut(value = "execution(* com.hust.edu.pointcut.Worker.doSomething(String)) && args(name)")public void doSomething(String name) throws Exception{//抓取异常通知//int i=5/0;switch (name){case "watch":System.out.println("----------watch TV---------");break;default:break;}}public void doNoCutSomething(String name) throws Exception{switch (name){case "watch":System.out.println("----------watch TV---------");break;default:break;}}
}

配置Spring配置文件

    <context:component-scan base-package="com.hust"/><!--proxy-target-clas 如果为true使用cglib动态代理 false使用JDK动态代理--><aop:aspectj-autoproxy proxy-target-class="true"/>

Spring不会自动寻找注解,必须告诉哪些包下的类有注解

实现步骤

  1. 在Spring配置文件中,设置注解在哪些保存。配置ComponentScan组件扫描
  2. 在Demo类中添加,@Component
  3. 在切点方法上添加@PointCunt,定义切点
  4. 在通知类中配置
    1. @Component 表示类交给Spring IOC容器管理
    2. @Aspect 表示相当于<aop-aspect>表示切面

Spring 详解(三):AOP 面向切面的编程相关推荐

  1. Spring(三)AOP面向切面编程

    原文链接:http://www.orlion.ga/205/ 一.AOP简介 1.AOP概念 参考文章:http://www.orlion.ml/57 2.AOP的产生 对于如下方法: public ...

  2. Spring IoC和DI XML方式 注解方式 依赖注入 AOP面向切面

    初识Spring框架 时代变迁 原始时代我们用一个jsp搞定一切,但如此开发大型项目时我们遇到了问题,前端美化的代码和后端的代码交织,代码中又有html.js.css样式,又有业务逻辑和数据库访问代码 ...

  3. 【Spring】面向切面编程详解(AOP)

    文章目录 一.AOP概述 什么是AOP AOP应用场景 二.AOP的基本术语 术语介绍 术语举例详解 三.AOP实例说明 四.通知类型详解 概述 前置通知 后置通知 环绕通知 最终通知 六.AOP实现 ...

  4. 【SSM】Spring系列——AOP面向切面编程

    文章目录 03 AOP面向切面编程 3.1 AOP概述 3.2 面向切面编程对有什么好处 3.3 模拟AOP框架实现 3.3.1 代码实现版本一 3.3.2 代码实现版本二 3.3.3 代码实现版本三 ...

  5. 大数据WEB阶段Spring框架 AOP面向切面编程(一)

    Spring - AOP面向切面编程(一) 一.代理模式概述 代理的特点:(目标对象即被代理者) 实现和目标对象相同的接口 具备和目标对象的方法 代理者不仅要做目标对象的方法 , 还要做一些额外的操作 ...

  6. 大数据WEB阶段Spring框架 AOP面向切面编程(二)

    Spring AOP面向切面编程(二) 一.切入点的execution表达式 execution的表达形式: execution(修饰符? 返回值类型 所在包类? 方法名(参数列表) 异常?) ?表示 ...

  7. Java绝地求生—Spring AOP面向切面编程

    Java绝地求生-Spring AOP面向切面编程 背景 动态代理 构建被代理对象 自动生成代理 调用动态代理 Spring方法 方式一:使用Spring的API接口 方式二:使用自定义类 方式三:使 ...

  8. spring中AOP(面向切面编程)

    spring中AOP(面向切面编程) 面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术.AOP是OOP的延续,是软件开发中的一个热点,也是spring框架中的一个重要内容 ...

  9. Spring AOP面向切面编程

    AOP面向切面编程: AOP(Aspect Oriented Programming),即面向切面编程,它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公 ...

最新文章

  1. 5.JasperReports学习笔记5-其它数据生成动态的报表(WEB)
  2. 手机屏幕镜像翻转软件_可以把ipad投屏到电视吗?屏幕镜像一键投屏
  3. C#指定窗口显示位置的方法
  4. OpenCV里IplImage的widthStep参数 和width参数
  5. mysql g月份分组_PowerBI快捷键——视觉对象分组功能
  6. 08.28 JavaScript 边界 选择修饰符 模式单元 修饰符 正则的使用
  7. kafka删除topic及其相关数据
  8. linux:fdisk分区命令详解
  9. idea中git的运用
  10. Talk预告 | 港科大(广州)助理教授王泽宇: 提升数字内容创作中的创意实现与迭代过程
  11. 分享5款2022年最好用的windows软件
  12. spss软件下载安装
  13. php 100元 换算,货币换算的PHP脚本
  14. 鸿蒙系统的发展前景,国产系统大有可为!从鸿蒙看未来操作系统的大潮
  15. 【建模教程】你还不知道的游戏模型规范要求知识点,汇总赶紧收藏!
  16. 计算机ppt音乐,ppt背景音乐_适合ppt播放的轻音乐
  17. AndroidStudio运行模拟器--夜神
  18. 第五十七篇 Django-CRM系统-1登录,注册,修改密码
  19. excel表格拆分成多个工作表的方法?
  20. CANopen通讯基础

热门文章

  1. 计算机网络总结之计算机概述
  2. Ubuntu13.10:[3]如何开启SSH SERVER服务
  3. arm开发板6410/2440上mjpg-streamer网络视频服务器移植
  4. 如何用Windows Live Writer写网易博客
  5. 大熊君说说JS与设计模式之(门面模式Facade)迪米特法则的救赎篇------(监狱的故事)...
  6. weka中文乱码解决办法
  7. 取得前九条之后的数据
  8. static unsigned short,int ,char
  9. 智慧树python程序设计基础山东联盟期末答案_智慧树Python程序设计基础(山东联盟)期末答案...
  10. IDEA中Project 和 Module的区别