后端:Aop 面向切面编程

news2024/12/26 11:42:32

文章目录

    • 1. Aop 初步学习面向切面编程,@EnableAspectJAutoProxy
    • 2. AOP的核心概念
    • 3. 前置通知(@Before)
    • 4. 后置通知(@After)
    • 5. 返回通知(@AfterReturning)
    • 6. 异常通知(@AfterThrowing)
    • 7. 通知的执行顺序
    • 8. 切点表达式的抽取
    • 9. 切点表达式的书写
      • 9.1 execution‌ 实现切点表达式
      • 9.2 within 实现切点表达式
      • 9.3 ‌‌@annotation 实现切点表达式

1. Aop 初步学习面向切面编程,@EnableAspectJAutoProxy

添加依赖,在新建的Spring Boot项目下的pom.xml文件添加aop对应的依赖,如下:

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

通过切面,在不改变原有代码的前提下,增强源代码的业务能力。下面是一段演示aop的代码。

package com.lize.demo.aop;

import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    public void add(){
        System.out.println("增加");
    }

    public void del(){
        System.out.println("删除");
    }

    public void query(){
        System.out.println("查询");
    }

    public void update(){
        System.out.println("修改");
    }
}

切面类,除了需要添加@Aspect注解表示这是一个切面类之外,还需要添加注解@Component表明这是一个Bean。通过注解@Around里边写上具体需要切入的方法,最终实现切面功能。

package com.lize.demo.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
// 标记为切面类
@Component
// 必须设置为切面的Bean
public class MyAspect {

    // 实现计时方法
    @Around("execution(* com.lize.demo.aop.UserService.*(..) )")
    // 切点表达式
    // * 表示方法的访问权限 可以为public等
    public void logTime(ProceedingJoinPoint point){

        long begin = System.currentTimeMillis();

        // 执行具体的方法
        try {
            point.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }

        long end = System.currentTimeMillis();

        System.out.println("用时为:"+(end-begin));
    }
}

单元测试类

package com.lize.demo.aop;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@SpringBootTest(classes = AopDemo.class)
@ComponentScan
public class AopDemo {
    @Test
    public void test(@Autowired UserService us){
        us.add();
    }
}

运行结果如下:
在这里插入图片描述
只运行了add方法,原本只是输出“增加”,但是添加切面之后,除了输出“增加”这个字符串之外,还额外输出执行这个add方法的用时。在这个过程中,出现问题有,运行上述代码之后,切面没有起作用,也就是打印结果依旧为“增加”,但是我的项目是一个Spring Boot项目,Spring Boot项目会自动通过启动类帮我们加上@EnableAspectJAutoProxy,从而使切面起作用。解决方法可以是在这个单元测试类上加上这个注解@EnableAspectJAutoProxy,或者去掉@ComponentScan注解以及把@SpringBootTest(classes = AopDemo.class)这个注解修改为@SpringBootTest。出现这个问题的原因是因为我的单元测试所在的目录与Spring Boot项目启动类所在的目录不在同一个目录下,如果在同一个目录下,可以省略这个注解 @EnableAspectJAutoProxy,不过,建议加上。
在这里插入图片描述

2. AOP的核心概念

目标对象(target):目标对象指将要被增强的对象。即包含主业务逻辑的类的对象,要增强的对象通常会有很多个
切面(aspect):指放存放增强代码的类。
通知(advice):用来放增强的代码的那个方法,通知方式可以有环绕通知(@Around)【代码增强在目标方法的任意位置,更加通用】、前置通知(@Before)【目标方法之前执行】、后置通知(@After)【目标方法之后执行】、异常通知(@AfterThrowing)【目标方法出现了异常执行】、返回通知(@AfterReturning)【目标方法返回值执行】。
切点(pointcut):增强代码要切入到哪些方法中,在代码中通常写的是切点表达式。
连接点(Join point):通知和目标方法的一个桥梁,要获取目标方法的信息,就得通过JoinPoint。

关于连接点JoinPoint,在上面的代码中使用的是这个ProceedingJoinPoint,查看源码可知,ProceedingJoinPoint继承JoinPoint,并且还添加了如下方法的。
在这里插入图片描述

3. 前置通知(@Before)

现在我想要在执行上述代码中的add方法之前获取add这个方法名,此时可以考虑使用前置通知,在上述代码的基础之上,修改对应的注解即可,参考代码如下:

package com.lize.demo.aop.advice;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
// 标记为切面类
@Component
// 必须设置为切面的Bean
public class MyAspect {

