Spring AOP实现

news2024/9/23 19:28:54

Spring AOP实现

  • AOP概述
    • 什么是AOP
    • 什么是Spring AOP
  • Spring AOP快速入门
    • 引入依赖
    • 实现计时器
  • Spring AOP详解
    • Spring AOP核心概念
      • 切点(Pointcut)
      • 连接点(Join Point)
      • 通知(Advice)
      • 切面(Aspect)
    • 通知类型
      • 注意事项
    • @PointCut
    • 多个切面
    • 切面优先级 @Order
    • 切点表达式
      • execution表达式
      • @annotation

AOP概述

在这里插入图片描述

什么是AOP

Aspect Oriented Programming(面向切面编程)
什么是面向切面编程,切面指的是某一类特定的问题,所以AOP也可以理解为面向特定方法的编程
简单来说:AOP是一种思想,是对某一类问题的集中处理

什么是Spring AOP

AOP是一种思想,它的实现方法有很多,其中包括Spring AOP,也有AspectJ、CGLIB等

Spring AOP快速入门

引入依赖

在pom.xml文件中添加配置

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

实现计时器

这里我通过一个AOP的实现来记录程序中各个函数执行的时间

package com.example.demo.aspect;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Slf4j
@Aspect
@Component
public class TimeAspect {
    @Around("execution(* com.example.demo.controller.*.*(..))")
    public Object timeCost(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        Object result = joinPoint.proceed();
        long end = System.currentTimeMillis();
        log.info(joinPoint+"消耗时间:{}",end-start+"ms");
        return result;
    }
}

执行上述代码
在这里插入图片描述
我们来分析一些这段代码
在这里插入图片描述
这个注解,表明当前类是一个切面类
在这里插入图片描述
这个注解,将该段程序交给spring来管理
在这里插入图片描述
环绕通知,在目标方法的前后都会被执行.后面的表达式表示对哪些方法进行增强
在这里插入图片描述
表示当前需要执行的方法
在这里插入图片描述
表示开始执行当前的方法
在这里插入图片描述
上面代码就解析完成了,接下来,我们开始正式学习AOP的知识

Spring AOP详解

Spring AOP核心概念

切点(Pointcut)

Pointcut的作用就是提供⼀组规则(使用AspectJ pointcut expression language 来描述), 告诉程序对哪些方法来进行功能增强.
在这里插入图片描述
@Around注解里面的就是切入点的表达式

连接点(Join Point)

满足切点表达式规则的方法, 就是连接点. 也就是可以被AOP控制的方法,以入门程序举例, 所有 com.example.demo.controller 路径下的方法, 都是连接点.
切点和连接点的关系
连接点是满足切点表达式的元素. 切点可以看做是保存了众多连接点的⼀个集合

通知(Advice)

通知就是具体要做的工作, 指哪些重复的逻辑,也就是共性功能(最终体现为一个方法)比如上述程序中记录业务方法的耗时时间, 就是通知
在这里插入图片描述

切面(Aspect)

切⾯(Aspect) = 切点(Pointcut) + 通知(Advice),既是整个程序

通知类型

上面我们讲了什么是通知, 接下来学习通知的类型. @Around 就是其中⼀种通知类型, 表示环绕通知.
Spring中AOP的通知类型有以下几种
• @Around: 环绕通知, 此注解标注的通知方法在目标方法前, 后都被执行
• @Before: 前置通知, 此注解标注的通知方法在目标方法前被执行
• @After: 后置通知, 此注解标注的通知方法在目标方法后被执行, 无论是否有异常都会执行
• @AfterReturning: 返回后通知, 此注解标注的通知方法在目标方法后被执行, 有异常不会执行
• @AfterThrowing: 异常后通知, 此注解标注的通知方法发生异常后执行
接下来.我们通过程序来进行学习

