读《Spring实战》:面向切面

news2025/1/16 6:49:33

AOP术语

通知(Advice)

在AOP中,切面的工作被称为通知,也就是通知就是具体要干的工作。
spring中有5中通知:

  • 前置通知: 在目标方法之前调用通知功能
  • 后置通知: 在目标方法之后调用通知功能
  • 返回通知: 在目标方法成功执行之后调用通知功能
  • 异常通知: 在执行目标方法抛出异常后调用通知功能
  • 环绕通知: 通知包裹了目标方法,在目标方法调用之前和调用之后执行自定义的行为。

连接点(Join point)

应用执行过程中的一个时机,可以体现为方法调用时,抛出异常时,方法返回时,甚至修改一个字段,通常和方法有关。

切点(Poincut)

切点可以描述为在哪里,在代码中就表现为明确的类和方法名称,以及方法参数来明确在哪里调用通知功能。

切面(Aspect)

切面是通知和切点的结合,通知和切点共同组成了切面的定义: 切面要做什么,在哪里做,在什么时候做。

引入(Introduction)

引入可以向现有的类添加新方法和属性。例如,可以新建一个通知类,这个通知类用来记录一个对象的最后修改时间,这个通知类只需要一个LocalDatTime属性,和一个setLastUpdateTime(LocalDateTime updateTime)方法即可。在目标对象发生修改的时候,这个通知类中的记录最后修改时间的属性和方法就可以加到目标对象中,从而可以在无需修改现有类的情况下让目标对象具有新的行为和状态。(但是这种可读性就是不太好,暂时没遇到)

织入(Weaving)

织入是把切面应用到目标对象并创建新的代理对象的过程,切面在指定的连接点(when)被织入到目标对象中,在目标对象的生命周期中有多个连接点可以织入:

  • 编译器:切面在目标对象编译的时候织入,这种方式需要特殊的编译器,AspectJ的织入编译就是以这种方式织入切面的。
  • 类加载期: 切面在目标类加载到JVM的时候织入,这种方式需要特殊的类加载器(ClassLoader),它可以在目标类被引入应用之前就增强目标类的字节码。AspectJ5的加载时织入就支持以这种方式织入切面。
  • 运行期: 切面在应用运行的某个时刻织入。一般情况下,在织入切面的时候,AOP容器会目标对象动态的创建一个代理对象。SpringAOP就是以这种方式织入切面的。

Spring AOP的支持

基础认识

并不是所有的AOP框架都是相同的,有些允许在字段修饰符级别应用通知,有些只支持与方法调用相关的连接点。Spring AOP构建在动态代理基础之上,因此Spring对于AOP的支持局限于方法拦截,Spring AOP提供了4中了类型的AOP:

  • 基于代理的经典Spring AOP
  • 纯POJO切面
  • @AspectJ注解驱动的切面
  • 注入式AspectJ切面(适用于Spring各版本)

关于Spring AOP的AspectJ切点,最重要的一点就是Spring仅支持AspectJ切点指示器的一个子集,这里的指示器我理解就是定位目标对象方法的一种表达式,根据这些指示器能找到织入切面的目标对象的方法在哪里,Spring AOP所支持的AspectJ切点指示器:

  • arg(): 限制目标方法的参数有哪些,是什么类型的 使用 (…)表示不限制
  • execution(): 用于匹配是连接点的执行方法
  • this: 限制执行目标方法的上下文对象,在Spring AOP中指的是代理对象,而不是被代理的对象
  • target: 限制执行目标方法的被代理对象的类型
  • within(): 限制 目标对象所在的位置(包,类,方法都可以)

在Spring 中尝试使用AspectJ其他指示器的时候,将会抛出IllegalArgumentException异常。上面的指示器中只有execution指示器时实际执行匹配的,而其他的指示器都是用来显示匹配的,说明execution是最主要的指示器,其他的都是用来辅助匹配的。并且在指示器的表达式中还可以使用逻辑词(&& ! || )。

Spring中使用AspectJ注解来声明通知方法:

  • @After: 在目标方法返回之后或者抛出异常之后
  • @AfterReturning: 在目标方法返回之后
  • @AfterThrowing: 在目标方法抛出异常之后
  • @Around: 通知方法将包裹目标方法,相当于前置和后置
  • @Before: 在调用目标方法之前

