AOP面向切面(方法)编程

news2024/11/23 18:50:20

AOP面向切面(方法)编程

快速入门:以下示例是计算DeptServiceImpl每一个方法执行的时间

package com.example.aop;

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

@Slf4j
@Aspect  标记这是一个切面类
@Component
public class MyAspect {
    // 提取公共的切入点表达式,这里表明DeptServiceImpl类中的所有方法
    @Pointcut("execution(* com.example.service.impl.DeptServiceImpl.*(..))")
    private void pt() {
    }

    @Around("pt()")
    public Object TimeAspect(ProceedingJoinPoint joinPoint) throws Throwable {
        long begin = System.currentTimeMillis();
        Object result = joinPoint.proceed();
        long end = System.currentTimeMillis();
        log.info(joinPoint.getSignature() + "执行耗时:{}", end - begin);
        return result;
    }
}

效果如下

在这里插入图片描述

我们创建一个切面类,当目标对象(使用@Pointcut指定的对象)的方法被调用时,AOP框架(如Spring AOP)会生成目标对象的代理对象(Proxy)。这个代理对象会拦截对目标对象方法的调用,然后执行这个代理对象中的函数(称为通知),这个代理对象中的函数就是我们在切面类中定义的函数(例如这里的TimeAspect),当通过代理对象调用方法时,代理对象会先执行切面类中定义的通知(如前置通知后置通知,环绕通知等),这里的@Around就是环绕通知,然后再执行目标对象的原方法。而不是直接调用我们原来的函数,这样就可以在原来的函数执行前后插入我们自己的代码,这就是AOP的原理

执行流程:调用者 -> 代理对象 -> 切面类中的通知 -> 原方法 -> 切面类中的通知 -> 返回结果

再例如下面这些通知

package com.example.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Slf4j
@Aspect // 标记这是一个切面类
@Component
public class AopTest{
    // 提取公共的切入点表达式,这里表明DeptServiceImpl类中的所有方法
    @Pointcut("execution(* com.example.service.impl.DeptServiceImpl.*(..))")
    private void pointcut() {
    }

    @Around("pointcut()")
    public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("around环绕通知");
        return joinPoint.proceed();
    }

    @Before("pointcut()")
    public void beforeAdvice(JoinPoint joinPoint) {
        // 方法执行前的逻辑
        log.info("before前置通知");
    }

    @After("pointcut()")
    public void afterAdvice(JoinPoint joinPoint) {
        // 方法执行后的逻辑
        log.info("after后置通知");
    }

    @AfterReturning("pointcut()")
    public void afterReturningAdvice(JoinPoint joinPoint) {
        // 方法返回后的逻辑
        log.info("afterReturning返回通知");
    }

    @AfterThrowing("pointcut()")
    public void afterThrowingAdvice(JoinPoint joinPoint) {
        // 方法抛出异常后的逻辑
        log.info("afterThrowing异常通知");
    }

}

一、通知类型

AOP提供了五种通知方式,分别是:

  • @Around:环绕通知,在目标方法执行前后都执行,这里的前后通知不是执行两次,而是在Object result = joinPoint.proceed()前后都能添加逻辑,比较特殊
  • @Before:前置通知,在目标函数执行前被执行
  • @After :后置通知,在目标函数执行后执行,不论目标方法是否正常返回或抛出异常都会执行。
  • @AfterReturning :后置通知,在目标函数正常返回时(不报错)才执行,如果目标函数报错了就不执行
  • @AfterThrowing : 后置通知,只有在目标函数报错时才执行,如果不报错就不执行,与@AfterReturning相反

二、通知顺序

如果有多个切面类,则按切面类名排序

  • 前置通知:字母排序靠前的先执行
  • 后置通知:字母靠前的后执行

可以这么理解,这就是一个栈,排序靠前的先进栈,然后后出站,先进后出

在这里插入图片描述

  • 使用@Order(数字)放在切面类上来控制顺序
@Order(2)
@Slf4j
@Aspect // 标记这是一个切面类
@Component
public class AopTest

@Order(1)
@Slf4j
@Aspect // 标记这是一个切面类
@Component
public class BopTest

在这里插入图片描述

三、切入点表达式

  • 基本概念

在这里插入图片描述

使用示例:

@Pointcut("execution(* com.example.service.*.*(..))")
public void allMethodsInServicePackage() {
    // 切入点表达式匹配 com.example.service 包下的所有类的所有方法
}

@Pointcut("execution(* com.example.service.MyService.*(..))")
public void allMethodsInMyService() {
    // 切入点表达式匹配 com.example.service.MyService 类中的所有方法
}