package com.example.demo.aspect;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Slf4j
@Aspect
@Component
public class AspectDemo {
    @Before("execution(* com.example.demo.controller.*.*(..))")
    public void doBefore(){
        log.info("执行AspectDemo doBefore");
    }
    @After("execution(* com.example.demo.controller.*.*(..))")
    public void doAfter(){
        log.info("执行AspectDemo doAfter");
    }
    @AfterReturning("execution(* com.example.demo.controller.*.*(..))")
    public void doAfterReturning(){
        log.info("doAfterReturning");
    }
    @AfterThrowing("execution(* com.example.demo.controller.*.*(..))")
    public void doAfterThrowing(){
        log.info("doAfterThrowing");
    }
    @Around("execution(* com.example.demo.controller.*.*(..)) ")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("doAround 前");
        Object result = joinPoint.proceed();
        log.info("doAround 后");
        return result;
    }
}

package com.example.demo.controller;

import com.example.demo.aspect.MyAspect;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
public class HelloController {
    @RequestMapping("/t1")
    public String helloTest(){
        return "t1";
    }
    @RequestMapping("/t2")
    public String testError(){
        Integer i = 10/0;
        return "t2";
    }
}

我们运行程序,我们通过访问t1的url来执行t1的程序
在这里插入图片描述
可以看到,各个注解执行的时间顺序是不一样的
而在程序正常的情况下,@AfterThrowing的通知并没有执行
那么接下来,我们来访问t2的url
结果是出现报错了,我们跳过程序打印的错误日志,可以看到
在这里插入图片描述
执行结果变成了四个

注意事项

• @Around 环绕通知需要调用ProceedingJoinPoint.proceed() 来让原始方法执行, 其他通知不需要考虑目标方法执行.
• @Around 环绕通知方法的返回值, 必须指定为Object, 来接收原始方法的返回值, 否则原始方法执行完毕, 是获取不到返回值的,而且@Around必须要有返回值

@PointCut

上面代码存在⼀个问题, 就是存在大量重复的切点表达式 execution(*
com.example.demo.controller..(…)) , Spring提供了 @PointCut 注解, 把公共的切点表达式提取出来, 需要用到时引用该切入点表达式即可.
下面我们来讲讲怎么使用

@Pointcut("execution(* com.example.demo.controller.*.*(..))")
    public void pointcut(){}

这里的方法名可以随意取
定义完成,怎么使用呢
我们只需要将pointcut()把其他通知里的切入点表达式给替换掉就行了

package com.example.demo.aspect;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Slf4j
@Aspect
@Component
public class AspectDemo {
    @Pointcut("execution(* com.example.demo.controller.*.*(..))")
    public void pointcut(){}
    @Before("pointcut()")
    public void doBefore(){
        log.info("执行AspectDemo doBefore");
    }
    @After("pointcut()")
    public void doAfter(){
        log.info("执行AspectDemo doAfter");
    }
    @AfterReturning("pointcut()")
    public void doAfterReturning(){
        log.info("doAfterReturning");
    }
    @AfterThrowing("pointcut()")
    public void doAfterThrowing(){
        log.info("doAfterThrowing");
    }
    @Around("pointcut() ")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("doAround 前");
        Object result = joinPoint.proceed();
        log.info("doAround 后");
        return result;
    }
}

这样就好啦,如果需要在其他切面中使用该切入点的定义时,需要将方法的修饰改为public,同时需要在切入点表达式中,在pointcut()前加上全限定类名,引用方式为: 全限定类名.方法名()

多个切面

我们创建多个切面类,观察一些结果