测试代码:

public interface IDay0404Target {

     void rain();

      void rain(String name);

      void rain(String name,String age);
}
@Component("day0404TargetImpl")
public class Day0404TargetImpl implements IDay0404Target{
    @Override
    public void rain() {
        System.out.println("无参的rain方法");
        rainOne();
    }

    @Override
    public void rain(String name) {
        System.out.println("一个String参数的rain方法");
    }

    @Override
    public void rain(String name,String age) {
        System.out.println("两个String参数的rain方法");
    }

    public  Integer rainOne(){
        System.out.println(" 返回值是Integer的rain方法");
        return 1;
    }
}
@Component("day0404TargetTwoImpl")
public class Day0404TargetTwoImpl implements IDay0404Target{
    @Override
    public void rain() {
        System.out.println("day0404TargetTwoImpl 无参的rain方法");
    }

    @Override
    public void rain(String name) {
        System.out.println("day0404TargetTwoImpl 一个String参数的rain方法");
    }

    @Override
    public void rain(String name,String age) {
        System.out.println("day0404TargetTwoImpl 两个String参数的rain方法");
    }
}

@RequestMapping("/day0404")
@RestController
public class Day0404Controller {

    @Autowired
    @Qualifier("day0404TargetImpl")
    private IDay0404Target day0404Target;

    @Autowired
    @Qualifier("day0404TargetTwoImpl")
    private IDay0404Target two;
    @GetMapping("/test")
    public void test(){
            day0404Target.rain();
            day0404Target.rain("ddd");
            day0404Target.rain("1231","12313");
            two.rain("一个");
    }
}


//  第一个 * 表示 不限制返回值,目标方法是Day0404TargetImpl类的rain()方法
//  使用@PointCut注解可以减少很多切点的命名  下面通知的注解直接使用这个修饰的方法即可。
@Pointcut("execution(* com.example.reactor_test.day0404.Day0404TargetImpl.rain())")
public void rainTest(){};


// 表示 Day0404TargetImpl 类中方法参数只有一个String的方法
@Pointcut("execution(* com.example.reactor_test.day0404.Day0404TargetImpl.*(String))")
public void rainTest2(){

}

// 表示 Day0404TargetImpl 类中方法名为rain,并且方法参数有两个String的方法
@Pointcut("execution(* com.example.reactor_test.day0404.Day0404TargetImpl.rain(..)) && args(String,String)")
public void rainTest3(){

}

// execution(* com.example.reactor_test.day0404.IDay0404Target.*(..)) 表示是 IDay0404Target 接口的实现类 并且任意方法
// this(com.example.reactor_test.day0404.Day0404TargetTwoImpl)  限制了执行方法的对象类型要是  Day0404TargetTwoImpl
@Pointcut("execution(* com.example.reactor_test.day0404.IDay0404Target.*(..)) && this(com.example.reactor_test.day0404.Day0404TargetTwoImpl)")
public void rainTest4(){

}

// target(com.example.reactor_test.day0404.Day0404TargetTwoImpl)  限制了执行方法的被代理对象的类型,也就是实际执行对象类型  要是  Day0404TargetTwoImpl
@Pointcut("execution(* com.example.reactor_test.day0404.IDay0404Target.*(..)) && target(com.example.reactor_test.day0404.Day0404TargetTwoImpl)")
public void rainTest5(){

}

// within(com.example.reactor_test.day0404.Day0404TargetImpl)  限制了执行方法的类
//  && execution(* rain(String)) 限制了方法名称是rain,并且参数只有一个String
@Pointcut("within(com.example.reactor_test.day0404.Day0404TargetImpl) && execution(* rain(String))")
public void rainTest6(){

}

//  Integer  限制了方法返回值是 Integer,并且方法是rainOne()
@Pointcut("execution(Integer com.example.reactor_test.day0404.Day0404TargetImpl.rainOne())")
public void rainTest7(){};

@Before("rainTest()")
public void beforeRain(){
    System.out.println("rainTest 的   @Before 方法");
}


@Before("rainTest2()")
public void afterRain(){
    System.out.println("rainTest2 的  @Before方法");
}

