深度理解 Spring AOP

news2024/12/23 4:28:26

一、什么是AOP(面向切面编程)?🍉

AOP 为 Aspect Oriented Programming 的缩写,意思为面向切面编程,是通过预编译方式 和运行期 动态代理 实现程序功能的统一维护的一种技术。

AOP (面向切面编程)是 OOP(面向对象) 的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程 的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

二、AOP 的作用及其优势🍉

作用:在程序运行期间,在不修改源码的情况下对方法进行功能增强

优势:减少重复代码,提高开发效率,并且便于维护

三、AOP 的底层实现🍉

实际上,AOP 的底层是通过 Spring 提供的的动态代理技术实现 的。在运行期间,Spring通过动态代理技术动态的生成代理对象,代理对象方法执行时进行增强功能的介入,在去调用目标对象的方法,从而完成功能的增强。

动态代理的作用

  • 1)在目标类源代码不改变的情况下,增加功能。
  • 2)减少代码的重复
  • 3)专注业务逻辑代码
  • 4)解耦合,让你的业务功能和日志,事务非业务功能分离。

四、AOP 相关概念🍉

Spring 的 AOP 实现底层就是对动态代理的代码进行了封装 ,封装后我们只需要对需要关注的部分进行代码编写,并通过配置的方式完成指定目标的方法增强。

在正式讲解 AOP 的操作之前,我们必须理解 AOP 的相关术语,常用的术语如下:

  1. 切面(Aspect)
    一个切面就是一个代理对象= 要为那些类生成代理+要添加的额外功能是那些

在这里插入图片描述

  1. 切入点(pointcut):将来要为那些类生成代理对象
  2. 通知/ 增强(advice):就是要添加的额外功能

生活案例:给面包之间涂果酱
在这里插入图片描述

注意:切面(Aspect)=切入点(pointcut)+通知点(advice,额外功能)

在这里插入图片描述

  1. AOP中的(面向切面编程)通知
    前置通知:目标方法之前的额外功能 MethodBeforeAdvice

环绕通知:它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式 MethodInterceptor(Interceptor [ɪntəˈsɛptə]拦截器)

后置通知:目标方法之后的额外功能 AfterReturningAdvice(returning[rɪˈtɜːrnɪŋ])

异常通知:执行异常的额外功能 ThrowsAdvice

最终通知:一定执行的额外功能

在这里插入图片描述

  1. Target(目标对象):代理的目标对象
  2. 代理(Proxy[ˈprɑːksi]):一个类被AOP注入增强后,就产生一个结果代理类
  3. 连接点(joinPoint):所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法(额外功能),因为spring只支持方法类型的连接点

五、切面编程步骤🍉

1、五种通知类🥝

AOP 切面=切入点+通知

前置通知:MethodBeforeAdvice
后置通知:AfterReturnAdvice
环绕通知:MethodInterceptor
异常通知:ThrowsAdvice(throw [θroʊz])
最终通知

通知的配置语法:

<aop : 通知类型 method=“切面类中方法名” pointcut=“切点表达式"> </aop:通知类型>
  • 切点表达式的抽取

当多个增强的切点表达式相同时,可以将切点表达式进行抽取,在增强中使用 pointcut-ref 属性代替 pointcut 属性来引用抽取后的切点表达式。

<aop:config>
    <!--引用myAspect的Bean为切面对象-->
    <aop:aspect ref="myAspect">
        <aop:pointcut id="myPointcut" expression="execution(* com.tjcu.aop.*.*(..))"/>
        <aop:before method="before" pointcut-ref="myPointcut"></aop:before>
    </aop:aspect>
</aop:config>

aop植入的配置

<aop:config>
    <aop:aspect ref=“切面类”>
        <aop:before method=“通知方法名称” pointcut=“切点表达式"></aop:before>
    </aop:aspect>
</aop:config>

切点表达式的写法:

execution([修饰符] 返回值类型 包名.类名.方法名(参数))

2、AOP的开发步骤🥝

1、开发目标类

2、开发通知类,确定额外功能

3、管理通知类

4、配置切入点 确定要为那些类添加额外功能

5、将目标类和切面类的对象创建权交给 spring

6、组装切面 切入点+通知(额外功能)

3、AOP编程所需要的依赖🥝

这里是引用引入依赖
spring-aop
spring-expression
spring-aspects

六、AOP实现前置通知案例🍉

1、目标接口类🥝

public interface CityService {
    public void login();
    public  void add(String name);
}

2、目标实现类(核心功能)🥝

/**
 * @Description:目标对象
 */
public class CityServiceImpl implements CityService{
    @Override
    public void login() {
        //前置通知:System.out.println("嘻嘻哈哈");

        //执行核心的业务逻辑 调用Dao
        System.out.println("登录调用Dao");
    }

