当前位置: 首页 > news >正文

Spring AOP 基础知识

1.背景

按照软件重构的思想,当多个类中存在相同的代码时,需要提取公共部分来消除代码坏味道。Java的继承机制允许用户在纵向上通过提取公共方法或者公共部分(模版方法方式)至父类中以消除代码重复问题;日志、访问控制、性能监测等重复的非业务代码揉杂在业务代码之中无法横向抽取,AOP技术为其提供了一个解决方案。

AOP技术将这些重复的非业务代码抽取出为一个模块,通过技术整合还原代码的逻辑和功能;即:在代码层面上消除了重复度,提高了可维护性,并在功能层面上得到还原。抽取重复代码作为一个模块是用户的问题,然而技术整合(对目标织入增强逻辑,后文介绍)以实现功能还原是AOP的目标和工作重心,Spring AOP是该技术的一种实现。

 

本文作为Spring系列的第八篇,介绍Spring框架中AOP的使用、注意事项和实现原理,原理部分会结合Spring框架源码进行。

 

Spring系列的后续文章如Spring系列-9 Async注解使用与原理和Spring系列-10 事务机制其底层原理都是Spring AOP。

 

2.  AOP 简介

常见的AOP实现方案有Spring AOP和AspectJ:相对于Spring AOP而言,AspectJ是一种更成熟、专业的AOP实现方案。AOP的技术整合(织入增强逻辑)可以发生在编译器、类加载期以及运行期:AspectJ在编译器(ajc)和类加载器(使用特定的类加载器)实现;Spring AOP在运行时通过动态代理方式实现。AspectJ提供了完整了AOP方案,而Spring AOP从实用性出发未常见的应用场景提供了技术方案,如不支持静态方法、构造方法等的AOP。

 

Spring AOP构建于IOC之上,与IOC一起作为Spring框架的基石。Spring AOP底层使用动态代理技术实现,包括:JDK动态代理与CGLIB动态代理;JDK动态代理技术要求被代理对象基于接口,而CGLIB动态代理基于类的继承实现代理,从而要求被代理类不能为final类且被代理的方法不能被final、staic、private等修饰。二者都有局限性,在一定程度上相互弥补。

 

3.AOP基本概念

[1] 执行点:在Spring AOP中指代目标类中具体的方法;

[2] 连接点:包含位置信息的执行点,位置信息包括:方法执行前、后、前后、异常抛出等;

[3] 切点:根据指定条件(类是否符合、方法是否符合等)过滤出的执行点的集合;

[4] 通知/增强:为目标对象增加的新功能,如在业务代码中引入日志、访问控制等功能;

[5] 切面:切面由切点和通知组成;

[6] 织入:将切面织入目标对象,形成代理对象的过程。

 

4.增强类型

Spring中使用Advise标记接口表示增强,Spring根据方位信息(方法执行前后、环绕、异常抛出等)为其定义了不同的子类接口。

 

public interface Advice {}

 

 

4.1 前置增强

BeforeAdvice接口表示前置增强,由于Spring当前仅支持方法增强,所以可用的接口为MethodBeforeAdvice.

 

//同Advise接口,BeforeAdvice也是个空接口
public interface MethodBeforeAdvice extends BeforeAdvice {void before(Method method, Object[] args, @Nullable Object target) throws Throwable;
}

 

如上所示,MethodBeforeAdvice接口中仅有一个before方法,入参分别是方法对象、参数数组、目标对象;该方法会在目标对象的方法调用前调用。

4.2 后置增强

 

public interface AfterReturningAdvice extends AfterAdvice {void afterReturning(@Nullable Object returnValue, Method method, Object[] args, @Nullable Object target) throws Throwable;
}

 

该方法中仅有一个afterReturning方法,入参比before多处一个返回值;该方法会在目标对象的方法调用后调用。

 

4.3 环绕增强

 

@FunctionalInterface
// Interceptor 是Advise的字接口,且是空接口
public interface MethodInterceptor extends Interceptor {@NullableObject invoke(@Nonnull MethodInvocation invocation) throws Throwable;
}

 

可通过invocation.proceed()语句调用目标对象方法并获得放回值,可在前后自定义逻辑,相对于前置和后置有更高的灵活性。

 

4.4 异常抛出增强

 

public interface ThrowsAdvice extends AfterAdvice {
}

 

ThrowsAdvice是一个空接口,起标签作用。在运行期间Spring通过反射调用afterThrowing接口,该接口可以被定义为:void afterThrowing(Method method, Object[] args, Object target, Throwable exception);