package com.example.demo.aspect;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Slf4j
@Aspect
@Component
public class AspectDemo1 {
    @Before("com.example.demo.aspect.AspectDemo.pt()")
    public void doBefore(){
        log.info("执行AspectDemo1 doBefore");
    }
    @After("com.example.demo.aspect.AspectDemo.pt()")
    public void doAfter(){
        log.info("执行AspectDemo1 doAfter");
    }
}
package com.example.demo.aspect;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Slf4j
@Aspect
@Component
public class AspectDemo2 {
    @Before("com.example.demo.aspect.AspectDemo.pt()")
    public void doBefore(){
        log.info("执行AspectDemo2 doBefore");
    }
    @After("com.example.demo.aspect.AspectDemo.pt()")
    public void doAfter(){
        log.info("执行AspectDemo2 doAfter");
    }
}
package com.example.demo.aspect;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Slf4j
@Aspect
@Component
public class AspectDemo3 {
    @Before("com.example.demo.aspect.AspectDemo.pt()")
    public void doBefore(){
        log.info("执行AspectDemo3 doBefore");
    }
    @After("com.example.demo.aspect.AspectDemo.pt()")
    public void doAfter(){
        log.info("执行AspectDemo3 doAfter");
    }
}

执行程序,访问t1
在这里插入图片描述
从执行结果可以看出,如果程序中有多个切面,执行顺序是依据切面类的名字来执行的
存在多个切面类时,默认按照切面类的类名字母排序:
• @Before 通知:字母排名靠前的先执行
• @After 通知:字母排名靠前的后执行

切面优先级 @Order

对于多个切面的情况,我们可以利用 @Order来控制他们执行的顺序
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
执行程序,观察结果
在这里插入图片描述
很明显,执行结果如愿得到了优化
@Order 注解标识的切面类, 执行顺序如下:
• @Before 通知:数字越小先执行
• @After 通知:数字越大先执行

切点表达式

上面的代码中, 我们⼀直在使用切点表达式来描述切点. 下面我们来介绍⼀下切点表达式的语法.
切点表达式常见有两种表达方式

  1. execution(……):根据方法的签名来匹配
  2. @annotation(……) :根据注解匹配

execution表达式

execution(<访问修饰符> <返回类型> <包名.类名.方法(方法参数)> <异常>)
其中:访问修饰符和异常可以省略
切点表达式⽀持通配符表达:

  1. '* :匹配任意字符,只匹配⼀个元素(返回类型, 包, 类名, 方法或者方法参数)
    a. 包名使用* 表示任意包(⼀层包使用⼀个*)
    b. 类名使用* 表示任意类
    c. 返回值使用* 表示任意返回值类型
    d. ⽅法名使用* 表示任意方法
    e. 参数使用* 表示⼀个任意类型的参数
  2. … :匹配多个连续的任意符号, 可以通配任意层级的包, 或任意类型, 任意个数的参数
    a. 使用… 配置包名,标识此包以及此包下的所有子包
    b. 可以使用… 配置参数,任意个任意类型的参数

@annotation

首先,我们先自定义一个注解(其他已有的注解也可以)

package com.example.demo.aspect;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface MyAspect {

}

在我们需要实现功能增强的连接点(方法)上添加我们刚刚创建的注解
这里我们在t1上添加
在这里插入图片描述
然后我们可以再创建一个切面类

package com.example.demo.aspect;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Slf4j
@Aspect
@Component
public class MyAspectDemo {
    @Before("@annotation(com.example.demo.aspect.MyAspect)")
    public void doBefore(){
        log.info("MyAspectDemo doBefore");

    }
    @After("@annotation(com.example.demo.aspect.MyAspect)")
    public void doAfter(){
        log.info("MyAspectDemo doAfter");
    }
}

可以看到,代码中,我们的切入点表达式格式是
@annotation(注解的全限定类名+注解名)
接下来,我们执行程序,注意,这里需要屏蔽其他切面的影响
访问t1
在这里插入图片描述
程序执行成功了
然后我们执行t2
可以看到,日志并没有t2的相关结果
那么到这里,AOP的使用,我就分享完了,感谢大家的支持

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

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

相关文章

【开源】基于JAVA的就医保险管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 科室档案模块2.2 医生档案模块2.3 预约挂号模块2.4 我的挂号模块 三、系统展示四、核心代码4.1 用户查询全部医生4.2 新增医生4.3 查询科室4.4 新增号源4.5 预约号源 五、免责说明 一、摘要 1.1 项目介绍 基于JAVAVue…

