七、Spring 面向切面编程(AOP)学习总结

news2024/12/28 19:03:08

文章目录

  • 一、初识面向切面编程(AOP)
    • 1.1 什么是 AOP
    • 1.2 AOP的应用场景
    • 1.3 Aop 在 Spring 中的作用
      • 1.3.1 Aop 的核心概念
    • 1.4 使用 Spring 实现 AOP
      • 1.4.1 方式一:使用 Spring API 接口实现 AOP 【主要是SpringAPI接口实现】
      • 1.4.2 方式二:自定义类来实现 AOP【主要是切面定义】
      • 1.4.3 方式三:使用注解实现 AOP




一、初识面向切面编程(AOP)


  •        在以往的企业开发过程中,一些已经写完的功能可以会在原本的基础上进行扩展,这个时候就需要去修改原有的代码,将新扩展的内容完善进去。但是这个动作其实是企业级开发的大忌,因为原本好用的代码很可能因为新增的内容导致出现问题。


           而解决这个问题会用到代理模式,将要新扩展的功能维护到代理角色中,再用代理角色去调用真实角色来实现功能的扩展,这样做不但将新扩展的功能实现类,原本的功能代码也无需变动,更加稳妥。而这也是 Spring AOP 的实现机制。

在这里插入图片描述



1.1 什么是 AOP

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

  • 如下图所示:AOP 就是通过一些方式,在原有业务逻辑不变的情况下将一些扩展功能完成
    在这里插入图片描述




1.2 AOP的应用场景


  • 日志记录: 记录调用方法的入参和结果返参。

  • 用户的权限验证: 验证用户的权限放到AOP中,与主业务进行解耦。

  • 性能监控: 监控程序运行方法的耗时,找出项目的瓶颈。

  • 事务管理: 控制Spring事务,Mysql事务等。

  • AOP可以拦截指定的方法,并且对方法增强,比如:事务、日志、权限、性能监测等增强,而且无需侵入到业务代码中,使业务与非业务处理逻辑分离。




1.3 Aop 在 Spring 中的作用


       AOP 采取横向抽取机制(动态代理),取代了传统纵向继承机制的重复性代码,其应用主要体现在事务处理、日志管理、权限控制、异常处理等方面。

       主要作用是分离功能性需求和非功能性需求,使开发人员可以集中处理某一个关注点或者横切逻辑,减少对业务代码的侵入,增强代码的可读性和可维护性。

       简单的说,AOP 的作用就是保证开发者在不修改源代码的前提下,为系统中的业务组件添加某种通用功能。

1.3.1 Aop 的核心概念


  • 提供声明式事务;允许用户自定义切面

    • 横切关注点: 跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志,安全,缓存,事务等等…

    • 切面(ASPECT): 横切关注点被模块化的特殊对象。即,它是一个类。

    • 通知(Advice): 切面必须要完成的工作。拦截到连接点之后,对切入点增强的内容。即,它是类中的一个方法。

    • 目标(Target): 被代理的目标对象。

    • 代理(Proxy): 指生成的代理对象

    • 切入点(PointCut): 指需要对那些 JointPoint 进行拦截,即被拦截的连接点

    • 连接点(JointPoint): 指那些被拦截到的点,在 Spring 中,指可以被动态代理拦截目标类的方法

    • 植入点(Weaving): 指把增强代码应用到目标上,生成代理对象的过程

通俗理解

横切关注点:在业务代码种需要新增的业务,即为关注点
切面:将关注点维护成一个类,即新增的业务就是切面
通知:切面中具体的方法,也就是具体需要完成的业务就是通知。
目标:即需要在那段业务新增业务代码,即目标对象。
代理:代理目标对象的对象
切入点:需要通知在那个位置执行新的业务代码

注:目标和代理被 Spring 完成了,InvocationHandler、Proxy



  • Spring AOP中,通过 “通知(Advice)” 定义横切逻辑,Spring中支持5种类型的Advice:即AOP在不改变原有代码的情况下,去增加新的功能。
