Spring 学习笔记03 - AOP

2023-10-27

AOP 概述

AOP 是什么

  • AOP - Aspect Oriented Programming,即面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术
    说白了,就是把我们重复使用的代码抽取出来,在运行时,动态地将代码切入到类的指定方法、指定位置上
    在这里插入图片描述
  • 好处
    AOP 把与业务逻辑无关的,却被各个业务模块大量调用的逻辑给封装起来,进而减少了系统的重复代码量,提高了代码的可重用性,降低了模块间的耦合度,以及有利于未来的扩展和维护,降低了维护成本。我认为最重要的是实现了各个方法本身只关注了核心业务逻辑

AOP 相关术语

  • 通知(Advice)
    就是使用(织入)到目标类连接点上的一段程序代码,比如上张图中的日志。
  • 连接点(JoinPoint)
    就是 Spring 允许你使用通知的地方,基本每个方法的前,后或抛出异常时都可以是连接点,spring只支持方法连接点。
  • 切入点(Pointcut)
    所有连接点都可以使用通知,但开发时并不是所有连接点都使用通知,所以切入点就是用来筛选或指定在哪些连接点上使用通知
  • 切面(Aspect)
    切面就是通知和切入点的结合,通知说明了要干什么,而切入点说明了要在哪个位置干。
  • 目标对象(Target
    被通知的对象,或者说被代理的对象,也就是真正的业务逻辑,它可以在毫不知情的情况下,被织入切面。
  • 代理对象(Proxy)
    一个类被 AOP 织入通知后,就产生一个结果类,它是一个融合了原类和通知逻辑的代理类。
  • 织入(Weaving)
    把切面应用到目标对象来创建新的代理对象的过程。

基于 XML 配置 AOP

简单实现

依赖

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.7.4</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

业务层接口及实现类

public interface StudentService {
    void insertStudent();
    void deleteStudent(Integer id);
}


public class StudentServiceImpl implements StudentService {
    @Override
    //模拟添加学生信息
    public void insertStudent() {
        System.out.println("执行添加学生方法");
        //int i = 1/0;
    }

    @Override
    //模拟删除学生信息
    public void deleteStudent(Integer id) {
        System.out.println("执行删除学生方法");
    }
}

通知类

public class Logger {
    public void beforeLog() {
        System.out.println("执行前置通知");
    }
    public void afterReturningLog() {
        System.out.println("执行后置通知");
    }
    public void afterThrowingLog() {
        System.out.println("执行异常通知");
    }
    public void afterLog() {
        System.out.println("执行最终通知");
    }
}

Spring 核心配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">
        
    <bean id="studentService" class="com.hn.service.impl.StudentServiceImpl"></bean>
    <!-- 通知类 -->
    <bean id="logger" class="com.hn.utils.Logger"></bean>
    <!-- 配置 AOP -->
    <aop:config>
        <!-- 配置切面 -->
        <aop:aspect id="loggerAdvice" ref="logger">
            <!-- 配置切入点表达式 -->
            <aop:pointcut id="ptStudentService" expression="execution(* com.hn.service.*.*(..))"/>
            <!-- 配置前置通知,在切入点方法执行之前执行 -->
            <aop:before method="beforeLog" pointcut-ref="ptStudentService"></aop:before>
            <!-- 配置后置通知,在切入点方法正常执行之后执行 -->
            <aop:after-returning method="afterReturningLog" pointcut-ref="ptStudentService"></aop:after-returning>
            <!-- 配置异常通知,在切入点方法执行时产生异常后执行 -->
            <aop:after-throwing method="afterThrowingLog" pointcut-ref="ptStudentService"></aop:after-throwing>
            <!-- 配置最终通知,无论切入点方法是否正常执行它都会在其后面执行 -->
            <aop:after method="afterLog" pointcut-ref="ptStudentService"></aop:after>
        </aop:aspect>
    </aop:config>
</beans>

AOP 的配置步骤

  1. 在 Spring 核心配置文件中引入约束并将通知类注入到 Spring 容器中;
  2. 使用 <aop:config> 标签声明AOP配置,所有关于 AOP 的配置都写在该标签内;
  3. 使用 <aop:aspect> 标签配置切面
<aop:config>
    <!-- 配置切面 -->
    <aop:aspect id="loggerAdvice" ref="logger">
    ...
    </aop:aspect>
</aop:config>

id 属性为该切面的唯一标识,ref 属性用于引用通知类
此标签写在 aop:aspect 标签内部时只能供当前切面使用;写在 aop:aspect 标签外部时则供全部切面使用
受到约束,该标签在 aop:aspect 标签外部时必须出现在 aop:aspect 标签之前

  1. 使用 <aop:pointcut> 标签配置切入点表达式,指定对哪些方法进行增强(通知),
<aop:config>
    <aop:aspect id="loggerAdvice" ref="logger">
	    <!-- 配置切入点 -->
	    <aop:pointcut id="ptStudentService" expression="execution(* com.hn.service.*.*(..))"></aop:pointcut>
	    ...
    </aop:aspect>
</aop:config>

id 属性为该切入点的唯一标识,expression 属性用于指定切入点表达式
切入点表达式的规范: execution([修饰符] 返回值类型 包路径.类名.方法名(参数))
标准的切入点表达式:public void com.hn.service.StudentServiceImpl.deleteStudent( int );全通配方式::* *..*.*(..)
一般我们都是对业务层实现类的方法进行增强,因此切入点表达式写法通常为:expression="execution(* com.hn.service.*.* (..)

  1. 在切面内配置通知

<aop:before> 标签:前置通知,指定在切入点方法执行之前执行
<aop:after-returning> 标签:后置通知,指定在切入点方法正常执行之后执行
<aop:afterthrowing> 标签:异常通知,指定在切入点方法执行时产生异常后执行
<aop:after> 标签:最终通知,指定无论切入点方法是否正常执行它都会在其后面执行
<aop:arround> 标签:环绕通知,可以在代码中手动控制通知的执行时机
单元测试

public class StudentServiceImplTest {
    @Test
    public void testDeleteStudent(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        StudentService studentService = (StudentService) applicationContext.getBean("studentService");
        studentService.deleteStudent(1);
    }
}

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

基于注解配置 AOP

需要用到的新注解

注解 作用
@EnableAspectJAutoProxy 开启 AOP 对注解的支持
@Aspect 声明当前类是切面类
@Pointcut 声明切入点表达式
@Before 声明该方法为前置通知
@AfterReturning 声明该方法为后置通知
@AfterThrowing 声明该方法为异常通知
@After 声明该方法为最终通知
@Around 声明该方法为环绕通知

简单实现

修改通知类,添加相应注解

@Configuration	//声明该类为配置类
@ComponentScan("com.hn")	//声明创建容器时要扫描的包
@Component("logger")	//将当前类对象存入容器中
@EnableAspectJAutoProxy	//开启 AOP 对注解的支持
@Aspect	//声明当前类是切面类
public class Logger {
    @Pointcut("execution(* com.hn.service.*.*(..))")	//声明切入点表达式
    public void pointcut(){}

    @Before("pointcut()")	//声明该方法为前置通知
    public void beforeLog() {
        System.out.println("执行前置通知");

    }
    @AfterReturning("pointcut()")	//声明该方法为后置通知
    public void afterReturningLog() {
        System.out.println("执行后置通知");
    }
    @AfterThrowing("pointcut()")	//声明该方法为异常通知
    public void afterThrowingLog() {
        System.out.println("执行异常通知");
    }
    @After("pointcut()")	//声明该方法为最终通知
    public void afterLog() {
        System.out.println("执行最终通知");
    }
}

在业务层实现类上添加注解

@Service("studentService")
public class StudentServiceImpl implements StudentService {
	...
}

单元测试

public class StudentServiceImplTest {
    @Test
    public void testInsertStudent(){
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(com.hn.utils.Logger.class);
        StudentService studentService = (StudentService) applicationContext.getBean("studentService");
        studentService.insertStudent();
    }
}

运行结果
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200501214719457.png

使用注解配置 AOP 的 bug

  • 在使用注解配置 AOP 时,会出现一个bug:四个通知的正常调用顺序原本应该为:前置通知 --> 后置通知/异常通知 --> 最终通知,而此时的调用顺序变成了 前置通知 --> 最终通知 --> 后置通知/异常通知,这会导致一些资源在执行最终通知时提前被释放掉了,而执行后置通知时就会出错。
  • 如果想解决这个问题,那么需要使用环绕通知,因为在环绕通知中每种通知的调用顺序是由我们自己决定的,不会受 Spring 的影响。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Spring 学习笔记03 - AOP 的相关文章

  • 无论线程如何,对象是否总是能看到其最新的内部状态?

    假设我有一个带有简单整数计数变量的可运行对象 每次可运行对象运行时该变量都会递增 该对象的一个 实例被提交以在计划的执行程序服务中定期运行 class Counter implements Runnable private int coun
  • 如何在ArrayList中的特定位置插入对象

    假设我有一个大小为 n 的对象的 ArrayList 现在我想在特定位置插入另一个对象 假设在索引位置 k 大于 0 且小于 n 并且我希望索引位置 k 处及其之后的其他对象向前移动一个索引位置 那么有没有什么方法可以直接在Java中做到这
  • 将处理项目移至 Eclipse

    我已经在处理项目上工作了一段时间 现在想将其移至 Eclipse 中 我已经在 Eclipse 环境中安装了 Proclipse 我有很多扩展名为 pde 的文件 然而 Proclipse 文件都以 java 结尾 所有 pde 文件都存在
  • Hashset - 创建 Set 后使对象相同

    如果我们在 HashSet 中添加两个不同的对象 可变的 然后通过调用 setter 更改对象的值 使它们相同 则大小仍然是 hashSet 的 2 我无法理解其原因 public static void main String args
  • java 中的梵文 i18n

    我正在尝试使用来自互联网的示例 ttf 文件在 java 中使用 i18n 进行梵文 印地文 我可以加载资源包条目 还可以加载 ttf 并设置字体 但它不会根据需要呈现 jlabel 它显示块代替字符 如果我在 Eclipse 中调试 我可
  • Java 小程序在 Mac 上闪烁

    这个问题很奇怪 问题并非在每个平台上都会发生 我在使用 MacOSX 的 Google Chrome 中出现了这种情况 但在 Safari 中却没有出现这种情况 对于使用 Windows 的朋友来说 在 Google Chrome 上运行得
  • Apache Thrift Java-Javascript 通信

    我正在编写一个基于 Apache Thrift 的 Java 服务器 它将从 Javascript 客户端接收数据 我已经完成了 Java 服务器 但问题是我可以获得 Javascript 客户端的工作示例 我无法找到一个好的示例 构建文档
  • 获取Android库中的上下文

    我正在编写一个 Android 应用程序 它的一些功能封装在内部库中 但是 要使此功能发挥作用 库需要一个应用程序上下文的实例 为图书馆提供这种上下文的最佳方式是什么 我看到了一些选择 但没有一个有吸引力 Have my library c
  • Spring 5.0.3 依赖问题

    升级到 spring 5 0 3 后我遇到以下问题 09 25 29 141 ERROR org jboss msc service fail ServerService Thread Pool 175 MSC000001 Failed t
  • Spring HATEOAS 和 HAL:更改 _embedded 中的数组名称

    我正在尝试使用 Spring HATEOAS 构建符合 HAL 的 REST API 经过一番摆弄后我终于开始工作了mostly正如预期的那样 示例 输出现在看起来像这样 links self href http localhost 808
  • 绘制平滑曲线

    我想创建更平滑的曲线 而不仅仅是线角 这是我现在画的图 这是我的代码 case FREEHAND float pts float ptk ptk new float 2 imageMatrix invert inv if mCurrentS
  • Java 中如何验证字符串的格式是否正确

    我目前正在用 Java 编写一个验证方法来检查字符串是否是要更改为日期的几种不同格式之一 我希望它接受的格式如下 MM DD YY M DD YY MM D YY 和 M D YY 我正在测试第一种格式 每次它都告诉我它无效 即使我输入了有
  • 如何让“循环”泛型在 Java 中工作?

    我在编译以下涉及一些泛型的代码时遇到错误 public abstract class State
  • 了解Kafka流groupBy和window

    我无法理解 kafka 流中的 groupBy groupById 和窗口的概念 我的目标是聚合一段时间内 例如 5 秒 的流数据 我的流数据看起来像 value 0 time 1533875665509 value 10 time 153
  • Java HashSet 是线程安全的只读吗?

    如果我通过 Collections unmodifyingSet 运行 HashSet 实例后 它是线程安全的吗 我问这个是因为 Set 文档声明它不是 但我只是执行读取操作 来自 Javadoc 请注意 此实现不是同步的 如果多个线程同时
  • 从字节数组设置 img src

    我需要设置img src我在对象中拥有的字节数组的属性 img
  • Spring - 如何在不匹配列名的情况下使用 BeanPropertyRowMapper

    我正在开发一个应用程序 该应用程序已使用行映射器从纯 JDBC 转换为 Spring 模板 我遇到的问题是数据库中的列与属性名称不匹配 这阻止我使用BeanPropertyRowMapper容易地 我看到一些关于在查询中使用别名的帖子 这会
  • 如何在 Spring Mvc 项目中设置上下文根

    我在 Tomcat 服务器中使用 Spring MVC 项目 每次运行应用程序时 服务器上下文根都会更改 如何设置固定上下文根 我的项目名称是 DemoApplication 首先部署此上下文根 路径是 http localhost 808
  • Firebase:用户注册后如何进行电话号码验证?

    所以我知道我可以使用电子邮件验证或电话号码验证 但我想做的是在用户注册或登录后进行电话号码验证 如何连接这两种身份验证方法 最后 Firebase中是否有一个函数可以检查用户是否通过电话号码验证 谢谢 即使用户已通过身份验证 您仍然可以使用
  • Android ClassNotFoundException:在路径上找不到类

    10 22 15 29 40 897 E AndroidRuntime 2561 FATAL EXCEPTION main 10 22 15 29 40 897 E AndroidRuntime 2561 java lang Runtime

随机推荐

  • 项目简单日志配置(控制台和输出文件)

    首先引入相关的log4j的jar包 然后配置log4j properties文件 简单配置如下 日志的输出级别以及输出目的地 这里是配置输出到控制台与日志文件 log4j常用的优先级FATAL gt ERROR gt WARN gt INF
  • MATLAB----模糊聚类

    data 1739 94 1675 15 2395 96 3 373 3 3087 05 2429 47 4 1756 77 1652 1514 98 3 864 45 1647 31 2665 9 1 222 85 3059 54 200
  • ReactHooks常用钩子总结

    ReactHooks常用钩子 重学react 这里对常用的ReactHooks做一下简单的总结 1 useState useState 会返回一个数组 一个 state 一个更新 state 的函数 在初始化渲染期间 返回的状态 state
  • 左值与右值

    目录 一 类型 二 地址 三 举例 一 类型 1 均与类型无关 int型 float型等等 均有 二 地址 1 右值不可取地址 引用的右值 程序没有分配内存区域 无地址 2 左值可以取地址 地址为所引用的实例 变量 对象的地址 int ma
  • Qt利用setStyleSheet设置样式

    Qt中设置按钮或QWidget的外观是 可以使用QT Style Sheets来进行设置 非常方便 可以用setStyleSheet font bold font size 20px color rgb 241 70 62 backgrou
  • [总结]怎么给VM(虚拟机)添加新磁盘

    1 关闭系统 点击VM gt Settings弹出的Add Hardware Wizard对话框 点击Add gt Hard Disk gt Next gt Create a new virtual disk gt SCSI Recomme
  • 软件设计 基础篇(二) 交互设计

    软件设计 基础篇 系列文章目录 软件设计 基础篇 二 交互设计 文章目录 软件设计 基础篇 系列文章目录 1 软件设计 1 1 设计简介 1 2 设计基础 2 交互设计 2 1 交互概述 2 2 交互起源 总结 1 软件设计 软件设计是从软
  • Shell函数调用

    文章目录 一 函数基本格式 二 函数调用 2 1函数中调用 2 2函数调用函数 2 3外部调用 2 4案例 三 总结 在shell脚本中 有些命令或者某些操作需要频繁的使用时 每次都重新写太过繁琐 这时我们就可以使用函数 当需要使用时 直接
  • 2022年开始学习Delphi并成为Delphi程序员的5个重要原因

    Delphi 是编程界的传奇语言之一 是软件开发历史的基石 随着新平台和框架的出现 新语言脱颖而出 但 Delphi 因其作为跨平台本机原生开发工具的可靠性和有效性而在IT界发展趋势来来去去的大潮中站稳了脚跟 更重要的是 随着时间的推移 D
  • 用python做一个随机点名程序(不重复点名)

    用python做一个简单的随机点名程序 不重复点名 这是我来到csdn的第一篇文章 内容如果有瑕疵的地方或者代码可以进一步改善 请大家对我指点一二 谢谢 废话不多说 上代码 import random 导入随机模块 import pytts
  • centos7 新磁盘挂载扩展到根目录

    摘要 局域网 ESXi 上搭建的 gitlab 代码仓库不能使用 查看了一下是 dev mapper centos root 路径存储满了 这里记录一下把新增磁盘挂载到根目录下的过程 在为 liunx 虚拟机扩充磁盘的时候 只能是在关机状态
  • 【对比Java学Kotlin】扩展

    扩展概述 以 JDK 内置的集合 ArrayList 为例 如果我们想给其添加一个能力 交互两个元素 swap index1 index2 我们应该怎么做 常见的有如下方式 工具类 静态方法 以 ArrayList 和要交换的两个 inde
  • node的child_process的execSync方法调用cmd报错

    const execSync require child process execSync node modules bin babel plugins router generator src out dir plugins router
  • 医院数据防泄露保护系统解决方案

    医院数据防泄露保护系统解决方案 目 录 1 医院需求分析 2 解决方案综述 2 1 产品部署示意图 3 产品优势功能 3 1 数据库审计与风险控制系统 3 1 1 事前安全风险评估 3 1 2 实时统方行为监控 3 1 3 双向审计让统方更
  • 95-36-030-ChannelHandler-ChannelInboundHandler

    文章目录 1 概述 2 继承体系 3 ChannelInboundHandler 4 ChannelInboundHandlerAdapter 1 概述 ChannelInboundHandler处理入站事件 以及用户自定义事件 2 继承体
  • SpringCloud Gateway API接口加解密

    接口范围 所有GET请求 白名单除外 body 体 是 application json 和 application json utf8 的 POST请求 白名单除外 POST url传参也支持 白名单除外 启用禁用 版本 后端提供独立接口
  • java程序内存占用过高的问题定位分析方法

    线上应用的故障排查能力是一个高级软件开发人员的必修课 也最能体现一个技术人员的工作经验和能力 从事Java开发 经常会碰到下面两种异常 1 java lang OutOfMemoryError PermGen space 2 java la
  • python下使用unrar出现错误的问题

    首先说一下我的系统和Python版本信息 win7 python2 7 12 我找了两篇个人认为比较好的文章 第一篇http blog csdn NET luoye7422 article details 41873499 按照他的方法来确
  • OpenFeign基础应用以及Sentinel整合OpenFeign使用

    OpenFeign基础应用 概念 OpenFeign是一种声明式 模板化的HTTP客户端 在Spring Cloud中使用OpenFeign 可以做到使用HTTP请求访问远程服务 就像调用本地方法一样的 开发者完全感知不到这是在调用远程方法
  • Spring 学习笔记03 - AOP

    目录 AOP 概述 AOP 是什么 AOP 相关术语 基于 XML 配置 AOP 简单实现 AOP 的配置步骤 基于注解配置 AOP 需要用到的新注解 简单实现 使用注解配置 AOP 的 bug AOP 概述 AOP 是什么 AOP Asp