其中method、args和target是可选的,exception参数是必选的;在目标方法抛出异常后,实施增强。

 

除此之外,框架还定义了一种引介增强,用于在目标类中添加一些新的方法和属性。

 

4.5 案例介绍

4.5.1:前置、后置、环绕增强

定义前置通知:

@Slf4j
public class MyBeforeAdvice implements MethodBeforeAdvice {@Overridepublic void before(Method method, Object[] args, Object target) throws Throwable {LOGGER.info("----before----");}
}

 

定义后置通知:

@Slf4j
public class MyAfterReturningAdvice implements AfterReturningAdvice {@Overridepublic void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {LOGGER.info("----after----");}
}

 

 

定义环绕通知:

@Slf4j
public class MyRoundAdvice implements MethodInterceptor {@Overridepublic Object invoke(MethodInvocation invocation) throws Throwable {LOGGER.info("====round before====");Object result = invocation.proceed();LOGGER.info("====round after====");return result;}
}

测试用例如下:

public class AdviceAopTest {@Testpublic void testAdvice() {ProxyFactory proxyFactory = new ProxyFactory();TaskService taskService = new TaskServiceImpl();proxyFactory.setTarget(taskService);proxyFactory.setInterfaces(TaskService.class);// 添加前置增强proxyFactory.addAdvice(new MyBeforeAdvice());// 添加后置增强proxyFactory.addAdvice(new MyAfterReturningAdvice());// 添加环绕增强proxyFactory.addAdvice(new MyRoundAdvice());// 获取代理对象TaskService proxy = (TaskService)proxyFactory.getProxy();proxy.sync();}
}

运行结果如下所示:

com.seong.demo.aop.advice.MyBeforeAdvice -----before--
com.seong.demo.aop.advice.MyRoundAdvice - ====round before====
com.seong.demo.aop.model.TaskServiceImpl -[sync data]
com.seong.demo.aop.advice.MyRoundAdvice -====round after====
com.seong.demo.aop.advice.MyAfterReturningAdvice -----after----

4.5.2:异常抛出增强

修改目标类代码逻辑:

@Slf4j
public class TaskServiceImpl implements TaskService{@Override@SneakyThrowspublic void sync() {LOGGER.info("[sync data]");throw new Exception("");}
}

测试用例如下:

public class ThrowsAdviceTest {@Testpublic void testAdvice() {ProxyFactory proxyFactory = new ProxyFactory();TaskService taskService = new TaskServiceImpl();proxyFactory.setTarget(taskService);proxyFactory.setInterfaces(TaskService.class);proxyFactory.addAdvice(new MyThrowsAdvice());TaskService proxy = (TaskService)proxyFactory.getProxy();proxy.sync();}
}

 

结果如下:

INF0 com.seong.demo.aop.model.TaskServiceImpl - [sync data]
ERROR com.seong.demo.aop.advice.MyThrowsAdvice-----Exception occurs----

5. 切点类型

框架定义切点是为了从目标类的连接点(执行点)中过滤出符合条件的部分,为此在切点类的内部提供类两个过滤器:ClassFilter和MethodMatcher,分别对类型和方法进行过滤。

public interface Pointcut {ClassFilter getClassFilter();MethodMatcher getMethodMatcher();// Pointcut.TRUE 对象表示所有目标类的所有方法均满足条件// (实例对应的ClassFilter和MethodMatcher对象的match方法均返回true)Pointcut TRUE = TruePointcut.INSTANCE;
}

Pointcut切点接口定义如上所示,Spring并基于此扩展出了多种切点类型;使得可以根据方法名、参数、是否包含注解以及表达式等进行过滤。

 

6. 切面类型

Spring使用Advisor表示切面类型,可以分为3类:一般切面Advisor、切点切面PointcutAdvisor、引介切面IntroductionAdvisor;一般切面Advisor仅包含一个Advice, 即表示作用对象是所有目标类的所有方法;PointcutAdvisor包含Advice和Pointcut信息,可以通过切点定位出满足Pointcut过滤条件的执行点集合;IntroductionAdvisor对应于引介切点和增强。

其中:PointcutAdvisor及其子类DefaultPointcutAdvisor是较为常见的切面类型,源码如下:

public class DefaultPointcutAdvisor extends AbstractGenericPointcutAdvisor implements Serializable {private Pointcut pointcut = Pointcut.TRUE;private Advice advice = EMPTY_ADVICE;public DefaultPointcutAdvisor() {}public DefaultPointcutAdvisor(Advice advice) {this(Pointcut.TRUE, advice);}public DefaultPointcutAdvisor(Pointcut pointcut, Advice advice) {this.pointcut = pointcut;setAdvice(advice);}
}

 

DefaultPointcutAdvisor包含一个切点和一个增强类型属性:Pointcut的默认值为Pointcut.TRUE表示所有目标类的所有方法均为连接点;Advice的默认值为EMPTY_ADVICE:Advice EMPTY_ADVICE = new Advice() {};, 即表示不进行增强。

测试用例中为ProxyFactory添加切面部分逻辑为:proxyFactory.addAdvice(new MyBeforeAdvice());

等价于 proxyFactory.addAdvisor(new DefaultPointcutAdvisor(new MyBeforeAdvice()));.

 

 

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • HashMap第7讲——get方法源码分析
  • python,利用可变对象实现设置参数的自动回存
  • Knife4j的原理及应用详解(七)
  • 探索大模型:袋鼠云在 Text To SQL 上的实践与优化
  • 技校专业群的生成机制研究
  • linux系统“/“目录比“/home“目录小
  • 多图详解入门级AI绘画 Midjourney工具注册使用流程,AI绘画必备工具
  • MySQL篇:日志
  • ubuntu计划任务反弹
  • php获取,昨,今,后天.... 本周,月,年...日期时间戳
  • 1.浅谈蓝牙BLE的总体框架
  • 【Mark笔记】基于Centos7.7更改SSH端口重启服务报错
  • SAP S4 销售组的定义和分配
  • WGS84坐标转换代码(JS版)
  • KVM虚拟机添加USB转串口设备
  • [分享]iOS开发-关于在xcode中引用文件夹右边出现问号的解决办法
  • canvas绘制圆角头像
  • centos安装java运行环境jdk+tomcat
  • jquery ajax学习笔记
  • SQL 难点解决:记录的引用
  • 彻底搞懂浏览器Event-loop
  • 回顾2016
  • 每天一个设计模式之命令模式
  • 前端每日实战:61# 视频演示如何用纯 CSS 创作一只咖啡壶
  • 前端知识点整理(待续)
  • 思维导图—你不知道的JavaScript中卷
  • 限制Java线程池运行线程以及等待线程数量的策略
  • 蚂蚁金服CTO程立:真正的技术革命才刚刚开始
  • ​ 轻量应用服务器:亚马逊云科技打造全球领先的云计算解决方案
  • # 消息中间件 RocketMQ 高级功能和源码分析(七)
  • #php的pecl工具#
  • #我与Java虚拟机的故事#连载11: JVM学习之路
  • $emit传递多个参数_PPC和MIPS指令集下二进制代码中函数参数个数的识别方法
  • (13)[Xamarin.Android] 不同分辨率下的图片使用概论
  • (2024)docker-compose实战 (9)部署多项目环境(LAMP+react+vue+redis+mysql+nginx)
  • (2024,RWKV-5/6,RNN,矩阵值注意力状态,数据依赖线性插值,LoRA,多语言分词器)Eagle 和 Finch
  • (2024.6.23)最新版MAVEN的安装和配置教程(超详细)
  • (el-Transfer)操作(不使用 ts):Element-plus 中 Select 组件动态设置 options 值需求的解决过程
  • (Java)【深基9.例1】选举学生会
  • (二)WCF的Binding模型
  • (附源码)springboot 校园学生兼职系统 毕业设计 742122
  • (论文阅读23/100)Hierarchical Convolutional Features for Visual Tracking
  • (免费领源码)python#django#mysql公交线路查询系统85021- 计算机毕业设计项目选题推荐
  • (原创)boost.property_tree解析xml的帮助类以及中文解析问题的解决
  • (转)h264中avc和flv数据的解析
  • (转)大型网站的系统架构
  • .net 按比例显示图片的缩略图
  • .NET/C# 使窗口永不获得焦点
  • .Net8 Blazor 尝鲜
  • .Net调用Java编写的WebServices返回值为Null的解决方法(SoapUI工具测试有返回值)
  • @Builder注释导致@RequestBody的前端json反序列化失败,HTTP400
  • [Android]RecyclerView添加HeaderView出现宽度问题
  • [Android实例] 保持屏幕长亮的两种方法 [转]
  • [AutoSar]BSW_Com02 PDU详解
  • [AX]AX2012 SSRS报表Drill through action