通知类型连接点
Before(前置通知)通知方法在目标方法调用之前执行,前置通知不会影响目标方法的执行,除非此处抛出异常
After(后置通知)通知方法在目标方法返回或者异常后调用(必然会执行)
After-Returning(最终通知)通知方法在目标方法返回后调用,如果连接点抛出异常,则不会执行
After-Throwing(抛出异常后通知)通知方法在目标方法抛出异常后调用
Around(环绕通知)环绕目标方法的通知,例如方法调用。这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为。它可以选择是否继续执行目标方法或直接返回自定义的返回值又或抛出异常将执行结束。

注:目标方法就是连接点



1.4 使用 Spring 实现 AOP


1.4.1 方式一:使用 Spring API 接口实现 AOP 【主要是SpringAPI接口实现】


  • 创建 Maven 项目并在 pom.xml 中引入如下依赖(使用 AOP 织入需要此依赖)

    <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
            <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjweaver</artifactId>
                <version>1.9.4</version>
            </dependency>
    
    • 在service包下,定义UserService业务接口和UserServiceImpl实现类

      • UserService 接口

        public interface UserService {
        
            void add();
            void query();
            void delete();
            void update();
        }
        
      • UserServiceImpl 实现类

        public class UserServiceImpl implements UserService {
            @Override
            public void add() {
                System.out.println("新增一个用户");
            }
        
            @Override
            public void query() {
                System.out.println("查询用户信息");
            }
        
            @Override
            public void delete() {
                System.out.println("删除一个用户");
            }
        
            @Override
            public void update() {
                System.out.println("修改用户信息");
            }
        }
        
    • 在 log 包下定义 一个前置增强和一个后置增强类

      • 前置通知增强类

        import org.springframework.aop.MethodBeforeAdvice;
        import java.lang.reflect.Method;
        
        //MethodBeforeAdvice : 前置通知
        public class BeforeLog implements MethodBeforeAdvice {
        
            /*
             * method:要执行的目标对象的方法
             * args:参数
             * target:目标对象
             * */
            @Override
            public void before(Method method, Object[] args, Object target) throws Throwable {
                System.out.println(target.getClass().getName() + "的" + method.getName() + "被执行了");
            }
        }
        
      • 后置通知增强类

        import org.springframework.aop.AfterReturningAdvice;
        import java.lang.reflect.Method;
        
        // AfterReturningAdvice : 后置通知
        public class AfterLog implements AfterReturningAdvice {
            
            /*
             * returnValue: 返回值
             * method:要执行的目标对象的方法
             * args:参数
             * target:目标对象
             * */
            @Override
            public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
                System.out.println("执行了"+method.getName()+"方法,返回结果为:"+returnValue);
            }
        }
        
    • 最后去 spring 的文件中注册 , 并实现aop切入实现 , 注意导入约束,配置 applicationContext.xml 文件

      <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:aop="http://www.springframework.org/schema/aop"
             xsi:schemaLocation="http://www.springframework.org/schema/beans
              https://www.springframework.org/schema/beans/spring-beans.xsd
              http://www.springframework.org/schema/aop
              https://www.springframework.org/schema/aop/spring-aop.xsd">
      
          <!--注册bean-->
          <bean id="afterLog" class="com.sys.log.AfterLog"/>
          <bean id="beforeLog" class="com.sys.log.BeforeLog"/>
          <bean id="userService" class="com.sys.service.impl.UserServiceImpl"/>
      
          <!--配置 Spring aop:需要导入aop约束-->
          <!--实现 aop 的方式一:使用原生 Spring API接口-->
          <aop:config>
              <!--配置切入点:
                          execution 表达式:execution(要执行的位置!)
                                          execution(* *) :第一个 * 代表返回值的类型任意,第二个 * 代表类,如果为 * 就代表所有类
                                          execution(* com.sys.XXX.*(..)):.*(..):表示任何方法名,括号表示参数,两个点表示任何参数类型-->
              <aop:pointcut id="pointcut" expression="execution(* com.sys.service.impl.UserServiceImpl.*(..))"/>
      
              <!--配置 Advice(通知)-->
              <!--前置加后置约等于环绕通知-->
              <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
              <aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"/>
      
          </aop:config>
          
      </beans>
      
      • Spring AOP切入点@Pointcut – execution表达式
    • 创建 MyTest 测试类

      public class MyText {
          public static void main(String[] args) {
              ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
             
              // 需要注意的是:AOP的代理是动态代理,也就是此处代理的并不是实现类,而是实现类实现的接口
              UserService userService = (UserService) context.getBean("userService");
              userService.add();
              userService.update();
              userService.query();
              userService.delete();
          }
      }
      
    • 执行结果 :因为接口定义的方法都是 Void 所以没有返回值
      在这里插入图片描述



