读《Spring实战》:面向切面

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),但是有了+号会启动失败,理论说又了+号表示不包含自身,只包含子类,但是没好使,就放弃了。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SQL Server维护计划

目录 1.概述 2.启动SQL Server 代理服务 3.制定维护计划 4.验证维护计划 5.删除维护计划 1.概述 此文还是存货哈! SQL Server 2008 R2维护计划。 2.启动SQL Server 代理服务 在设置维护计划之前,必须先确保SQL Server 代理服务已启动。启动方法如…

FastAPI Web框架教程 第12章 异步async-await

12-1 fastapi是异步Web框架 从本教程开篇,我们就说FastAPI这个web框架是异步框架,那它到底是如何体现异步的呢? 想要学习使如何使用FastAPI的异步功能,那就必须要先了解什么是异步,什么是asyncio、async/await 【基…

BoostCompass —— 搜索引擎

文章目录 一、项目简介二、Boost库简介1. 简介2. Boost 库的特点 三、项目主要模块1. 网页内容获取,数据预处理模块2. 建立正排索引和倒排索引,项目核心模块3. 编写 http_server 模块,进行网络开放 四、项目功能预览1. 项目文件预览2. 项目执…

基于储能电站服务的冷热电多微网系统 双层优化配置

目录 一、主要内容 二、部分代码 三、程序结果 四、下载链接 一、主要内容 随着储能技术的进步和共享经济的发展,共享储能电站服务模式将成为未来用户侧储能应用的新形态。提出基于储能电站服务的冷热电联供型多微网系统双层优化配置方法。 首先,提出…

【深入理解计算机系统第3版】有符号数和无符号数转换以及移位运算练习题2.23

题目 考虑下面的C函数&#xff1a; int fun1(unsigned word) {return (int) ((word << 24) >> 24); }int fun2(unsigned word) {return ((int) word << 24) >> 24; } 假设一个采用补码运算的机器上以32位程序来执行这些函数。还假设有符号数值的右移…

C易错注意之const修饰指针,含char类型计算,位段及相关经典易错例题

目录 前言 一&#xff1a;const修饰指针 1.const修饰变量 2.const 修饰指针 1.const int*p&m; 2. int* const p&m; 3. int const *p&m; 4. int const *const p&m; 5.总结 总之一句话为&#xff1a;左定值有定向 二&#xff1a;关于计算中char类型…

前端开发基础(HTML5 + CSS3)【第一篇】:HTML标签之文字排版、图片、链接、音频、视频 涵盖了两个综合案例 做到了基础学得会,实战写的出

点击前往前端开发基础专栏&#xff1a; 文章目录 HTML5 CSS3 开发一、开发环境搭建下载 VS Code1. 2 插件的下载1.3 项目和文件的下载 二、 什么是 HTML2.1 标签的语法2.2 代码演示&#xff1a;2.3 小结 三 、HTML基本骨架3.1 快捷键生成HTML骨架3.2 代码展示3.3 小结 四、标…

AI绘画:实例-利用Stable Diffusion ComfyUI实现多图连接:区域化提示词与条件设置

在Stable Diffusion ComfyUI中&#xff0c;有一种高级技巧可以让用户通过细致的区域化提示词来控制图像的不同部分&#xff0c;从而实现多图连接的效果。这种方法允许艺术家在同一画布上展现多个场景&#xff0c;创造出富有层次和故事性的图像。以下是实现这一效果的详细步骤。…

Transformer模型-Multi-Head Attention多头注意力的简明介绍

今天介绍transformer模型的Multi-Head Attention多头注意力。 原论文计算scaled dot-product attention和multi-head attention 实际整合到一起的流程为&#xff1a; 通过之前文章&#xff0c;假定我们已经理解了attention&#xff1b;今天我们按顺序来梳理一下整合之后的顺序。…

与机器对话:ChatGPT 和 AI 语言模型的奇妙故事