    @Override
    public void add(String name) {
        //前置通知:System.out.println("嘻嘻哈哈");
        //执行核心的业务逻辑 调用Dao
        System.out.println("添加调用Dao");
    }
}

3、前置通知(额外功能)动态代理代码🥝

/**
 * @Description: 通知类。额外功能
 */
public class MyBeforeAdvice implements MethodBeforeAdvice {
    /**
     * 额外功能书写的方法
     * 参数1:代理对象当前调用的方法
     * 参数2:当前代理对象调用的方法的参数
     * 参数3:目标对象(被代理的对象)
     * @param method
     * @param objects
     * @param o
     * @throws Throwable
     */
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
     System.out.println("嘻嘻哈哈");
    }
}

4、将目标类和切面类的对象创建权交给 spring🥝

<!--管理目标对象-->
    <bean class="before.CityServiceImpl" id="cityService"></bean>
    <!--管理通知类 动态代理实现AOP-->
    <bean id="myBeforeAdvice" class="before.MyBeforeAdvice"></bean>

5、在 spring.xml 中配置织入关系(前置功能)🥝

<!--组装切面-->
    <aop:config>
    <!--配置切入点
    id:切入点的唯一标识
    expression:切入点表达式,为那些类添加额外功能
     execution() 切入点表达式的一种 精确到要添加到额外功能的方法
     *:通配
     空格:
     before.CityServiceImpl.*:所有方法
     (..):所有参数
    -->
        <aop:pointcut id="pc1" expression="execution(* before.CityServiceImpl.*(..))"/>
    <!--组装切面=切入点+通知
      advice-ref:通知的id
      pointcut-ref:切入点的id
    -->
        <aop:advisor advice-ref="myBeforeAdvice" pointcut-ref="pc1"></aop:advisor>
    </aop:config>

6、测试代码🥝

  @Test
    public void testMethodBeforeAdvice() {
        //启动工厂
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("before/spring.xml");
        //获取组件 目标对象就是代理对象
        CityService cityService = (CityService) ctx.getBean("cityService");
        
        //目标对象就是代理对象  class com.sun.proxy.$Proxy4
        System.out.println(cityService.getClass());    
        
        //调用方法,通过代理类调用目标类
        cityService.add("123");
    }

注意:获取组件时,目标对象就是代理对象

在这里插入图片描述

七、Spring中的环绕通知案例🍉

1、dao层接口🥝

public interface StudentDao {
    /**
     * 登录
     * @param name
     */
    public void login(String name);

    /**
     * 分页
     * @param name
     * @return
     */
    public String pageShow(String name);
}

2、dao层实现类🥝

public class StudentDaoImpl implements StudentDao{
    @Override
    public void login(String name) {
        //循环10000次
        for (int i = 0; i < 10000; i++) {

        }
        System.out.println("数据库实现登录");

    }

    @Override
    public String pageShow(String name) {
        //循环10000次
        for (int i = 0; i < 10000; i++) {

        }
        System.out.println("数据库实现分页");
        return name;

    }
}

3、目标接口类🥝

public interface StudentService {
    /**
     * 登录
     * @param name
     */
    public void login(String name);

    /**
     * 分页
     * @param name
     * @return
     */
    public String pageShow(String name);
}

4、目标实现类🥝

public class StudentServiceImpl implements StudentService{
    private StudentDao studentDao;

    public void setStudentDao(StudentDao studentDao) {
        this.studentDao = studentDao;
    }

    @Override
    public void login(String name) {
        System.out.println("登录日志");
      studentDao.login(name);
    }

    @Override
    public String pageShow(String name) {
        System.out.println("分页日志");
        String s = studentDao.pageShow(name);
        return s;
    }
}

5、环绕通知🥝