1.4.2 方式二:自定义类来实现 AOP【主要是切面定义】


  • 不使用 Spring 提供的 API 接口,使用自定义的 diy 类,搭配 xml 的 aop 配置实现

    • 创建 diy 工具类

      public class DiyPointCut {
      
          public void before(){
              System.out.println("======方法执行前======");
          }
      
          public void after(){
              System.out.println("======方法执行后======");
          }
      }
      
    • 创建 Beans.xml

      <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:aop="http://www.springframework.org/schema/aop"
             xsi:schemaLocation="http://www.springframework.org/schema/beans
      		        https://www.springframework.org/schema/beans/spring-beans.xsd
      		        http://www.springframework.org/schema/aop
      		        https://www.springframework.org/schema/aop/spring-aop.xsd">
      
          <!-- 将 Diy 工具类注册到 SPring 容器种 -->
          <bean id="diyPointCut" class="com.sys.diy.DiyPointCut"/>
          <bean id="userService" class="com.sys.service.impl.UserServiceImpl"/>
          <!-- 配置 aop -->
          <aop:config>
              <!--自定义切面,ref 要引用的类-->
              <aop:aspect ref="diyPointCut">
                  <!--配置切入点-->
                  <aop:pointcut id="pointcut" expression="execution(* com.sys.service.impl.UserServiceImpl.*(..))"/>
                  <!--配置通知
                              before:前置通知
                              after:后置通知-->
                  <aop:before method="before" pointcut-ref="pointcut"/>
                  <aop:after method="after" pointcut-ref="pointcut"/>
              </aop:aspect>
          </aop:config>
      
      </beans>
      
    • 修改 MyTest,其他代码内容不变

      public class MyTest {
          public static void main(String[] args) {
              ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
      
              // 需要注意的是:AOP的代理是动态代理,也就是此处代理的并不是实现类,而是实现类实现的接口
              UserService userService = (UserService) context.getBean("userService");
              userService.add();
              System.out.println("-------------------------------------------------");
              userService.update();
              System.out.println("-------------------------------------------------");
              userService.query();
              System.out.println("-------------------------------------------------");
              userService.delete();
          }
      }
      
    • 执行结果



1.4.3 方式三:使用注解实现 AOP


  • @Aspect : 被该注解标注的类就是一个切面

  • @Before: 标注切面类中的方法为前置通知

    • 参数:需要传入切入点,即 execution 表达式
  • @After: 标注切面类中的方法为后置通知

    • 参数:需要传入切入点,即 execution 表达式
  • @Around: 标注切面类中的方法为环绕通知

    • 参数:需要传入切入点,即 execution 表达式

    • 被标记为环绕通知的方法还需要搭配 proceed() 来完成通知,如果没有该方法,那么切入点对应的方法不执行,也可以理解为这个方法就是执行切入点中的方法用的

      • proceed() : 通过这个方法判断切入点中的方法在环绕通知中的那个位置执行


  • 代码示例:(前置通知和后置通知)

    • 新增 Diy 切面类

      @Aspect // 将该类标记为一个切面
      public class AnnotationPointCut {
      
          // 将该方法标记为前置通知
          @Before("execution(* com.sys.service.impl.UserServiceImpl.*(..))")
          public void before(){
              System.out.println("=====方法执行前=====");
          }
      
      
          // 将该方法标记为后置通知
          @After("execution(* com.sys.service.impl.UserServiceImpl.*(..))")
          public void after(){
              System.out.println("=====方法执行后=====");
          }
      }
      
    • 修改 Beans.xml 开启注解支持

      <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:aop="http://www.springframework.org/schema/aop"
             xsi:schemaLocation="http://www.springframework.org/schema/beans
      		        https://www.springframework.org/schema/beans/spring-beans.xsd
      		        http://www.springframework.org/schema/aop
      		        https://www.springframework.org/schema/aop/spring-aop.xsd">
      
          <bean id="userService" class="com.sys.service.impl.UserServiceImpl"/>
      
          <!--方式三:使用注解-->
          <bean id="annotationPointCut" class="com.sys.diy.AnnotationPointCut"/>
          <!--开启aop注解支持! JDK(默认是 proxy-target-class="false")  cglib(proxy-target-class="true")-->
          <aop:aspectj-autoproxy/>
      
      </beans>
      
    • 其他代码不变,运行测试类,执行结果


  • 环绕通知代码示例

    • 修改 Diy 切面类

      @Aspect // 将该类标记为一个切面
      public class AnnotationPointCut {
      
          @Around("execution(* com.sys.service.impl.UserServiceImpl.*(..))")
          public void around(ProceedingJoinPoint jp) throws Throwable{
              System.out.println("环绕前");
      
              Signature signature = jp.getSignature();// 获得签名
              System.out.println("signature:"+signature);
      
              jp.proceed(); //执行方法
      
              System.out.println("环绕后");
          }
      
      }
      
    • 其他代码不变,运行测试类,执行结果

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/835825.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++教程从入门到实战(c++基础入门,看这一篇就够了)