@Pointcut("execution(* com.example.service.MyService.myMethod(..))")
public void specificMethod() {
    // 切入点表达式匹配 com.example.service.MyService 类中的 myMethod 方法
}

@Pointcut("execution(* com.example.service.MyService.myMethod(String, ..))")
public void specificMethodWithStringParam() {
    // 切入点表达式匹配 com.example.service.MyService 类中的 myMethod 方法,
    // 该方法的第一个参数类型是 String,其他参数任意
}

@Pointcut("execution(* com.example.service.*.*(..)) && @annotation(org.springframework.transaction.annotation.Transactional)")
public void allTransactionalMethodsInServicePackage() {
    // 切入点表达式匹配 com.example.service 包下的所有标注了 @Transactional 注解的方法
}

  • 注意事项

在这里插入图片描述

注意,切入点表达式尽量缩小范围,范围过大会导致程序运行效率较低

通过上述切入点表达式,我们会发现execution在指定特定的多个方法时就比较麻烦,需要使用&&,||等,不利于使用,下面介绍更利于特定方法使用的方式

  • 使用自定义注释
package com.example.aop;

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 MyLog {
}

创建方式:

在这里插入图片描述

我们只需要在目标方法上使用自定义注解,就能使用AOP代理了

  • 在切面类中指定自定义注释的全类名
package com.example.aop;

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

@Slf4j
@Aspect // 标记这是一个切面类
@Component
public class MyAspect {
    @Around("@annotation(com.example.anno.MyLog)")
    public Object recordLog(ProceedingJoinPoint joinPoint) throws Throwable {
        Object result = joinPoint.proceed();
        return result;
    }
}

  • 添加注释
public class MyService {
    @MyLog
    public void method1() {
        // 方法实现
    }

    @MyLog
    public void method2() {
        // 方法实现
    }
}

四、连接点

在这里插入图片描述

由于@Around的使用比较特殊,只能通过ProceedingJoinPoint对象获取相关信息,而其他通知只能使用JoinPoint来获取

  • ProceedingJoinPoint
@Around("execution(* com.itheima.service.DeptService.*(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
    // 获取目标类名
    String className = joinPoint.getTarget().getClass().getName();
    System.out.println("Target Class: " + className);

    // 获取目标方法签名
    Signature signature = joinPoint.getSignature();
    System.out.println("Method Signature: " + signature);

    // 获取目标方法名
    String methodName = joinPoint.getSignature().getName();
    System.out.println("Method Name: " + methodName);

    // 获取目标方法运行参数
    Object[] args = joinPoint.getArgs();
    System.out.println("Method Arguments: " + Arrays.toString(args));

    // 执行原始方法,获取返回值
    Object res = joinPoint.proceed();
    System.out.println("Method Result: " + res);

    return res;
}

  • JoinPoint
@Before("execution(* com.itheima.service.DeptService.*(..))")
public void beforeAdvice(JoinPoint joinPoint) {
    // 获取目标类名
    String className = joinPoint.getTarget().getClass().getName();
    System.out.println("Target Class: " + className);

    // 获取目标方法签名
    Signature signature = joinPoint.getSignature();
    System.out.println("Method Signature: " + signature);

    // 获取目标方法名
    String methodName = joinPoint.getSignature().getName();
    System.out.println("Method Name: " + methodName);

    // 获取目标方法运行参数
    Object[] args = joinPoint.getArgs();
    System.out.println("Method Arguments: " + Arrays.toString(args));
}

对比

  • JoinPoint 适用于所有类型的通知(前置通知、后置通知、返回通知、异常通知),但它没有 proceed() 方法,因此无法控制目标方法的执行。
  • ProceedingJoinPoint 继承自 JoinPoint,仅适用于环绕通知。它包含 proceed() 方法,可以在通知中执行目标方法,并且在方法执行的前后插入逻辑。

总结起来,JoinPointProceedingJoinPoint 都可以用来获取目标方法的各种信息,但只有 ProceedingJoinPoint 可以控制目标方法的执行。

使用示例:

  • 自定义注释
package com.example.anno;

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 MyLog {
}

  • 记录员工操作日记
package com.example.aop;

import com.alibaba.fastjson.JSONObject;
import com.example.mapper.OperateLogMapper;
import com.example.pojo.OperateLog;
import com.example.utils.JwtUtils;
import io.jsonwebtoken.Claims;
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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.Arrays;

@Slf4j
@Aspect // 标记这是一个切面类
@Component
public class MyAspect {
    @Autowired
    private HttpServletRequest request;
    @Autowired
    private OperateLogMapper operateLogMapper;