    @Before("execution(* com.lize.demo.aop.advice.UserService.*(..) )")
    // 切点表达式
    public void before(JoinPoint joinPoint){

        String name = joinPoint.getSignature().getName();
        // 获取当前执行的方法名
        System.out.println("当前执行的方法是:"+name);

    }
}

运行结果:
在这里插入图片描述

4. 后置通知(@After)

在目标方法执行之后再执行的代码,如下:

@After("execution(* com.lize.demo.aop.advice.UserService.*(..) )")
// 切点表达式
public void after(JoinPoint joinPoint){

    System.out.println("后置通知");
}

在这里插入图片描述

5. 返回通知(@AfterReturning)

在try\catch\finally这个防止异常代码结构中,如果在try中有返回值,依旧会执行finally里边的代码

获取目标方法的返回值:

@AfterReturning(value = "execution(* com.lize.demo.aop.advice.UserService.*(..) )",returning = "ans")
public void afterReturning(JoinPoint joinPoint,Object ans){
    System.out.println("返回通知。。目标方法的返回值为:"+ans);
}

上述代码中为了获取返回值对象,在注解中使用returning进行接收,查看@AfterReturning的源码就可以知道了。
在这里插入图片描述
运行结果如下:
在这里插入图片描述
因为在add方面里边没有返回值,因此上述返回值为null。

6. 异常通知(@AfterThrowing)

当目标方法出现异常时执行的方法,参考代码如下:
在这里插入图片描述

@AfterThrowing(value = "execution(* com.lize.demo.aop.advice.UserService.*(..) )",throwing = "ans")
public void afterThrowing(JoinPoint joinPoint,Exception ans){

    System.out.println("异常通知。。目标方法的报错信息为:"+ans);
}

通过throwing 获取异常信息,查看一下@AfterThrowing的源码就知道了。
在这里插入图片描述
运行结果如下:
在这里插入图片描述

7. 通知的执行顺序

  • 正常情况下:先执行前置通知、再执行目标方法,然后执行返回通知、最后执行后置通知;
  • 异常情况下:先执行前置通知、再执行目标方法、然后执行异常通知、最后执行后置通知。

在环绕通知中可以包含其他四种通知,因此环绕通知也是通用的。

8. 切点表达式的抽取

在这里插入图片描述
看一下上面的切点表达式,如果要进行修改,那么这些都需要进行修改,为此,可以利用切点表达式的抽取,把切点表达式抽取出来,这样只需要修改一处地方,在需要引入切点表达式的地方,引入方法即可,如下:

package com.lize.demo.aop.advice;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Aspect
// 标记为切面类
@Component
// 必须设置为切面的Bean
public class MyAspect {


    @Pointcut("execution(* com.lize.demo.aop.advice.UserService.*(..) )")
    public void pointCut(){}


    @Before("pointCut()")
    // 切点表达式
    public void before(JoinPoint joinPoint){

        String name = joinPoint.getSignature().getName();
        // 获取当前执行的方法名
        System.out.println("当前执行的方法是:"+name);
    }

    @After("pointCut()")
    // 切点表达式
    public void after(JoinPoint joinPoint){

        System.out.println("后置通知");
    }

    @AfterReturning(value = "pointCut()",returning = "ans")
    public void afterReturning(JoinPoint joinPoint,Object ans){

        System.out.println("返回通知。。目标方法的返回值为:"+ans);
    }

    @AfterThrowing(value = "pointCut()",throwing = "ans")
    public void afterThrowing(JoinPoint joinPoint,Exception ans){

        System.out.println("异常通知。。目标方法的报错信息为:"+ans);
    }
}

9. 切点表达式的书写

9.1 execution‌ 实现切点表达式

execution‌ 能匹配到方法级别

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
  • execution 表示切点标识符
  • modifiers-pattern 表示访问修饰符,即目标方法的访问权限,不写代表所有
  • ret-type-pattern 表示目标方法的返回值类型,可以为void,*代表所有
  • declaring-type-pattern 表示完整限定名,包含包名和类名;
    • 包名
      • 可以写完整的,如com.xx.service
      • 也可以这样写,如com.xx.* 等价于 com.xx.dao、com.xx.service等,反正”*“只能代表一个层级吧!比如com.xx.service.inter是不能匹配到的
      • 如果想任意层级 ,可以这样写com.xx…,这样就可以匹配到com.xx.service.inter、com.xx.service.inter.until等
    • 类名
      • ”*“代表所有类
      • com.xx…*可以匹配到com.xx.service.utils.inter.任意包.任意类
  • name-pattern 表示方法,"*"代表匹配所有
  • param-pattern 表示方法的参数,”…“表示任意参数,不写表示匹配无参数

9.2 within 实现切点表达式

within 只能匹配到级别,表示类下面的所有方法都能匹配到。