1. 安装 g编译器 是编译C代码使用的编译器&#xff0c;不要使用gcc了。 在用户目录中单独创建一个文件夹存放下载后的3个安装文件&#xff0c;然后进入到目录中执行下面命令 cd 文件夹 sudo dpkg -i *.deb 本地安装 sudo apt-get install g 在线安装 2. C的语言特点及优势 c wi…

一道名题-(csp 儒略日)的心得与技巧

这道题&#xff0c;我做了三年&#xff0c;平均每年做一次&#xff0c;我来讲讲我的心得。 读题 题面很长&#xff0c;细节很多&#xff0c;我们需要耐心细心的读&#xff0c;此时多花一点时间是划得来的。 我们得出大致关系如下 历法公历日常用历儒略历公元前公元后历法公历…

springBoot项目导入外部jar包

一、将外部的jar包复制到指定文件夹 二、修改pom文件 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocati…

利用abapGit的离线模式导出、导入开发对象

1. 背景 abapGit是为ABAP服务器开发的开源Git客户端&#xff0c;用于在ABAP系统之间导入和导出ABAP对象。 使用abapGit&#xff0c;可以将ABAP对象从任何系统导出到另一个系统&#xff0c;通常是从本地系统导出到云&#xff0c;或者从一个云系统导出到另一个云系统。 当然从…

红帽8.2版本CSA题库:第一题配置网络设置

红帽认证工程师是业界公认的最权威的Linux认证之一。RHCE 是世界上第一个面向Linux 的认证考试&#xff0c;它不是一个普通的认证测试&#xff0c;和其他操作系统认证考试相比&#xff0c;它没有笔试&#xff0c;全部是现场实际操作&#xff0c;所以RHCE成了业界公认的最难的认…

机器学习笔记 - YOLO-NAS 最高效的目标检测算法之一

一、YOLO-NAS概述 YOLO(You Only Look Once)是一种对象检测算法,它使用深度神经网络模型,特别是卷积神经网络,来实时检测和分类对象。该算法首次在 2016 年由 Joseph Redmon、Santosh Divvala、Ross Girshick 和 Ali Farhadi 发表的论文《You Only Look Once: Unified, Re…

自监督去噪:Noise2Self原理分析及实现 (Pytorch)

文章地址:https://arxiv.org/abs/1901.11365 代码地址: https://github.com/czbiohub-sf/noise2self 要点   Noise2Self方法不需要信号先验信息、噪声估计信息和干净的训练数据。唯一的假设就是噪声在测量的不同维度上表现出的统计独立性&#xff0c;而真实信号表现出一定的…

亚马逊云科技七项生成式AI新产品生成式AI,为用户解决数据滞后等难题

7月27日&#xff0c;亚马逊云科技在纽约峰会上一连发布了七项生成式AI创新&#xff0c;涵盖了从底层硬件到工具、软件、再到生态的全方位更新&#xff0c;成为它在该领域迄今最全面的一次升级展示&#xff0c;同时也进一步降低了生成式AI的使用门槛。 亚马逊云科技凭借自身端到…

vue实现全屏、退出全屏方法