核心方法: Object proceed = methodInvocation.proceed(); 放行
public class StudentAroundAdvice implements MethodInterceptor {
    /**
     * 参数 内部封装者当前的代理对象 方法的参数,执行方法等
     *
     * @param methodInvocation
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        //1.控制事务
        System.out.println("控制事务");
        //method Invocation 方法调用
        System.out.println("当前调用方法的名字" + methodInvocation.getMethod().getName());
        //arguments参数 methodInvocation:方法调用
        System.out.println("当前的参数为:" + methodInvocation.getArguments()[0]);

        System.out.println("--------------");

        //记录当前的时间 单位毫秒
        long begin = System.currentTimeMillis();
        System.out.println("调用查询的数据库");

        //放行,执行目标方法 proceed:继续做proceed
        Object proceed = methodInvocation.proceed();


        //记录结束的时间 单位毫秒
        long end = System.currentTimeMillis();
        System.out.println("dao执行所用时间" + (end - begin));

        return proceed;
    }
}

6、将目标类和切面类的对象创建权交给 spring🥝

  <!--管理dao组件-->
    <bean id="studentDao" class="com.tjcu.dao.StudentDaoImpl"></bean>

    <!--管理Service组件/目标对象-->
    <bean id="studentService" class="com.tjcu.service.StudentServiceImpl">
        <!--注入值-->
        <property name="studentDao" ref="studentDao"></property>
    </bean>

    <!--管理通知组件-->
    <bean id="studentAroundAdvice" class="com.tjcu.advice.StudentAroundAdvice"></bean>

7、在 applicationContext.xml 中配置织入关系,aop相关配置🥝

 <!--aop相关配置  切面=切点+环绕通知-->
    <aop:config>
        <!--切入点 execution:[eksɪˈkjuːʃn]执行-->
        <aop:pointcut id="pointcut" expression="execution(* com.tjcu.service.StudentServiceImpl.*(..))"/>
        <!--组装切面  advisor[ [ædˈvaɪzər]]:顾问  advice-ref:通知  pointcut-ref:切入点-->
        <aop:advisor advice-ref="studentAroundAdvice" pointcut-ref="pointcut"></aop:advisor>
    </aop:config>

8、测试代码🥝

  @Test
    public void AroundAdviceTest() {
        //启动工厂
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("com/tjcu/Spring/ApplicationContext.xml");

        //获取组件 目标对象就是代理对象
        StudentService studentService = (StudentService) context.getBean("studentService");

        //调用方法,通过代理类调用目标类
        studentService.pageShow("通过代理类调用调用目标类");
    }

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

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

相关文章

Jmeter 接口自动化和 Python 接口自动化,到底选哪个?

目录 前言&#xff1a; 背景 Jmeter 接口自动化 特点 Python 接口自动化 特点 谈项目 写在最后 前言&#xff1a; JMeter接口自动化和Python接口自动化都是常见的选择。 背景 很多刚接触接口自动化的朋友都会疑惑&#xff0c;市面上 Jmeter 接口自动化&#xff0c;Py…

题目2 文件上传(保姆级教程)

url&#xff1a;http://192.168.154.253:82/ #打开http://XXX:81/&#xff0c;XXX为靶机的ip地址 审题 1、打开题目看到有一个提示&#xff0c;此题目需要绕过WAF过滤规则&#xff0c;上传木马获取webshell&#xff0c;最后从根目录下key.php文件中获得flag 2、开始答题 第一步…

【数据结构】二叉树详解(3)

⭐️ 前言 ✨ 往期链接&#xff1a;【数据结构】二叉树详解(1) 在第一篇二叉树文章中&#xff0c;我们探讨了二叉树的链式结构定义与实现。二叉的遍历包含( 前序/中序/后序遍历 )及代码实现和递归流程图的详细讲解。还有一些二叉树的其他接口定义与实现&#xff0c;包含 Binar…

基于netlify生成custom SSL certificate

&#xff08;1&#xff09;腾讯云申请 &#xff08;2&#xff09;域名控制台解析 &#xff08;3&#xff09;Nginx下载&#xff08;crt: CA certificate Chain)

C++ 设计模式 ---- 接口隔离模式

“接口隔离”模式 在组件构建过程中&#xff0c;某些接口之间直接的依赖常常会带来很多问题、甚至根本无法实现。采用添加一层间接&#xff08;稳定&#xff09;接口&#xff0c;来隔离本来互相紧密关联的接口是一种常见的解决方案。典型模式&#xff1a;1、Facade2、Proxy3、…

MongoDB原生语句更新嵌套数组的值

一、更新一层嵌套数组 首先执行MongoDB原生语句脚本在user集合中产生一些样本数据,如下所示: db.user.insert({"_id":1,"title":"爱情公寓3","students":[{"student_id":1001,"student_name":"林宛瑜&quo…

Docker介绍及安装使用

Docker介绍及安装使用 一、Docker的概述1、Docker是什么&#xff1f;2、Docker的Logo3、Docker的设计宗旨&#xff08;一次封装&#xff0c;到处运行&#xff09;4、容器化越来越受欢迎的原因 二、Docker与虚拟机的区别三、Docker的使用场景四、Docker的核心概念1、镜像2、容器…

基于linux下的高并发服务器开发(第二章)- 2.25 sigprocmask 函数使用

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);- 功能&#xff1a;将自定义信号集中的数据设置到内核中&#xff08;设置阻塞&#xff0c;解除阻塞&#xff0c;替换&#xff09;- 参数&#xff1a;- how : 如何对内核阻塞信号集进行处理SIG_BLOCK: 将用户设…

【MySQl】MySQl中的乐观锁是怎么实现的

文章目录 前言一、乐观锁二、如何实现乐观锁呢&#xff0c;一般来说有以下2种方式2.1、使用数据版本&#xff08;Version&#xff09;记录机制实现2.2、乐观锁定的第二种实现方式和第一种差不多 前言 mysql中的乐观锁是怎么实现的&#xff1f;很多新手对此不是很清楚&#xff…

第一次参加【CSDN周赛(考试/编程竞赛)】第65期,应该注意些什么?都考什么题目?要具备什么知识?耗时__,我居然取得了__分的成绩

订阅专栏,学习更多干货知识!! 第一次参加 CSDN里的竞赛(考试),都需要注意些什么?考试都考了什么?要具备什么知识?本文带你了解一下!! 🤾🏿‍♂️目录 🌁一、先来看结果吧(有Bug?)🥕1.1 什么情况!🥕1.2 测评报告🥤1.2.1 选择题🥤1.2.2 编程题🥕1…

Redis持久化:分别启用rdb和aof,并查看是否有对应文件生成

一、rdb 简介&#xff1a;在指定的时间间隔内将内存中的数据集快照写入磁盘&#xff0c; 也就是Snapshot快照&#xff0c;它恢复时是将快照文件直接读到内存里。 1. 进入redis.conf文件中查看配置文件 [rootserver ~]# vim /usr/local/redis-stable/redis.conf 2.把持久化的…

《Docker数据管理:卷、挂载和持久化,保障容器环境数据安全》

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

【博客682】k8s apiserver bookmarks机制以更高效检测变更

k8s apiserver bookmarks机制以更高效检测变更 list-watch背景&#xff1a; List-Watch 是kubernetes中server和client通信的最核心的机制&#xff0c; 比如说api-server监听etcd&#xff0c; kubelet监听api-server&#xff0c; scheduler监听api-server等等&#xff0c;其实…

Paragon NTFS2023最新版Mac读写NTFS磁盘工具

Paragon NTFS for Mac是Mac平台上一款非常优秀的读写工具&#xff0c;可以在Mac OS X中完全读写、修改、访问NTFS硬盘、U盘等外接设备的文件。这款软件最大的亮点简书可以让我们读写 NTFS 分区&#xff0c;因为在Mac OS X 系统上&#xff0c;默认状态下我们只能读取NTFS 分区&a…

152. 乘积最大子数组

152. 乘积最大子数组 原题链接&#xff1a;完成情况&#xff1a;解题思路&#xff1a;参考代码&#xff1a; 原题链接&#xff1a; 152. 乘积最大子数组 https://leetcode.cn/problems/maximum-product-subarray/ 完成情况&#xff1a; 解题思路&#xff1a; 看好题目&…

Nginx 301重定向分析

参考; 404 - 墨天轮 深度硬核文:Nginx的301重定向处理过程分析 - 知乎 Nginx的301状态码处理逻辑设计 HTTP协议中3xx开头的状态响应码都是表示重定向的响应。根据RFC的定义&#xff1a; 301 Moved Permanently 302 Found 303 See Other 307 Temporary Redirect 301是永…

STL——String类(2)成员函数详解

目录 前言 一.String的成员函数&#xff1a; 1.基本成员函数 代码实验&#xff1a; 实验结果&#xff1a; 类对象每次扩容后的capacity数据展示&#xff1a; 1.2. resize()&#xff1a;调整字符串大小 1.3reserve()&#xff1a;请求更改该对象的容量capacity值 代码实验…

分组密码模式的填充

分组加密 在密码学中&#xff0c;分组加密(Block cipher)&#xff0c;又称分块加密或块密码&#xff0c;是一种对称密钥算法。 它将明文分成多个等长的模块(block)&#xff0c;使用确定的算法和对称密钥对每组分别加密解密。 常见的分组加密算法有: DES、3DES、AES、IDEA。 …

Ubuntu虚拟机部署配置

目录 虚拟机镜像下载 VirtualBox7下载 VirtualBox7安装镜像流程 创建虚拟机 虚拟机挂单独硬盘 网络设置 检查虚拟机配置 启动虚拟机 ubuntu配置 查询虚拟机IP地址 修改ROOT密码 更新apt NTP同步 挂载磁盘&关闭swap 虚拟机镜像下载 目前国内操作系统镜像源非常…

2023-07-19力扣今日二题

链接&#xff1a; 2737. 找到最近的标记节点 题意&#xff1a; 给一个n节点有向图&#xff0c;求节点s和点集marked中的最短距离&#xff0c;没有可以的到达的点则返回-1 解&#xff1a; 摇了一题困难过了2/3&#xff0c;搞不定了 没有负权边的单源最短距离&#xff0c;迪…