spring之AOP(面向切面编程)之详结

AOP面向切面编程,一种编程范式,指导开发者如何组织程序结构

作用:

在不惊动原始设计的基础上为其进行功能增强

核心概念

  • 代理:SpringAOP的核心本质是采用代理模式实现的
  • 连接点:程序执行过程中的任意位置,粒度为执行方法、抛出异常、设置变量等
    • 在springAOP中,理解为方法的执行
  • 切入点:匹配连接点的式子
    • 在springAOP中,一个切入点可以只描述一个具体方法,也可以匹配多个方法
  • 通知:在切入点出执行的操作,也就是共性功能,在AOP中,功能最终以方法的形式呈现
  • 通知类:定义通知的类
  • 切面:描述通知与切入点的对应关系

如果不理解的话可以看这一段话帮助理解:连接点(所有英雄)、切入点(增强的英雄)、通知类(buff库)、通知(各种buff)、切面(可以理解为设计师,从buff库里面拿出具体的buff加强给英雄)

现给一个入门案例:

注解配置类:

@Configuration
@ComponentScan("com.example")
@EnableAspectJAutoProxy  //切面相关注解
public class SpringConfig {
}

切面:

@Component  //交给spring来管理
@Aspect //声明这个类是切面
public class MyAdvice {//定义一个私有的切入点@Pointcut("execution(void com.example.dao.BookDao.update())")private void pt(){}//通知在方法前执行@Before("pt()")public void method(){System.out.println(System.currentTimeMillis());}
}

连接点及实现类:

public interface BookDao {public void save();public void update();
}
@Repository
public class BookDaoImpl implements BookDao {@Overridepublic void save() {System.out.println("book dao save...");}@Overridepublic void update() {System.out.println("book dao update...");}
}

测试:

public class Aop {public static void main(String[] args) {ApplicationContext ctx= new AnnotationConfigApplicationContext(SpringConfig.class);BookDao bookDao = ctx.getBean(BookDao.class);bookDao.update();}
}
//输出
1712407510166
book dao update...