原文&#xff1a;Talking to Machines: The Fascinating Story of ChatGPT and AI Language Models 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 从 ELIZA 到 ChatGPT&#xff1a;会话式人工智能的简史 会话式人工智能是人工智能&#xff08;AI&#xff09;的一个分…

三子棋(C游戏)

文章目录 三子棋的描述思路关键代码运行代码 三子棋的描述 三子棋是一种民间传统游戏&#xff0c;又叫九宫棋、圈圈叉叉棋、一条龙、井字棋等。游戏分为双方对战&#xff0c;双方依次在9宫格棋盘上摆放棋子&#xff0c;率先将自己的三个棋子走成一条线就视为胜利&#xff0c;…

Flink运行机制相关概念介绍

Flink运行机制相关概念介绍 1. 流式计算和批处理2. 流式计算的状态与容错3. Flink简介及其在业务系统中的位置4. Flink模型5. Flink的架构6. Flink的重要概念7. Flink的状态、状态分区、状态缩放&#xff08;rescale&#xff09;和Key Group8. Flink数据交换9. 时间语义10. 水位…

【TSP旅行商问题】改进的大邻域搜索算法LNS

课题名称&#xff1a;基于改进的大规模邻域搜索算法LNS求解TSP问题 版本时间&#xff1a;2024-04-01 程序运行&#xff1a;直接运行LNS_TSP.m 文件即可 代码获取方式&#xff1a; QQ&#xff1a;491052175 VX&#xff1a;Matlab_Lover 模型介绍&#xff1a; 第一步&…

grep无法使用完整的正则表达式

问题描述 grep无法使用完整的正则表达式&#xff0c;比如前置断言、后置断言、\d和\t、\n等 问题原因 使用了扩展正则&#xff0c;而不是perl正则。规则和perl正则不同 从文档上讲得很清楚&#xff1a; -E PATTERN is an extended regular expression 他是扩展表达式&#…

ChatGPT 之联盟营销

原文&#xff1a;ChatGPT for Affiliate Marketing 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 第二章 制定转化对话 制定转化对话是每个营销人员和企业所有者都应该掌握的关键技能。它涉及创建和传递引人入胜的信息&#xff0c;吸引您的受众并激励他们采取行动。…

vue给input密码框设置眼睛睁开闭合对于密码显示与隐藏

<template><div class"login-container"><el-inputv-model"pwd":type"type"class"pwd-input"placeholder"请输入密码"><islot"suffix"class"icon-style":class"elIcon"…

spark-hive连接操作流程、踩坑及解决方法

文章目录 1 简介2 版本匹配3 spark hive支持版本源码编译3.1 spark-src下载3.2 maven换源3.3 spark编译 4 hive 安装与mysql-metastore配置4.1 mysql下载安装4.1.1 为mysql设置系统环境变量4.1.2 初次登陆更改root身份密码4.1.3 安装后直接更改密码 4.2 hive初始化4.2.1 编写hi…

Flutter仿Boss-4.短信验证码界面

效果 简述 在移动应用开发中&#xff0c;处理短信验证码是确保用户身份验证和安全性的重要步骤。本文将介绍如何使用Flutter构建一个短信验证码界面&#xff0c;让用户输入通过短信发送到他们手机的四位验证码。 依赖项 在这个项目中&#xff0c;我们将使用以下依赖项&#…

C# 实现子进程跟随主进程关闭

文章目录 前言一、如何实现&#xff1f;1、创建作业对象&#xff08;1&#xff09;、创建对象&#xff08;2&#xff09;、设置销毁作业时&#xff0c;关闭拥有的进程 2、子进程加入作业对象3、销毁作业对象&#xff08;1&#xff09;、手动销毁&#xff08;2&#xff09;、所在…

git 常用命令和使用方法

1.git理论基础 1.1git简介 git是一个开源的分布式版本控制系统&#xff0c;可以有效、高速地处理从很小到非常大的项目版本管理。 1.2git工作流程 在工作目录中&#xff0c;添加、修改文件将需要进行版本管理的文件放入暂存区中将暂存区域的文件提交到git仓库中 2.git基本…