有效解决问题 : Failed to execute ‘exitFullscreen‘ on ‘Document‘: Document not active 在未全屏的情况下触发exitFullscreen&#xff0c;会报这个错。 <el-button class"screen" click"toggleFullscreen">全屏</el-button> fullscre…

Unity 编辑器选择器工具类Selection 常用函数和用法

Unity 编辑器选择器工具类Selection 常用函数和用法 点击封面跳转下载页面 简介 在Unity中&#xff0c;Selection类是一个非常有用的工具类&#xff0c;它提供了许多函数和属性&#xff0c;用于操作和管理编辑器中的选择对象。本文将介绍Selection类的常用函数和用法&#xff…

Redis面试题2

Redis面试题-2 10、统计高并发网站每个网页每天的 UV 数据&#xff0c;结合Redis你会如何实现&#xff1f; 选用方案&#xff1a;HyperLogLog 如果统计 PV 那非常好办&#xff0c;给每个网页一个独立的 Redis 计数器就可以了&#xff0c;这个计数器的 key 后缀加上当天的日期…

loguru 日志库

1 下载地址 loguru PyPI 2 安装 pip install loguru 3 简单使用 from loguru import logger# 当前目录下创建 log 文件夹&#xff0c;保留 3 天 logger.add("./Log/INFO_{time:YYYY-MM-DD}.log", encoding"utf-8", retention"3 day")logger.…

JUC并发编程(一)JUC工具使用

文章目录 Synchronizedsynchronized解决 Lock锁synchronized锁与Lock锁的区别 生成者消费者问题synchronized实现lock版实现- conditioncondition实现精准通知唤醒 CallableJUC常用辅助类CountDownLatch&#xff08;倒计时器&#xff09;CyclicBarrier&#xff08;循环栅栏&…

Find My资讯|AirTag 2或将在明年底量产,苹果Find My应用全面

AirTag 2021 年和 2022 年出货量预计在 2000 万个和 3500 万个。如果 AirTag 出货量继续增长&#xff0c;苹果大概率会开发第二代产品。郭明錤现更新了其预测报告&#xff0c;他表示&#xff0c;苹果 AirTag 2 可能会在明年第四季度量产。 AirTag 是用于固定在背包、行李和其…

Typora未保存报错闪退

Typora未保存报错闪退 在使用Typora编写笔记时&#xff0c;可能会遇到没有保存就退出的情况&#xff0c;也可能软件突然崩溃导致之前的笔记没有保存&#xff0c;实际上Typora会自动保存记录在本地文件中&#xff0c;接下来我们进行恢复。 点击左上角文件标签&#xff0c;进入…

《Gradle构建脚本的基本结构和语法》

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

【Python机器学习】实验08 决策树

文章目录 决策树1 创建数据2 定义香农信息熵3 条件熵4 信息增益5 计算所有特征的信息增益&#xff0c;选择最优最大信息增益的特征返回6 利用ID3算法生成决策树7 利用数据构造一颗决策树Scikit-learn实例决策树分类决策树回归Scikit-learn 的决策树参数决策树调参 实验1 通过sk…

学习笔记|简单分享一下自建Gravatar镜像

目录 前言 Gravatar 使用 思路 操作 步骤一&#xff1a;注册或登录华为云 步骤二&#xff1a;创建委托账号 步骤三&#xff1a;创建OBS桶 步骤四&#xff1a;数据回源配置 步骤五&#xff1a;配置生命周期规则 步骤六&#xff1a;绑定自定义域名 步骤七&#xff1a…

学习Android嵌入式需要做哪些准备?

首先嵌入式系统可以分为四个层次&#xff1a;硬件层、驱动层、系统层和应用层&#xff0c;其中每一个层次都会有一类专业的学生适合深入学习。硬件层就适合电子、通信、机电等学生&#xff0c;驱动层因为比较难&#xff0c;按我的理解都是需要从硬件层和应用层过渡。而应用层就…

LNMP原理及安装

LNMP: 目前成熟的企业网站的应用模式之一&#xff0c;指的是一套协同工作的系统和相关软件能够提供静态页面服务&#xff0c;也可以提供动态web服务 LNMP的组成: L &#xff1a;表示linux系统&#xff0c;操作系统N &#xff1a;表示nginx网站服务&#xff0c;前端&#xf…