@Before("rainTest3()")
public void afterRain2(){
    System.out.println("rainTest3 的  @Before方法");
}

@Before("rainTest4()")
public void afterRain3(){
    System.out.println("rainTest4 的  @Before方法");
}

@Before("rainTest5()")
public void test5(){
    System.out.println("rainTest5 的  @Before方法");
}

@Before("rainTest6()")
public void test6(){
    System.out.println("rainTest6 的  @Before方法");
}

@Before("rainTest7()")
public void test7(){
    System.out.println("rainTest7 的  @Before方法");
}

}

执行结果:


rainTest 的   @Before 方法
无参的rain方法
rainTest2 的  @Before方法
rainTest6 的  @Before方法
一个String参数的rain方法
rainTest3 的  @Before方法
两个String参数的rain方法
rainTest4 的  @Before方法
rainTest5 的  @Before方法
day0404TargetTwoImpl 一个String参数的rain方法

Spring中还引入了一个新的bean执行器:

execution(*.com.cn.test.one.two.perform()) && bean('one')

这个指示器其实和this有点像,它不是AspectJ中原生的,而是Spring AOP中的概念,限制的对象:

所匹配的是IoC容器中具有指定ID或名称的Bean。由于Spring AOP默认采用代理模式进行增强,因此实际上在进行AOP代理时,Spring容器返回给客户端的将会是代理对象,而非原始的被代理对象。

环绕通知

直接上代码:

@Aspect
@Component
public class Day0404AroundAspect {

    @Pointcut("execution(* com.example.reactor_test.day0404.Day0404TargetImpl.rain())")
    public void rain(){

    }

    @Around("rain()")
    public void test(ProceedingJoinPoint joinPoint){
        System.out.println("第一次打印+"+ LocalDateTime.now().toString());
        try {
            Thread.sleep(5000);
            joinPoint.proceed();
        }catch (Throwable throwable){
            System.out.println("发生错误");
        }

        System.out.println("第二次打印"+ LocalDateTime.now().toString());
    }
}
打印结果:


第一次打印+2024-04-04T15:27:04.544536400
rainTest 的   @Before 方法
无参的rain方法
 返回值是Integer的rain方法
第二次打印2024-04-04T15:27:09.553697300
rainTest2 的  @Before方法
rainTest6 的  @Before方法
一个String参数的rain方法
rainTest3 的  @Before方法
两个String参数的rain方法
rainTest4 的  @Before方法
rainTest5 的  @Before方法
day0404TargetTwoImpl 一个String参数的rain方法

根据打印结果也可以看到,如果不执行joinPoint.proceed()的话是会阻塞的,可以用来做失败尝试操作;并且第一次打印和第二次打印就相当于调用了@Before注解和@After注解,将这两个注解放在了一个方法等于。

传递参数

@Aspect
@Component
public class Day0404ReceiveParamAspect {


    @Pointcut("execution(* com.example.reactor_test.day0404.Day0404TargetImpl.rain(String)) && args(name)")
    public void rainTest2(String name){

    }

    @Before("rainTest2(name)")
    public void test(String name){
        System.out.println("接收到的参数的值是:"+name);
    }
}

引入

之前在术语那块有提到引入,使用引入可以给目标对象加入新的方法和属性。在使用@After,@Before这些有关AOP注解来包装某些bean的时候,Spring会生成这些bean对象的代理对象实例(通过JDK代理或者cglib代理的方式),这些代理对象会实现这些bean实现的接口(没有实现接口就会继承bean),从而在间接调用被代理对象的原始方法的时候进行增强,添加自定义逻辑。如果这些代理对象可以暴露出某个接口,对调用者而言就是被代理对象增加了新的方法和属性。

在这里插入图片描述

调用者去调用目标对象(也就是被代理对象,切面应用的对象)的方法的时候,代理会把此调用委托给目标对象,当调用引入的方法的时候,会委托给引入的代理对象。也就是一个bean的实现被拆分到了多个类中,由被代理对象和引入代理队形来共同完成。

public interface IDay0404Target {

     void rain();

      void rain(String name);