    @Around("@annotation(com.example.anno.MyLog)")
    public Object recordLog(ProceedingJoinPoint joinPoint) throws Throwable {
        // 获取token
        String token = request.getHeader("token");
        // 解析token
        Claims claims = JwtUtils.parseJWT(token);
        // 获取用户id
        Integer operateUser = (Integer) claims.get("id");
        // 当前操作时间
        LocalDateTime operateTime = LocalDateTime.now();
        //操作的类名
        String className = joinPoint.getTarget().getClass().getName();
        //操作的方法名
        String methodName = joinPoint.getSignature().getName();
        //操作方法的参数
        Object[] args = joinPoint.getArgs();
        String methodParams = Arrays.toString(args);

        // 调用目标方法
        long begin = System.currentTimeMillis();
        Object result = joinPoint.proceed();
        long end = System.currentTimeMillis();
        //操作的返回值
        String returnValue = JSONObject.toJSONString(result);
        //操作的耗时
        long costTime = end - begin;
        //记录日志
        OperateLog log = new OperateLog(null, operateUser, operateTime, className, methodName, methodParams, returnValue, costTime);
        operateLogMapper.insert(log);
        return result;
    }
}

效果图
在这里插入图片描述

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

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

相关文章

springboot集成shardingsphere

导入maven依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spr…

时代巨兽!深度神经网络如何改变我们的世界?

深度神经网络 1、 简介1.1 定义深度神经网络1.2 深度学习的发展历程1.3 深度神经网络的应用领域 2、深度神经网络的基本原理2.1 神经元层2.1.1 神经元2.1.2 神经元层 2.2 前向传播2.3 反向传播2.4 激活函数2.4.1、作用2.4.2、常见激活函数2.4.3、选择激活函数的考虑 2.5 损失函…

Qt QListView自定义树状导航控件

大部分的软件都有多个页面&#xff0c;这时候就需要一个导航栏控件&#xff0c;通过在导航栏中选择某一栏&#xff0c;同时显示对应的页面。 本文代码效果如下&#xff1a; 本文的导航栏控件基于大佬 feiyangqingyun 的导航栏控件博客Qt/C编写自定义控件46-树状导航栏_qt之实现…

24年最新版基础入门大模型辅助Python编程指南

今天这篇文章只会聊 Python 入门基础&#xff0c;如何通过大模型辅助Python 编程&#xff0c;在 后续的文章里慢慢聊。 一点点 python都不会。又想用大模型带飞&#xff0c;辅助 python 编程&#xff0c;提升数据分析能力和效率&#xff0c;怎么办&#xff1f; 一点都不需要担…

Dify源码本地部署启动

背景 Dify是一个开源LLM应用程序开发平台。Dify的直观界面结合了人工智能工作流、RAG管道、代理功能、模型管理、可观察性功能等&#xff0c;让您快速从原型到生产。 Dify提供在线试用功能&#xff0c;可以直接在线体验其功能。同时也支持docker部署&#xff0c;源码部署等方…

C++多线程:生产者消费者模式

文章目录 一、模式简介二、头文件、全局变量2.1 仓库类的设计2.1.1 关于仓库类的分析2.1.2 仓库类的设计代码 2.2 工厂类的设计2.2.1 关于工厂类的分析2.2.2 工厂类的设计代码a 将产品item放到仓库repob 将产品item从仓库repo取出c 生产者操作d 消费者操作 2.2.3 主函数代码 三…

Github 2024-06-14 开源项目日报Top10