@Before("within(com.lize.demo.aop.advice.UserService)")
// 切点表达式
public void before(JoinPoint joinPoint){

    String name = joinPoint.getSignature().getName();
    // 获取当前执行的方法名
    System.out.println("当前执行的方法是:"+name);
}

9.3 ‌‌@annotation 实现切点表达式

@annotation 用于匹配特定注解的方法
这里定义了一个自定义的注解

package com.lize.demo.aop.advice;


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 {
    String value();
}

在这里插入图片描述

@Before("@annotation(log)")
// 切点表达式
public void before(JoinPoint joinPoint,MyLog log){

    String name = joinPoint.getSignature().getName();
    // 获取当前执行的方法名
    System.out.println("当前执行的方法是:"+name+"  "+log);
}

运行结果:
在这里插入图片描述

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

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

相关文章

三周精通FastAPI:41 部署:FastAPI版本和HTTPS概念

官方文档&#xff1a;https://fastapi.tiangolo.com/zh/deployment/versions/ 关于 FastAPI 版本 FastAPI 已在许多应用程序和系统的生产环境中使用。 并且测试覆盖率保持在100%。 但其开发进度仍在快速推进。 经常添加新功能&#xff0c;定期修复错误&#xff0c;并且代码仍…

Pinpoint(APM)进阶--Pinot指标采集(System Metric/Inspector)

接上文 Pinpoint使用Pinot进行指标数据存储&#xff0c;Pinot流摄入需要Kafka 本文详解Kafka和Pinot的安装部署&#xff0c;以及Pinpoint的指标采集 Pinot 简介 Apache Pinot是一个实时分布式OLAP数据存储&#xff0c;专为低延迟、高吞吐量分析而构建&#xff0c;非常适合面…

ReactPress:重塑内容管理的未来

ReactPress Github项目地址&#xff1a;https://github.com/fecommunity/reactpress 欢迎提出宝贵的建议&#xff0c;欢迎一起共建&#xff0c;感谢Star。 ReactPress&#xff1a;重塑内容管理的未来 在当今信息爆炸的时代&#xff0c;一个高效、易用的内容管理系统&#xff0…

uniapp路由与页面跳转详解:API调用与Navigator组件实战

UniApp路由与页面跳转详解&#xff1a;API调用与Navigator组件实战 路由 uniapp页面路由为框架统一管理&#xff0c;开发者需要在page.json里面配置每个路由页面的路径及页面样式。 路由跳转 uniapp有两种页面路由跳转方式&#xff0c;调用API跳转和navigator组件跳转。 调…

35.Redis 7.0简介

2022 年 2 月初&#xff0c;Redis 7.0 迎来了首个候选发布&#xff08;RC&#xff09;版本。这款内存键值数据库迎来了“重大的性能优化”和其它功能改进&#xff0c;性能优化包括降低写入时复制内存的开销、提升内存效率&#xff0c;改进 fsync 来避免大量的磁盘写入和优化延迟…

MySQL技巧之跨服务器数据查询:基础篇-如何获取查询语句中的参数

MySQL技巧之跨服务器数据查询&#xff1a;基础篇-如何获取查询语句中的参数 上一篇已经描述&#xff1a;借用微软的SQL Server ODBC 即可实现MySQL跨服务器间的数据查询。 而且还介绍了如何获得一个在MS SQL Server 可以连接指定实例的MySQL数据库的连接名: MY_ODBC_MYSQL 以…

连续15年霸榜“双11”行业第一,九牧做对了什么?

文 | 螳螂观察&#xff08;TanglangFin&#xff09; 作者 | 余一 随着“双十一”的落幕&#xff0c;各类销售榜单再次成为热门话题。 天猫“双11”全周期589个品牌成交额破亿&#xff0c;其中苹果、海尔、美的、小米、九牧等45个品牌成交额突破10亿。 值得注意的是在绝大多…

【网页设计】HTML5 和 CSS3 提高

目标 能够说出 3~5 个 HTML5 新增布局和表单标签能够说出 CSS3 的新增特性有哪些 1. HTML5 的新特性 注&#xff1a;该部分所有内容可参考菜鸟教程菜鸟教程 - 学的不仅是技术&#xff0c;更是梦想&#xff01; (runoob.com) HTML5 的新增特性主要是针对于以前的不足&#xf…

Linux手动安装nginx

