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

SpringAOP面向切面编程的概念和使用

目录

1、什么是Spring AOP?

2、AOP基于动态代理实现

3、AOP的通知类型

4、AOP的核心概念

5、SpringAOP+AspectJ实现步骤

6、SpringAOP和注解的实现


1、什么是Spring AOP?

        AOP(Aspect-Oriented Programming: 面向切面编程):将那些与业务无关, 却为业务模块所共同调用的逻辑(例如事务处理、日志管理、权限控制等)封装抽取成一个可重用的模块,这个模块被命名为“切面”(Aspect),便于减少系统的重复代码降低模块间的耦合度,并有利于未来的可拓展性和可维护性;

2、AOP基于动态代理实现

  • 如果被代理的对象,已经实现某个接口,则 Spring AOP 会使用 JDK Proxy(反射),基于接口的方式,创建代理对象(JDK动态代理的核心是InvocationHandler接口和Proxy类);
  • 如果被代理的对象,没有实现某个接口,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 Cglib,基于继承的方式,生成一个被代理对象的子类来作为代理(Cglib动态代理的核心是MethodInterceptor接口和Enhancer类);

3、AOP的通知类型

        AOP将抽取出来的共性功能称为通知,通知类型:以通知在上下文中的具体位置作为划分:

  • 前置通知(Before)
  • 返回通知(After-returning)
  • 异常通知(After-throwing)
  • 后置通知(After)
  • 环绕通知(Around)

4、AOP的核心概念

        AOP连接点(Join point):AOP将所有的方法都视为连接点,不管是接口里面的抽象方法,还是实现类里面的重写方法,都是连接点。连接点是 AOP 切面(Aspect)可以插入的地方。

        AOP切点(Pointcut):AOP将可能被抽取共性功能的方法称为切入点。切入点是连接点的子集。只有运行成功的方法才能是切点

        AOP目标对象(Target):就是挖掉功能的方法对应的类生的对象,这种对象是无法直接完成最终工作的。比如之前的借钱的业务,如果只有方法没有事务管理的话,这种就是挖掉功能的方法。(没有横切关注点如事务管理、日志记录、权限控制等没有直接实现)。

        AOP织入(Weaving):就是将挖掉的功能回填的动态过程,通过织入,AOP 将切面(Aspect)中的通知(Advice)插入到目标对象的指定切点(Pointcut)中。

        AOP切面(Aspect)切点+通知,它定义了横切关注点的实现(通知)以及应用的地方(切点)。切面将横切关注点与业务逻辑分离,定义了需要在哪里以及如何应用这些关注点。

5、SpringAOP+AspectJ实现步骤

        首先是service接口类,定义了增删改查的方法但是没有事务管理和日志。

public interface IAccountService {/*** 新增* */public void save(int i);/*** 修改* */public void update();/*** 删除* */public int delete();
}

        然后是service层实现类,重写了接口中的方法:

public class AccountServiceImp implements IAccountService {@Overridepublic void save(int i) {System.out.println("业务层的新增方法:" + i);}@Overridepublic void update() {System.out.println("业务层的修改方法");int a = 10/0;}@Overridepublic int delete() {System.out.println("业务层的删除方法");return 0;}
}

        紧接着我们在util包下,创建日志记录类

public class Logger {public void beforeMethod(){System.out.println("日志类logger中调用beforeMethod方法进行日志记录");}public void returnMethod(){System.out.println("日志类logger中调用returnMethod方法进行日志记录");}public void throwMethod(){System.out.println("日志类logger中调用throwMethod方法进行日志记录");}public void afterMethod(){System.out.println("日志类logger中调用afterMethod方法进行日志记录");}public Object arroundMethod(ProceedingJoinPoint pjp){Object obj = null;try{System.out.println("环绕通知===前置通知");//切点方法Object[] ars = pjp.getArgs();obj = pjp.proceed(ars);System.out.println("环绕通知===返回通知");}catch (Throwable e){e.printStackTrace();System.out.println("环绕通知===异常通知");}finally {System.out.println("环绕通知===后置通知");return obj;}}
}

        前四个方法是用来定义AOP通知的方法,包括前置通知、后置通知、异常通知、最终通知。

        最后一个方法为环绕通知,可以完全控制目标的方法执行,不需要写上面四个通知方法,一个就全部解决。

        其中的ProceedingJoinPoint 是 AspectJ 和 Spring AOP 中的一种特殊类型的 JoinPoint,提供了继续执行连接点的功能。与普通的 JoinPoint 相比,ProceedingJoinPoint 允许在环绕通知中控制目标方法的执行

        首先调用了前置通知,然后通过.getArgs()方法获得目标方法的参数,再通过.proceed(objs)方法将参数传入并调用方法。然后调用后置通知。最后调用后置通知,并返回方法的返回值。

        最后在XML配置文件中:

<!--注入日志记录层(通知)-->
<bean id="logger" class="com.apesource.util.Logger"></bean>