根据Github Trendings的统计,今日(2024-06-14统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量JavaScript项目2Python项目2非开发语言项目2TypeScript项目1Dart项目1Rust项目1Lua项目1Java项目1Jupyter Notebook项目1从零开始构建你喜爱的技…

Jira,一个强大灵活的项目和任务管理工具 Python 库

目录 01初识 Jira 为什么选择 Jira? 02安装与配置 安装 jira 库 配置 Jira 访问 获取 API token: 配置 Python 环境: 03基本操作 创建项目 创建任务 查询任务 更新任务 删除任务 04高级操作 处理子任务 搜索任务 添加附件 评论任务 05实战案例 自动化创建…

java:spring actuator扩展原有info endpoint的功能

# 项目代码资源&#xff1a; 可能还在审核中&#xff0c;请等待。。。 https://download.csdn.net/download/chenhz2284/89437506 # 项目代码 【pom.xml】 <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId&…

DomoAI让你轻松变身视频达人!支持20s完整视频生成!

账号注册 官网&#xff1a;https://www.domoai.app/zh-Hant/library 功能 支持不同风格的视频类型&#xff0c;支持图片转视频&#xff0c;支持文字转图片&#xff0c;支持静态图片变为动态。 可以切换语言为中文 风格转换 选择不同风格的 支持生成20s&#xff0c;目前接触…

牛客网金九银十最新版互联网Java高级工程师面试八股文出炉!面面俱到,太全了

前言 作为一个 Java 程序员&#xff0c;你平时总是陷在业务开发里&#xff0c;每天噼里啪啦忙敲着代码&#xff0c;上到系统开发&#xff0c;下到 Bug 修改&#xff0c;你感觉自己无所不能。然而偶尔的一次聚会&#xff0c;你听说和自己一起出道的同学早已经年薪 50 万&#x…

IDEA创建web项目

IDEA创建web项目 第一步&#xff1a;创建一个空项目 第二步&#xff1a;在刚刚创建的项目下创建一个子模块 第三步&#xff1a;在子模块中引入web 创建结果如下&#xff1a; 这里我们需要把这个目录移到main目录下&#xff0c;并改名为webapp&#xff0c;结果如下 将pom文件…

贪心算法学习四

例题一 解法&#xff08;暴⼒解法 -> 贪⼼&#xff09;&#xff1a; 暴⼒解法&#xff1a; a. 依次枚举所有的起点&#xff1b; b. 从起点开始&#xff0c;模拟⼀遍加油的流程 贪⼼优化&#xff1a; 我们发现&#xff0c;当从 i 位置出发&#xff0c;⾛了 step 步…

如何在 Windows 上安装 MySQL(保姆级教程2024版)

MySQL 是最流行的数据库管理系统 (DBMS) 之一。它轻量、开源且易于安装和使用&#xff0c;因此对于那些刚开始学习和使用关系数据库的人来说是一个不错的选择。 本文主要系统介绍Windows的环境下MySQL的安装过程和验证过程。 目录 1 安装过程 1.1 前置要求 1.2 下载并安装 …

【three.js】旋转、缩放、平移几何体

目录 一、缩放 二、平移 三、旋转 四、居中 附源码 BufferGeometry通过.scale()、.translate()、.rotateX()、.rotateY()等方法可以对几何体本身进行缩放、平移、旋转,这些方法本质上都是改变几何体的顶点数据。 我们先创建一个平面物体,样子是这样的。 一、缩放 // 几何…

基于Matlab的人脸表情识别系统(GUI界面)【W5】

简介&#xff1a; 该系统是一个基于Matlab开发的人脸表情识别应用程序&#xff0c;旨在识别输入图像中的人脸表情&#xff0c;并通过直观的图形用户界面&#xff08;GUI&#xff09;向用户展示识别结果。系统结合了图像处理、机器学习和用户交互技术&#xff0c;使用户能够轻松…

【PL理论】(24) C- 语言:有块的作用域 | 更新的语法 | 新的语义域 | 环境 vs. 内存

&#x1f4ad; 写在前面&#xff1a;我们将再次扩展之前的C语言&#xff0c;让我们向这种语言引入“作用域”的概念。 目录 0x00 C- 语言&#xff1a;有块的作用域 0x01 C- 语言&#xff1a;更新的语法 0x02 新的语义域 0x03 环境 vs. 内存 0x00 C- 语言&#xff1a;有块的…

DistilBertModel模型的简单解释

前言 DistilBertModel((embeddings): Embeddings((word\_embeddings): Embedding(30522, 768, padding\_idx0)(position\_embeddings): Embedding(512, 768)(LayerNorm): LayerNorm((768,), eps1e-12, elementwise\_affineTrue)(dropout): Dropout(p\0.1, inplaceFalse))(trans…

洗地机哪个牌子质量好,性价比高?一文盘点市场热门选择

近年来&#xff0c;洗地机因为其能快速的解决我们耗时、费力又繁琐的地板清洁工作&#xff0c;备受人们的喜爱。但面对多款设备不同功能和特点相近的洗地机&#xff0c;你可能会疑惑&#xff1a;“洗地机哪个牌子质量好&#xff1f;”&#xff0c;如果你正在寻找一款高效、便捷…

视频剪辑可以赚钱吗 快速学会视频剪辑的方法

由于视频剪辑的需求不断增长&#xff0c;学会视频剪辑成为一项自媒体必备的技能&#xff0c;这个技能可以为个人带来收入和职业发展带来机会。无论是作为自由职业者还是在公司工作&#xff0c;掌握视频剪辑技能都可以为你提供更多的工作机会和竞争优势。这篇文章将讲解视频剪辑…