本次以安装nginx-1.12.2为例 1、首先说明一下,安装nginx之前需要安装如下素材: 2、开始安装 第一步,安装依赖yum -y install gcc zlib zlib-devel pcre-devel openssl openssl-devel第二步,下载并安装nginx安装包(nginx官网:http://nginx.org/)# 下载 wget http://nginx…

基于springboot+vue实现的大型超市数据处理系统 (源码+L文+ppt)4-015

第4章 系统设计 本章主要讲述的是大型超市数据处理系统的设计开发结构&#xff0c;简单介绍了开发流程与数据库设计的原则以及数据表的关系结构图&#xff0c;并且详细的展示了数据表的内部结构信息与属性。 图4-2 大型超市数据处理系统总体结构图 4.4 数据表信息&#xff…

游戏引擎学习第七天

视频参考:https://www.bilibili.com/video/BV1QFmhYcE69 ERROR_DEVICE_NOT_CONNECTED 是一个错误代码&#xff0c;通常在调用 XInputGetState 或 XInputSetState 函数时返回&#xff0c;表示指定的设备未连接。通常会出现以下几种情况&#xff1a; 未连接控制器&#xff1a;如…

IEC60870-5-104 协议源码架构详细分析

IEC60870-5-104 协议源码架构 前言一、资源三、目录层级一二、目录层级二config/lib60870_config.hdependencies/READMEexamplesCMakeLists.txtcs101_master_balancedcs104_client_asyncmulti_client_servertls_clienttls_server说明 make这些文件的作用是否需要导入这些文件&a…

全面介绍软件安全测试分类,安全测试方法、安全防护技术、安全测试流程

一、软件系统设计开发运行安全 1、注重OpenSource组件安全检查和版本更新&#xff08;black duck&#xff09; 现在很多云、云服务器都是由开源的组件去搭成的&#xff0c;对于OpenSource组件应该去做一些安全检查和版本更新&#xff0c;尤其是版本管理&#xff0c;定期对在运…

Cent OS-7的Apache服务配置

WWW是什么&#xff1f; WWW&#xff08;World Wide Web&#xff0c;万维网&#xff09;是一个全球性的信息空间&#xff0c;其中的文档和其他资源通过URL标识&#xff0c;并通过HTTP或其他协议访问。万维网是互联网的一个重要组成部分&#xff0c;但它并不是互联网的全部。互联…

遗传算法与深度学习实战(23)——利用遗传算法优化深度学习模型

遗传算法与深度学习实战&#xff08;23&#xff09;——利用遗传算法优化深度学习模型 0. 前言1. 神经进化2. 使用遗传算法作为深度学习优化器小结系列链接 0. 前言 神经进化涵盖了所有用于改进深度学习的进化算法。更具体地说&#xff0c;神经进化用来定义应用于深度学习的特…

Kubernetes 核心组件调度器(Scheduler)

文章目录 一&#xff0c;调度约束1.Kubernetes的基本构建块和最小可调度单元pod创建过程&#xff08;工作机制&#xff0c;重点&#xff09;1.1list-watch 组件List-Watch 的优点List-Watch 的应用场景List-Watch 的挑战与优化 2.调度过程(重点)2.1调度过程&#xff1a;2.2Kube…

STM32WB55RG开发(3)----生成 BLE 程序连接手机APP

STM32WB55RG开发----3.生成 BLE 程序连接手机APP 概述硬件准备视频教学样品申请源码下载参考程序选择芯片型号配置时钟源配置时钟树RTC时钟配置RF wakeup时钟配置查看开启STM32_WPAN条件配置HSEM配置IPCC配置RTC启动RF开启蓝牙设置工程信息工程文件设置结果演示 概述 本项目旨…

[C++]内联函数和nullptr

> &#x1f343; 本系列为初阶C的内容&#xff0c;如果感兴趣&#xff0c;欢迎订阅&#x1f6a9; > &#x1f38a;个人主页:[小编的个人主页])小编的个人主页 > &#x1f380; &#x1f389;欢迎大家点赞&#x1f44d;收藏⭐文章 > ✌️ &#x1f91e; &#x1…

微软OmniParser:一切皆文档,OCR驱动智能操作

前沿科技速递&#x1f680; 微软推出的OmniParser是一种创新的框架&#xff0c;旨在将手机和电脑屏幕视为文档&#xff0c;通过OCR技术与多模态大模型实现对用户界面的深度理解和操作。OmniParser能够高效识别和提取界面中的文本信息、位置和语义&#xff0c;助力自动化操作。 …

使用 Web Search 插件扩展 GitHub Copilot 问答

GitHub Copilot 是一个由 GitHub 和 OpenAI 合作开发的人工智能代码提示工具。它可以根据上下文提示代码&#xff0c;还可以回答各种技术相关的问题。但是 Copilot 本身不能回答非技术类型的问题。为了扩展 Copilot 的功能&#xff0c;微软发布了一个名为 Web Search 的插件&am…