        首先注入日志记录层logger。

<!--配置AOP--><aop:config><!--配置切面 定义了横切关注点(如日志记录、事务管理、安全性等--><aop:aspect id="aopAspect" ref="logger"><!--切点 指定了在哪些连接点(Join Point)上执行通知。--><!--expression 是切点表达式--><!--execution(* com.apesource.service.*.*(..)):这个表达式定义了切点,匹配 com.apesource.service 包下的所有类的所有方法。* 代表返回类型和方法名,(..) 代表任意参数。--><aop:pointcut id="dian" expression="execution(* com.apesource.service.*.*(..))"/><!--通知 通知定义了在切点处执行的具体操作。-->
<!--            <aop:before method="beforeMethod" pointcut-ref="dian"></aop:before>--><!--执行成功后-->
<!--            <aop:after-returning method="returnMethod" pointcut-ref="dian"></aop:after-returning>-->
<!--            <aop:after-throwing method="throwMethod" pointcut-ref="dian"></aop:after-throwing>--><!--执行后-->
<!--            <aop:after method="afterMethod" pointcut-ref="dian"></aop:after>--><!--环绕通知--><aop:around method="arroundMethod" pointcut-ref="dian"></aop:around></aop:aspect></aop:config>

        然后定义一个AOP切面,并配置了一个环绕通知。

        <aop:config> 标签:表示这是 AOP 配置的开始。所有的 AOP 配置元素都应在这个标签内。

        <aop:aspect> 标签: 定义了一个切面(Aspect)。 id="aopAspect":为该切面指定一个唯一的标识符 aopAspect。 ref="logger":表示这个切面引用了一个名为 logger 的 bean,该 bean 中包含了实际的通知方法。

        <aop:pointcut> 标签:定义了一个切点(Pointcut)。expression="execution(* com.apesource.service.*.*(..))":切点表达式,定义了哪些连接点(Join Point)应该被拦截

        切点表达式配置语法:execution(修饰符 返回值 包名称.类名称.方法名称(参数列表))

        示例代码:

execution(public void com.apesource.service.ServiceImp.findAll())

        修饰符可以省略代表任意:

execution(返回值 包名称.类名称.方法名称(参数列表))

        返回值可以使用“*”代表任意:

execution(* 包名称.类名称.方法名称(参数列表))

        包名可以使用“*”代表任意名称:

execution(* *.*.*.类名称.方法名称(参数列表))

         包名可以使用“..”代表任意个数:

execution(* *...类名称.方法名称(参数列表))

        类名与方法名可以使用“*”代表任意:

execution(* *...*.*(参数列表))

        参数列表可以使用".."代表任意个数任意类型:

execution(* *...*.*(..))

        如果有参数(int======>int、String===>java.lang.String)

        后面的通知标签中,method代表的是在目标方法返回结果后要调用的通知方法,pointcut-ref代表的是这个通知方法应用在哪个切点上。

        <aop:after-returning>:定义一个后置通知(After Returning Advice),在目标方法成功执行并返回结果之后调用。

        <aop:after-throwing>:定义一个异常通知(After Throwing Advice),在目标方法执行过程中抛出异常时调用。

        <aop:after>:定义一个最终通知(After Advice),无论目标方法是否成功执行或抛出异常,都会在目标方法执行后调用。

        <aop:around>:定义一个环绕通知(Around Advice),包裹目标方法的执行。可以在目标方法之前和之后执行自定义逻辑,并决定是否执行目标方法。

        总体来说的步骤为:

1. 添加依赖,aop与aspectj表达式的依赖

2. 创建spring的主配置文件,bean内的命名空间要添加aop的

3. 创建业务代码并编写日志记录代码(事务管理代码)

4. 将业务层与日志记录层注入spring容器

5. <aop:config>--aop配置 aop:aspect--aop切面 aop:before--通知内容与通知类型

6、SpringAOP和注解的实现

        和上面的配置类比较的话就是一个是通过扫描注解来完成AOP织入,而一个是通过配置类来实现AOP织入。

        logger工具类实现:

@Component
@Aspect //切面
public class Logger {@Pointcut("execution(* com.apesource.service.AccountServiceImp.update())")public void dian(){}//前置通知@Before("dian()")public void BeforeLogger(){System.out.println("前置通知==>日志类logger中调用BeforeLogger方法进行日志记录");}//返回通知@AfterReturning("dian()")public void AfterReturningLogger(){System.out.println("返回通知==>日志类logger中调用AfterReturningLogger方法进行日志记录");}//异常通知@AfterThrowing("dian()")public void AfterThrowingLogger(){System.out.println("异常通知==>日志类logger中调用AfterThrowingLogger方法进行日志记录");}//后置通知@After("dian()")public void AfterLogger(){System.out.println("后置通知==>日志类logger中调用AfterLogger方法进行日志记录");}//环绕通知//@Around("dian()")public Object AroundLogger(ProceedingJoinPoint pjp){Object returnobj = null;//保存主业务方法的返回值try{//1.前置通知System.out.println("环绕通知===》前置通知");Object[] objs = pjp.getArgs();//主业务方法的参数returnobj =  pjp.proceed(objs);//调用主业务方法//3.后置通知System.out.println("环绕通知===》后置通知");}catch (Throwable tw){//4.异常通知System.out.println("环绕通知===》异常通知");}finally{//5.最终通知System.out.println("环绕通知===》最终通知");}return returnobj;}
}

        @Aspect 注解注解表明 Logger 类是一个切面类。

        @Pointcut 注解定义了一个切入点,表示在 com.apesource.service.AccountServiceImp 类的 update 方法执行时,切面会触发。

        前置通知@Before("dian()") 这个注解表明 BeforeLogger 方法会在 dian() 定义的切入点之前执行。

        返回通知 (@AfterReturning) 这个注解表明 AfterReturningLogger 方法会在 dian() 定义的切入点成功返回之后执行。

        异常通知 (@AfterThrowing) 这个注解表明 AfterThrowingLogger 方法会在 dian() 定义的切入点抛出异常时执行。

        后置通知 (@After) 这个注解表明 AfterLogger 方法会在 dian() 定义的切入点方法执行之后(无论是否成功)执行。

        环绕通知 (@Around) 方法会在 dian() 定义的切入点的前后执行,并且可以控制目标方法的执行。

        然后在XML中扫描并开启aop代理即可:

<!--扫描注解--><context:component-scan base-package="com.apesource"></context:component-scan><!--开启spring注解的aop动态代理--><aop:aspectj-autoproxy></aop:aspectj-autoproxy>

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 萱仔求职系列——1.1 机器学习基础知识复习
  • Redisson 实现分布式锁
  • vue2项目微信小程序的tabs切换效果
  • 学单片机怎么在3-5个月内找到工作?
  • 2024杭电多校(7) 1007. 创作乐曲【线段树预处理、dp、思维】
  • STM32的SDIO接口详解
  • 梅特勒金属探测器检测仪维修SAFELINE V3-QF1
  • 2024年电脑录屏软件推荐:捕捉屏幕,记录生活,分享精彩
  • Flink 实时数仓(八)【DWS 层搭建(二)流量域、用户域、交易域搭建】
  • 【JavaEE初阶】CAS(比较和交换)
  • OrangePi AIpro学习4 —— 昇腾AI模型应用
  • 代码随想录算法训练营Day33 | 509. 斐波那契数 | 70. 爬楼梯 | 746. 使用最小花费爬楼梯
  • C++——模板进阶
  • 卷积神经网络 - 结构化输出篇
  • 【Linux】编译器gcc/g++ 、程序翻译过程、动静态库
  • 《网管员必读——网络组建》(第2版)电子课件下载
  • Akka系列(七):Actor持久化之Akka persistence
  • CentOS6 编译安装 redis-3.2.3
  • CSS选择器——伪元素选择器之处理父元素高度及外边距溢出
  • Docker容器管理
  • input的行数自动增减
  • Java到底能干嘛?
  • TCP拥塞控制
  • Vue 动态创建 component
  • 搭建gitbook 和 访问权限认证
  • 复杂数据处理
  • 区块链将重新定义世界
  • 用mpvue开发微信小程序
  • 再谈express与koa的对比
  • ​Kaggle X光肺炎检测比赛第二名方案解析 | CVPR 2020 Workshop
  • #QT(串口助手-界面)
  • #QT(智能家居界面-界面切换)
  • #数学建模# 线性规划问题的Matlab求解
  • (arch)linux 转换文件编码格式
  • (Mac上)使用Python进行matplotlib 画图时,中文显示不出来
  • (Redis使用系列) SpringBoot中Redis的RedisConfig 二
  • (二)windows配置JDK环境
  • (附源码)ssm教材管理系统 毕业设计 011229
  • (附源码)ssm考生评分系统 毕业设计 071114
  • (力扣记录)235. 二叉搜索树的最近公共祖先
  • (贪心) LeetCode 45. 跳跃游戏 II
  • (图)IntelliTrace Tools 跟踪云端程序
  • (转)Google的Objective-C编码规范
  • (转)为C# Windows服务添加安装程序
  • .net core docker部署教程和细节问题
  • .NET delegate 委托 、 Event 事件,接口回调
  • .Net Web窗口页属性
  • .NET 中创建支持集合初始化器的类型
  • .NET/C# 解压 Zip 文件时出现异常:System.IO.InvalidDataException: 找不到中央目录结尾记录。
  • .net下的富文本编辑器FCKeditor的配置方法
  • /3GB和/USERVA开关
  • @Autowired @Resource @Qualifier的区别
  • @Transactional 详解
  • [51nod1610]路径计数
  • [Android]Tool-Systrace