      void rain(String name,String age);
}
@Component
public class IDay0404ImportImpl implements IDay0404ImportInterface{
    @Override
    public void rainProcessor() {
        System.out.println("引入的对象的方法 ");
    }
}
@Component
@Aspect
public class Day0404ImportConfig {

    @DeclareParents(value = "com.example.reactor_test.day0404.impl.Day0404TargetImpl",defaultImpl = IDay0404ImportImpl.class)
    public  static IDay0404ImportInterface day0404ImportInterface;

    @DeclareParents(value = "com.example.reactor_test.day0404.impl.Day0404TargetTwoImpl",defaultImpl = IDay0404ImportImpl.class)
    public  static IDay0404ImportInterface two;
}
    @Autowired
    @Qualifier("day0404TargetImpl")
    private IDay0404Target day0404Target;

    @Autowired
    @Qualifier("day0404TargetTwoImpl")
    private IDay0404Target two;
    @GetMapping("/test")
    public void test(){
            day0404Target.rain();
            day0404Target.rain("ddd");
            day0404Target.rain("1231","12313");
            two.rain("一个");
        IDay0404ImportInterface t1 = (IDay0404ImportInterface) day0404Target;
        IDay0404ImportInterface t2 = (IDay0404ImportInterface) two;
        t1.rainProcessor();
        t2.rainProcessor();
    }
打印结果:

。。。。。。。。
。。。。。。。。
引入的对象的方法 
引入的对象的方法 

本来是想用 @DeclareParents(value = “com.example.reactor_test.day0404.IDay0404Target+”,defaultImpl = IDay0404ImportImpl.class),但是有了+号会启动失败,理论说又了+号表示不包含自身,只包含子类,但是没好使,就放弃了。

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

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

相关文章

无线电和雷达频谱大全

1,频率单位 2,全球警用雷达频率 3,军用雷达频段 4,国际电联ITU雷达频段 5,无线电频段 6,电子对抗ECM频段 7,声波频段

C#清空窗体的背景图片

目录 一、涉及到的知识点 1.设置窗体的背景图 2.加载窗体背景图 3.清空窗体的背景图 二、 示例 一、涉及到的知识点 1.设置窗体的背景图 详见本文作者的其他文章:C#手动改变自制窗体的大小-CSDN博客 https://wenchm.blog.csdn.net/article/details/137027140…

基于SpringBoot和Vue的金融融资管理系统的设计和实现【附源码】

1、系统演示视频(演示视频) 2、需要交流和学习请联系

paddlepaddle模型转换onnx指导文档

一、检查本机cuda版本 1、右键找到invdia控制面板 2、找到系统信息 3、点开“组件”选项卡, 可以看到cuda版本,我们这里是cuda11.7 cuda驱动版本为516.94 二、安装paddlepaddle环境 1、获取pip安装命令 ,我们到paddlepaddle官网&#xff…

网络原理 - HTTP / HTTPS(3)——http响应