工作流程

  1. Spring容器启动
  2. 读取所有切面配置中的切入点
  3. 初始化bean,判定bean对应的类中的方法是否匹配到任意切入点
    1. 匹配失败,创建对象
      public static void main(String[] args) {ApplicationContext ctx= new AnnotationConfigApplicationContext(SpringConfig.class);BookDao bookDao = ctx.getBean(BookDao.class);//bookDao.update();System.out.println(bookDao);System.out.println(bookDao.getClass());}//输出
      com.example.dao.impl.BookDaoImpl@b3ca52e
      class com.example.dao.impl.BookDaoImpl

    2. 匹配成功,创建原始对象(目标对象)的代理对象。目标对象:原始功能去掉共性功能对应的类产生的对象,这种对象是无法直接完成最终工作的。代理:目标对象无法直接完成工作,需要对其进行功能回调,通过原始对象的代理对象实现
      public static void main(String[] args) {ApplicationContext ctx= new AnnotationConfigApplicationContext(SpringConfig.class);BookDao bookDao = ctx.getBean(BookDao.class);//bookDao.update();System.out.println(bookDao);System.out.println(bookDao.getClass());}//输出
      com.example.dao.impl.BookDaoImpl@7530ad9c
      class com.sun.proxy.$Proxy19
  4. 获取bean的执行方法
    1. 获取bean,调用方法并执行,完成操作
    2. 获取的bean是代理对象时,根据代理对象的运行模式运行原始方法与增强的内容,完成操作

切入点表达式

  • 切入点:要进行增强的方法
  • 切入点表达式:要进行增强的方法的描述方式

1. 描述方式

  1. 方式1:执行com.example.dao包下的BookDao接口中的无参数update方法:execution(void com.example.dao.BookDao.update())
  2. 方式2:执行com.example.dao.impl包下的BookDaoImpl类中的无参数update方法:execution(void com.example.dao.impl.BookDaoImpl.update())

2. 切入点表达式的标准格式

格式:动作关键字(访问修饰符  返回值  包名.类/接口名.方法名(参数)异常名)

execution(public void com.example.dao.impl.BookDaoImpl.update(int))
  • 动作关键字:描述切入点的动作行为,例如execution表示执行到指定切入点
  • 访问修饰符:public、private等,可以省略
  • 返回值
  • 包名
  • 类/接口名
  • 方法名
  • 参数
  • 异常名:方法定义中抛出指定异常,可以省略

通配符描述切入点:

  • * :单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配出现
    //匹配com.example包下任意包中的BookDao类或接口中所有update开头的带有一个参数的方法
    execution(public * com.example.*.BookDao.update*(*))
  • .. :多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写
    //匹配com包下任意包中的BookDao类或接口中所有名称为update的方法
    execution(public Book com..BookDao.update(..))
  • + :专用于匹配子类型
    execution(* *..*Dao+.*(..))

通知类型

  • AOP通知描述了抽取的共性功能,根据共性功能抽取的位置不同,最终运行代码时要将其加入到合理的位置
  • AOP通知共分为5种类型:
    • 前置通知:@Before
      @Component  //交给spring来管理
      @Aspect //声明这个类是切面
      public class MyAdvice {//定义一个私有的切入点@Pointcut("execution(void com.example.dao.BookDao.update())")private void pt(){}//通知在方法前执行@Before("pt()")public void method(){System.out.println(System.currentTimeMillis());}
      }
      //输出
      1712407510166
      book dao update...
    • 后置通知:@After。如果方法异常也会执行
      @Component  //交给spring来管理
      @Aspect //声明这个类是切面
      public class MyAdvice {//定义一个私有的切入点@Pointcut("execution(void com.example.dao.BookDao.update())")private void pt(){}@After("pt()")public void method(){System.out.println(System.currentTimeMillis());}
      }//输出
      book dao update...
      1712407576367
      
    • 环绕通知:@Around
      @Component  //交给spring来管理
      @Aspect //声明这个类是切面
      public class MyAdvice {//定义一个私有的切入点@Pointcut("execution(void com.example.dao.BookDao.update())")private void pt(){}@Around("pt()")public void method(ProceedingJoinPoint pjp) throws Throwable {System.out.println("around before advice...");//表示对原始操作的调用pjp.proceed();//如果方法有返回值的话用以下//Object proceed = pjp.proceed();System.out.println("around after advice...");}
      }//输出
      around before advice...
      book dao update...
      around after advice...
      
      • 注意事项:
        • 环绕通知必须依赖形参ProceedingJoinPoint才能实现对原始方法的调用,进而实现原始方法调用前后同时添加通知
        • 通知中如果未使用ProceedingJoinPoint对原始方法进行调用将跳过原始方法的执行
        • 对原始方法的调用可以不接收返回值,通知方法设置成void即可,如果接收返回值,必须设定为Object类型
        • 原始方法的返回值如果是void类型,通知方法的返回值类型可以设置成void,也可以设置成Object类型
        • 由于无法预知原始方法运行后是否会抛出异常,因此环绕通知方法必须抛出Throwable对象
    • 返回后通知:@AfterRunning。方法执行完后执行,如果方法异常不会执行
    • 抛出异常后通知:@AfterThrowing。方法异常会执行,无异常则不执行

通知获取数据

  • 获取切入点方法的参数
    • JoinPoint:适用于前置、后置、返回后、抛出异常后通知,必须是第一个参数。现以前置为例:
      //接口实现类的方法
      @Override
      public String findName(int id) {System.out.println("id:"+id);return "test";
      }//前置通知
      //定义一个私有的切入点@Pointcut("execution(* com.example.dao.BookDao.findName(..))")private void pt(){}@Before("pt()")public void before(JoinPoint joinPoint){//获取方法的参数Object[] args = joinPoint.getArgs();System.out.println(Arrays.toString(args));System.out.println("before advice...");}//测试public static void main(String[] args) {ApplicationContext ctx= new AnnotationConfigApplicationContext(SpringConfig.class);BookDao bookDao = ctx.getBean(BookDao.class);String name = bookDao.findName(100);System.out.println(name);}//输出
      [100]
      before advice...
      id:100
      test
      
    • ​​​​​​​ProceedingJoinPoint:适用于环绕通知
       @Around("pt()")public Object around(ProceedingJoinPoint pjp) throws Throwable {//获取方法的参数Object[] args = pjp.getArgs();System.out.println(Arrays.toString(args));//可以对原始操作的参数进行修改args[0]=666;Object proceed = pjp.proceed(args);//args可加可不加,默认是方法的参数,修改后传递的是修改的数据return proceed;
      }
      //测试public static void main(String[] args) {ApplicationContext ctx= new AnnotationConfigApplicationContext(SpringConfig.class);BookDao bookDao = ctx.getBean(BookDao.class);String name = bookDao.findName(100);System.out.println(name);}//输出
      [100]
      id:666
      test
  • 获取切入点方法返回值
    • ​​​​​​​返回后通知
      @AfterReturning(value = "pt()",returning = "ret")public void afterReturning(Object ret){System.out.println("方法的返回值是:"+ret);System.out.println("afterReturning advice...");}
    • 环绕通知。
       @Around("pt()")public Object around(ProceedingJoinPoint pjp) throws Throwable {//获取方法的参数Object[] args = pjp.getArgs();System.out.println(Arrays.toString(args));//可以对原始操作的参数进行修改args[0]=666;Object proceed = pjp.proceed(args);//args可加可不加,默认是方法的参数,修改后传递的是修改的数据return proceed;//proceed就是方法的返回值
      }
      
  • 获取切入点方法运行异常信息
    • ​​​​​​​抛出异常后通知
      @AfterThrowing(value = "pt()",throwing = "t")public void afterThrowing(Throwable t){System.out.println("afterThrowing advice...");}
    • 环绕通知。
      @Around("pt()")public Object around(ProceedingJoinPoint pjp) throws Throwable {Object ret=null;try {ret= pjp.proceed();} catch (Throwable e) {e.printStackTrace();}return ret;}

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

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

相关文章

Python项目21:一个简单的记账系统(收入+支出+查询)

------------★Python练手项目源码★------------ Python项目源码20:银行管理系统(开户、查询、取款、存款、转账、锁定、解锁、退出) Python项目19:学员信息管理系统(简易版) Python项目18:…

【Linux-运维】查看操作系统的指定端口占用情况确定端口是哪个服务占用

不同的查看端口占用的方法,应用场景有所不同 一、查询某个端口是否被占用?lsof -i:端口号lsof -i:协议 查看某个协议的占用情况netstat -tlnp|grep 端口号ss -tlnp|grep 端口号fuser 端口号/协议ls -l /proc/$(lsof -t -i:端口号)|grep exe 二、确认指定…

统计天数C++

1.统计天数 问题描述 给定两个年份 n*,*m,问,两个年份之间有多少天(包括年份 n 和年份 m)? 输入格式 第一行包含两个整数 n*,m,含义与问题描述中相同。 输出格式 输出共一行,包含一个整数…

Android14系统go版添加微件功能

一般normal版软件自带微件功能,但是go版没有这个功能,但是客户有时会要求也要加上这个微件功能,实现的方法修改如下: 1. frameworks/base diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index…

docker compose部署项目—踩坑记录

问题1:创建容器设置的mysql 的密码未生效 创建容器用 MYSQL_ROOT_PASSWORD配置了密码,但是密码没生效,还是原来的密码。 mysql:image: mysql:8.0container_name: mysql_testports:- "3306:3306"environment:TZ: Asia/ShanghaiMYSQ…

C++ 【原型模式】

简单介绍 原型模式是一种创建型设计模式 | 它使你能够复制已有对象,客户端不需要知道要复制的对象是哪个类的实例,只需通过原型工厂获取该对象的副本。 以后需要更改具体的类或添加新的原型类,客户端代码无需改变,只需修改原型工…

JS第九天

今天是第九天,学习了JS中的设置日期和倒计时,计时器以及验证码倒计时,那么话不多说我们开始今天的学习吧 一、日期设置 1.1日期创建 调用 new Date() 来创建一个新的 Date 对象。在调用时可以带有一些参数,创建一个 Date 对象&…

[ 云计算 | AWS ] ChatGPT 竞争对手 Claude 3 上线亚马逊云,实测表现超预期

文章目录 一、前言二、Claude 3 介绍以及相关测试细节三、在亚马逊云科技上体验 Claude 33.1 在 Amazon Bedrock 服务中配置 Claude 33.2 为聊天配置使用 Claude 3 模型3.3 Caude 3 Sonet 聊天体验 四、文末总结五、参考文献 一、前言 3月4号,Anthropic 发布了号称…

第十四届蓝桥杯C/C++大学B组题解(一)

1、日期统计 #include <bits/stdc.h> using namespace std; int main() {int array[100] {5, 6, 8, 6, 9, 1, 6, 1, 2, 4, 9, 1, 9, 8, 2, 3, 6, 4, 7, 7,5, 9, 5, 0, 3, 8, 7, 5, 8, 1, 5, 8, 6, 1, 8, 3, 0, 3, 7, 9,2, 7, 0, 5, 8, 8, 5, 7, 0, 9, 9, 1, 9, 4, 4, 6,…

鸿蒙Lottie动画-实现控制动画的播放、暂停、倍速播放、播放顺序

介绍 本示例展示了lottie对动画的操作功能。引入Lottie模块&#xff0c;实现控制动画的播放、暂停、倍速播放、播放顺序、播放到指定帧停止或从指定帧开始播放、侦听事件等功能&#xff0c;动画资源路径必须是json格式。 效果预览 使用说明&#xff1a; 进入页面默认开始201…

Python人工智能应用---中文分词词频统计

目录 1.中文分词 2.循环分别处理列表 &#xff08;1&#xff09;分析 &#xff08;2&#xff09;代码解决 3.词袋模型的构建 &#xff08;1&#xff09;分析需求 &#xff08;2&#xff09;处理分析 1.先实现字符串的连接 2.字符串放到新的列表里面 4.提取高频词语 &…

一些好玩的东西

这里写目录标题 递归1.递归打印数组和链表?代码实现原理讲解二叉树的 前 中 后 序位置 参考文章 递归 1.递归打印数组和链表? 平常我们打印数组和链表都是 迭代 就好了今天学到一个新思路–>不仅可以轻松正着打印数组和链表 , 还能轻松倒着打印(用的是二叉树的前中后序遍…

Linux基础篇:Linux第三方软件仓库——可以让Linux变得有趣的软件仓库

Linux第三方软件仓库——可以让Linux变得有趣的软件仓库 一、epel源介绍 EPEL&#xff08;Extra Packages for Enterprise Linux&#xff09;源是一个由Fedora项目组维护的第三方软件仓库&#xff0c;为企业级Linux发行版&#xff08;如Red Hat Enterprise Linux&#xff08;…

清明作业 c++

1.封装一个类&#xff0c;实现对一个数求累和阶乘质数 #include <iostream>using namespace std; int mproduct(int a){if(a>1){return a*mproduct((a-1));}else{return 1;} } class number{int a; public:number():a(5){};number(int a):a(a){}void set(int a){thi…

c#编程基础学习之数组

目录 数组排序数组System.Linq 命名空间其他创建数组的方法 数组 数组用于在单个变量中存储多个值&#xff1b; 声明数组需定义带方括号[]的变量类型&#xff1b; string[] cars {"MARRY", "BOB", "Far", "Aada"}; //字符串数组 in…

ChatGLM-6B实战微调(P-tuning-v2、LORA)

硬件准备 GPU: NVIDIA Tesla A30 24GB python 3.10 pytorch 1.11 transformers 4.27.1 实验环境 Change your pip source pip config set global.extra-index-url https://pypi.tuna.tsinghua.edu.cn/simple # Writing to /opt/conda/pip.conf pip config set global.inde…

【嵌入式学习】FreeRTOS day04.05

练习 1.总结二进制信号量和计数型信号量的区别&#xff0c;以及他们的使用场景。 二进制信号量&#xff1a;信号量的数值只有0和1&#xff08;用于共享资源的访问&#xff09; 计数性信号量&#xff1a;计数型信号量的值一般是大于或者等于2&#xff08;生产者和消费者模型&am…

软件设计原则:开闭原则

定义 开闭原则&#xff08;Open-Closed Principle, OCP&#xff09;是面向对象设计的基本原则之一&#xff0c;由 Bertrand Meyer 提出。它指出软件实体&#xff08;类、模块、函数等&#xff09;应该对扩展开放&#xff0c;对修改封闭。这意味着软件应该设计成在不修改现有代…

如何从vue项目界面上看出来node.js版本

一&#xff1a; 要从 Vue 项目界面上看出当前使用的 Node.js 版本&#xff0c;你可以通过以下几种方式进行查看&#xff1a; 开发者工具&#xff1a;在浏览器中打开 Vue 项目&#xff0c;并打开开发者工具&#xff08;一般是按下 F12 键&#xff09;。在开发者工具的控制台选项…

【复读EffectiveC++16】条款16:成对使用new和delete时要采取相同形式

条款16&#xff1a;成对使用new和delete时要采取相同形式 此条款&#xff0c;依然是针对对象管理资源的补充&#xff0c;内容分为三个部分&#xff1a; 错误案例为什么要采取相同形式需要注意什么 一、错误案例 取原书的例子&#xff1a; std::string* stringArray new s…