双链表的基本知识以及增删查改的实现

满怀热忱&#xff0c;前往梦的彼岸 前言 之前我们对单链表进行了非常细致的剖析&#xff0c;现在我们所面临的则是与之相对应的双链表&#xff0c;我会先告诉诸位它的基本知识&#xff0c;再接着把它的增删查改讲一下&#xff0c;ok&#xff0c;正文开始。 一.链表的种类 我…

07.领域驱动设计:掌握整洁架构、六边形架构以及3种常见微服务架构模型的对比和分析

目录 1、概述 2、整洁架构 3、六边形架构 4、三种微服务架构模型的对比和分析 5、从三种架构模型看中台和微服务设计 5.1 中台建设要聚焦领域模型 5.2 微服务要有合理的架构分层 5.2.1 项目级微服务 5.2.2 企业级中台微服务 5.3 应用和资源的解耦与适配 6、总结 1、概…

三步万能公式解决软件各种打不开异常

程序员都知道,辛苦做的软件发给客户打不开那是一个大写的尴尬,尴尬归尴尬还是要想办法解决问题. 第一步清理环境. 目标机台有环境和没有运行环境的,统统把vs环境卸载了,让目标机台缺少环境.第二步打包环境 源代码添加打包工程,setup,重新编译.![添加setup ](https://img-blo…

LeetCode——415. 字符串相加

C开头 &#x1f636;‍&#x1f32b;️&#x1f636;‍&#x1f32b;️&#x1f636;‍&#x1f32b;️&#x1f636;‍&#x1f32b;️Take your time ! &#x1f636;‍&#x1f32b;️&#x1f636;‍&#x1f32b;️&#x1f636;‍&#x1f32b;️&#x1f636;‍&#…

【架构论文】SCALE: Secure and Scalable Cache Partitioning(2023 HOST)

SCALE: Secure and Scalable Cache Partitioning 摘要 LLC可以提高性能&#xff0c;但是会引入安全漏洞&#xff0c;缓存分配的可预测变化可以充当侧信道&#xff0c;提出了一种安全的缓存分配策略&#xff0c;保护缓存免受基于时间的侧信道攻击。SCALE使用随机性实现动态可扩…

AI大模型专题:2024大模型安全流通平台市场厂商评估报告

今天分享的是AI大模型系列深度研究报告&#xff1a;《AI大模型专题&#xff1a;2024大模型安全流通平台市场厂商评估报告》。 &#xff08;报告出品方&#xff1a;揽睿星舟&#xff09; 报告共计&#xff1a;22页 大模型安全流通平台市场分析 企业需要大模型安全流通平台覆盖…

C语言 开发篇+一个简单的数据库管理系统ZDB

说明&#xff1a;本文供数据库爱好者和初级开发人员学习使用 标签&#xff1a;数据库管理系统、RDBMS、C语言小程序、C语言、C程序 系统&#xff1a;Windows 11 x86 CPU &#xff1a;Intel IDE &#xff1a;CLion 语言&#xff1a;C语言 标准&#xff1a;C23 提示&#xff1a;如…

C语言用SHBrowseForFolder弹出选择文件夹的对话框

【程序运行效果】 【程序代码】 main.c&#xff1a; #define COBJMACROS #include <stdio.h> #include <tchar.h> #include <Windows.h> #include <windowsx.h> #include <CommCtrl.h> #include <ShlObj.h> #include "resource.h&q…

JVM-类的生命周期

类的生命周期概述 类的生命周期描述了一个类加载、使用、卸载的整个过程。整体可以分为&#xff1a; 加载 连接&#xff0c;其中又分为验证、准备、解析三个子阶段 初始化 使用 卸载 加载阶段 加载(Loading)阶段第一步是类加载器根据类的全限定名通过不同的渠道以二进制流的方…

深入玩转Playwright:高级操作解析与实践

playwright高级操作 iframe切换 ​ 很多时候&#xff0c;网页可能是网页嵌套网页&#xff0c;就是存在不止一个html标签&#xff0c;这时候我们的selenium或者playwright一般来说定位不到&#xff0c;为什么呢&#xff1f; ​ 因为默认是定位到第一个标准的html标签内部。 …

费一凡:土木博士的自我救赎之道 | 提升之路系列(五)

导读 为了发挥清华大学多学科优势&#xff0c;搭建跨学科交叉融合平台&#xff0c;创新跨学科交叉培养模式&#xff0c;培养具有大数据思维和应用创新的“π”型人才&#xff0c;由清华大学研究生院、清华大学大数据研究中心及相关院系共同设计组织的“清华大学大数据能力提升项…

五、防御保护---防火墙出口选路篇

五、防御保护---防火墙智能选路篇 一、就近选路二、策略路由选路1.策略路由的概念1.1匹配条件&#xff08;通过ACL定义&#xff09;1.2动作 三、智能选路 --- 全局路由策略1.基于链路带宽的负载分担2.基于链路质量进行负载分担3.基于链路权重进行负载分担4.基于链路优先级的主备…

股票市场

&#xff08;一&#xff09;股票市场 顾名思义&#xff0c;就是买卖股票的场所。就是为了撮合想发展但缺钱的企业与有钱但想投资的投资者。 股票市场按照交易场所&#xff0c;可分为场内市场和场外市场&#xff1a; 场内市场是指证券交易所&#xff0c; 场外市场就是证券交易…

比Filebeat更强大的日志收集工具-Fluent bit的http插件实战

文章目录 1.前言2. fluent bit http插件配置以及参数详解3. Http 接口服务3.1 开发Http 接口服务3.2 重启fluent bit向http web服务发送数据 1.前言 Fluent Bit 的 HTTP 插件提供了一种灵活而通用的机制&#xff0c;可用于将日志数据 从各种环境中传输到指定的远程服务器&#…

Python算法题集_滑动窗口最大值

本文为Python算法题集之一的代码示例 题目239&#xff1a;滑动窗口最大值 说明&#xff1a;给你一个整数数组 nums&#xff0c;有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。 返回 滑动窗…

常见的网络安全威胁和防护方法

随着数字化转型和新兴技术在各行业广泛应用&#xff0c;网络安全威胁对现代企业的业务运营和生产活动也产生了日益深远的影响。常见的网络安全威胁通常有以下几种&#xff1a; 1. 钓鱼攻击 攻击者伪装成合法的实体&#xff08;如银行、电子邮件提供商、社交媒体平台等&#xf…

C++实现通讯录管理系统

目录 1、系统需求 2、创建项目 2.1 创建项目 3、菜单功能 4、退出功能 5、添加联系人 5.1 设计联系人结构体 5.2 设计通讯录结构体 5.3 main函数中创建通讯录 5.4 封装联系人函数 5.5 测试添加联系人功能 6、显示联系人 6.1 封装显示联系人函数 7、删除联系人 7.1…

获取依赖aar包的两种方式-在android studio里引入 如:glide

背景&#xff1a;我需要获取aar依赖到内网开发&#xff0c;内网几乎代表没网。 一、 如何需要获取依赖aar包 方式一&#xff1a;在官方的github中下载,耗时不建议 要从开发者网站、GitHub 存储库或其他来源获取 ‘com.github.bumptech.glide:glide:4.12.0’ AAR 包&#xff…

MySQL:MVCC原理详解

MySQL是允许多用户同时操作数据库的&#xff0c;那么就会出现多个事务的并发场景。那么再并发场景会出现很多问题&#xff1a;脏读、不可重复读、幻读的问题。 而解决这些问题所用到的方法就是&#xff1a;MVCC 多版本并发控制。而这个MVCC的实现是基于read_view、undoLog 如…