目录 一、认识 “状态码”(status code) 常见的状态码 (1)200 OK (2)404 Not Found (3)403 ForBidden (4)405 Method Not Allowed (5&…

Unity框架,ET框架8.1版本的打包流程记录

目录 打包代码前置1.必须要安装Visusal Studio 2022的组件,如下图,必须都要进行安装,不然会在代码重构的时候报错,丢失SDK。Rider的版本必须2023及以上 步骤一、使用Rider编辑器打开项目后进行重构项目步骤二、使用HybirdCLR生成A…

openGauss学习笔记-256 openGauss性能调优-使用Plan Hint进行调优-优化器GUC参数的Hint

文章目录 openGauss学习笔记-256 openGauss性能调优-使用Plan Hint进行调优-优化器GUC参数的Hint256.1 功能描述256.2 语法格式256.3 参数说明 openGauss学习笔记-256 openGauss性能调优-使用Plan Hint进行调优-优化器GUC参数的Hint 256.1 功能描述 设置本次查询执行内生效的…

Flume 拦截器概念及自定义拦截器的运用

文章目录 Flume 拦截器拦截器的作用拦截器运用1.创建项目2.实现拦截器接口3.编写事件处理逻辑4.拦截器构建5.打包与上传6.编写配置文件7.测试运行 Flume 拦截器 在 Flume 中,拦截器(Interceptors)是一种可以在事件传输过程中拦截、处理和修改…

【Qt 学习笔记】Qt的坐标体系

博客主页:Duck Bro 博客主页系列专栏:Qt 专栏关注博主,后期持续更新系列文章如果有错误感谢请大家批评指出,及时修改感谢大家点赞👍收藏⭐评论✍ Qt的坐标体系 文章编号:Qt 学习笔记 / 11 文章目录 Qt的坐…

ML.NET(二) 使用机器学习预测表情分析

这个例子使用模型进行表情分析: 准备数据: happy,sad 等; using Common; using ConsoleApp2; using Microsoft.ML; using Microsoft.ML.Data; using System.Diagnostics; using static Microsoft.ML.Transforms.ValueToKeyMappingEstimator;…

[C#]OpenCvSharp实现直方图均衡化全局直方图局部直方图自适应直方图

【什么是直方图均衡化】 直方图均衡化是一种简单而有效的图像处理技术,它旨在改善图像的视觉效果,使图像变得更加清晰和对比度更高。其核心原理是将原始图像的灰度直方图从可能较为集中的某个灰度区间转变为在全部灰度范围内的均匀分布。通过这种方法&a…

【接口】HTTP(1)|请求|响应

1、概念 Hyper Text Transfer Protocol(超文本传输协议)用于从万维网(就是www)服务器传输超文本到本地浏览器的传送协议。 HTTP协议是基于TCP的应用层协议,它不关心数据传输的细节,主要是用来规定客户端和…

【Linux】第二个小程序--简易shell

请看上面的shell,其本质就是一个字符串,我们知道bash本质上就是一个进程,只不过命令行就是一个输出的字符串, 我们输入的命令“ls -a -l”实际上是我们在输入行输入的字符串,所以,如果我们想要做一个简易的…

vscode开发ESP32问题记录

vscode 开发ESP32问题记录 1. 解决vscode中的波浪线警告 1. 解决vscode中的波浪线警告 参考链接:https://blog.csdn.net/fucingman/article/details/134404485 首先可以通过vscode 中的IDF插件生成模板工程,这样会自动创建.vscode文件夹中的一些json配…

Jackson @JsonUnwrapped注解扁平化 序列化反序列化数据

参考资料 Jackson 2.x 系列【7】注解大全篇三JsonUnwrapped 以扁平的数据结构序列化/反序列化属性Jackson扁平化处理对象 目录 一. 前期准备1.1 前端1.2 实体类1.3 Controller层 二. 扁平化序列反序列化数据2.1 序列化数据2.2 反序列化数据 三. 前缀后缀处理属性同名四. Map数…

RabbitMQ3.7.8集群分区(脑裂现象)模拟及恢复处置全场景测试

测试环境准备: MQ服务器集群地址,版本号为3.7.8: 管理控制台地址:http://173.101.4.6:15672/#/queues 集群状态 rabbitmqctl cluster_status 集群操作相关命令: 创建一个RabbitMQ集群涉及到如下步骤: 安装RabbitMQ: 在每台要在集…

【Linux】Ubuntu 文件权限管理

Linux 系统对文件的权限有着严格的控制,用于如果相对某个文件执行某种操作,必须具有对应的权限方可执行成功,这也是Linux有别于Windows的机制,也是基于这个权限机制,Linux可以有效防止病毒自我运行。因为运行的条件是必…

软件架构复用

1.软件架构复用的定义及分类 软件产品线是指一组软件密集型系统,它们共享一个公共的、可管理的特性集,满足某个特定市场或任务的具体需要,是以规定的方式用公共的核心资产集成开发出来的。即围绕核心资产库进行管理、复用、集成新的系统。核心…

【随笔】Git 高级篇 -- 相对引用2(十三)

💌 所属专栏:【Git】 😀 作  者:我是夜阑的狗🐶 🚀 个人简介:一个正在努力学技术的CV工程师,专注基础和实战分享 ,欢迎咨询! 💖 欢迎大…

HTML:框架

案例&#xff1a; <frameset cols"5%,*" ><frame src"left_frame.html"><frame src"right_frame.html"> </frameset> 一、<frameset>标签 <frameset>标签&#xff1a;称为框架标记&#xff0c;将一个HTML…