【Spring】Spring AOP入门及实现原理剖析

文章目录

  • 1 初探Aop
    • 1.1 何为AOP?
    • 1.2 AOP的组成
      • 1.2.1 切面(Aspect)
      • 1.2.2 连接点(Join Point)
      • 1.2.3 切点(Pointcut)
      • 1.2.4 通知(Advice)
    • 1.3 AOP的使用场景
  • 2 Spring AOP入门
    • 2.1 添加 Spring AOP 框架⽀持
    • 2.2 定义切面和切点
    • 2.3 定义相关通知
  • 3 Spring AOP实现原理
    • 3.1 何为动态代理?
    • 3.2 JDK 动态代理实现
    • 3.3 CGLIB 动态代理实现
    • 3.4 两种方式的区别
  • 写在最后


1 初探Aop

1.1 何为AOP?

AOP (Aspect-Oriented Programming) 是一种编程范式,它提供一种将程序中的横切关注点模块化的方式。横切关注点可以是日志、事务、安全等,它们不属于业务逻辑,但是又必须要与业务逻辑紧密耦合在一起。在 AOP 中,我们将这些横切关注点称为“切面”,它们独立于业务逻辑模块,但是可以在程序运行的不同阶段被织入到业务逻辑中。使用 AOP 可以提高代码复用性、降低模块之间的耦合度、简化代码的维护性等。

1.2 AOP的组成

AOP由切面、切点、连接点和通知组成。

1.2.1 切面(Aspect)

切面是包含了通知、切点和切面的类,相当于AOP实现的某个功能的集合。通俗理解,在程序中就是一个处理某方面具体问题的一个类。里面包含了许多方法,这些方法就是切点和通知。

1.2.2 连接点(Join Point)

应⽤执⾏过程中能够插⼊切⾯的⼀个点,这个点可以是⽅法调⽤时,抛出异常时,甚⾄修改字段时。切⾯代码可以利⽤这些点插⼊到应⽤的正常流程之中,并添加新的⾏为。连接点可以理解为可能会触发AOP规则的所有点。 狭义可以理解为需要进行功能增强的方法。

1.2.3 切点(Pointcut)

切点是连接点的集合。它定义了在哪些连接点上应用特定的通知。通过使用切点表达式,可以根据连接点的特征(例如方法签名或类名)选择特定的连接点。即,切点是用来进行主动拦截的规则(配置)。
具体来说:Pointcut 的作⽤就是提供⼀组规则(使⽤ AspectJ pointcut expression language 来描述)来匹配 Join Point,给满⾜规则的 Join Point 添加 Advice。

1.2.4 通知(Advice)

在AOP术语中,切面的工作被称之为通知。 通知是切面在连接点上执行的动作。它定义了在何时(例如在方法调用之前或之后)以及如何(例如打印日志或进行性能监控)应用切面的行为。即,程序中被拦截请求触发的具体动作。

1.3 AOP的使用场景

回顾下笔者之前的文章,基于Servlet实现的前后端分离的博客系统中,除了登录等⼏个功能不需要做⽤户登录验证之外,其他⼏乎所有⻚⾯调⽤的前端控制器( Controller)都需要先验证⽤户登录的状态。然⽽,当系统的功能越来越多,则要写的登录验证也越来越多,一旦某些功能需要改动,这种处理方式由于耦合性很高,牵一发就会动全身。⽽这些⽅法⼜是相同的,对于这种功能统⼀,且使⽤的地⽅较多的功能,就可以考虑 AOP来统⼀处理了。
例如,原本的博客系统在作者删除、发布、浏览博客前都需要进行登录状态的验证,如果用户未登录,则请求重定向到登录界面。使用AOP后,在用户调用Server服务之前,统一进行校验,验证通过则正常服务,否则,被“拦截”。
AOP登录拦截
除了统一登录判断外,使用AOP还可以实现:

  • 统⼀⽇志记录
  • 统⼀⽅法执⾏时间统计
  • 统⼀的返回格式设置
  • 统⼀的异常处理
  • 事务的开启和提交等

2 Spring AOP入门

以上,我们已经对AOP有了基本的了解。接下来,我们的目标是尝试使用Spring AOP来实现AOP的功能,完成的目标如下:

拦截所有StudentController里的方法,即每次调用StudentController中的任意方法的时候,执行相应的通知事件。

Spring AOP 的实现步骤如下:

  1. 添加 Spring AOP 框架⽀持。
  2. 定义切⾯和切点。
    (1)创建切面类
    (2)配置拦截规则
  3. 定义通知。

2.1 添加 Spring AOP 框架⽀持

首先,创建Spring Boot项目
Spring Boot项目
在pom.xml中添加Spring AOP的依赖配置:

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2.2 定义切面和切点

使用 @Aspect 注解表明当前类为一个切面,而在切点中,我们要定义拦截的规则,具体实现如下:

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;@Aspect // 表明此类为一个切面
@Component // 随着框架的启动而启动
public class StudentAspect {// 定义切点, 这里使用 Aspect 表达式语法@Pointcut("execution(* com.hxh.demo.controller.StudentController.*(..))")public void pointcut(){ }
}

在上述实现代码中,pointcut 为一个空方法,只是起到一个“标识”的作用,即,标识下面的通知方法具体指的是哪个切点,切点可以有多个。

切点表达式由切点函数组成,其中 execution() 是最常⽤的切点函数,⽤来匹配⽅法,语法为:

execution(<修饰符><返回类型><包.类.⽅法(参数)><异常>)
修饰符和异常可以省略

*常见的切点表达式的示例:

  • 匹配特定类的所有方法:
    execution(* com.example.MyClass.*(..)):匹配 com.example.MyClass 类中的所有方法。
  • 匹配特定包下的所有方法:
    execution(* com.example.*.*(..)):匹配 com.example 包及其子包下的所有方法。
  • 匹配特定注解标注的方法:
    execution(@com.example.MyAnnotation * *(..)):匹配被 com.example.MyAnnotation 注解标注的所有方法。
  • 匹配特定方法名的方法:
    execution(* com.example.MyClass.myMethod(..)):匹配 com.example.MyClass 类中名为 myMethod 的方法。
  • 匹配特定方法参数类型的方法:
    execution(* com.example.MyClass.myMethod(String, int)):匹配 com.example.MyClass 类中具有一个 String 参数和一个 int 参数的 myMethod 方法。
  • 匹配特定返回类型的方法:
    execution(String com.example.MyClass.myMethod(..)):匹配 com.example.MyClass 类中返回类型为 String 的 myMethod 方法。

StudentController.java

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequestMapping("/student")
public class StudentController {@RequestMapping("/hi")public String sayHi(String name) {System.out.println("执行了 sayHi 方法~");return "Hi," + name;}@RequestMapping("/hello")public String sayHello() {System.out.println("执行了 sayHello 方法~");return "Hello, hxh";}
}

2.3 定义相关通知

通知定义的是被拦截方法具体要执行的业务。
Spring 切⾯类中,可以在⽅法上使⽤以下注解,会设置⽅法为通知⽅法,在满⾜条件后会通知本⽅法进⾏调⽤:

  • 前置通知使⽤ @Before:通知⽅法会在⽬标⽅法调⽤之前执⾏。
  • 后置通知使⽤ @After:通知⽅法会在⽬标⽅法返回或者抛出异常后调⽤。
  • 返回之后通知使⽤ @AfterReturning:通知⽅法会在⽬标⽅法返回后调⽤。
  • 抛异常后通知使⽤ @AfterThrowing:通知⽅法会在⽬标⽅法抛出异常后调⽤。
  • 环绕通知使⽤ @Around:通知包裹了被通知的⽅法,在被通知的⽅法调用之前和调⽤之后执⾏⾃定义的⾏为。

具体实现如下:

前置通知与后置通知(异常通知和返回后通知仅仅是注解不同,方式一致,这里不再赘述~)

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;@Aspect // 表明此类为一个切面
@Component // 随着框架的启动而启动
public class StudentAspect {// 定义切点, 这里使用 Aspect 表达式语法@Pointcut("execution(* com.hxh.demo.controller.StudentController.*(..))")public void pointcut(){ }// 前置通知@Before("pointcut()")public void beforeAdvice() {System.out.println("执行了前置通知~");}// 后置通知@After("pointcut()")public void afterAdvice() {System.out.println("执行了后置通知~");}
}

实现结果

环绕通知的具体实现
环绕通知是有Object返回值的,需要把执行流程的结果返回给框架,框架拿到对象继续执行。

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;@Aspect // 表明此类为一个切面
@Component // 随着框架的启动而启动
public class StudentAspect {// 定义切点, 这里使用 Aspect 表达式语法@Pointcut("execution(* com.hxh.demo.controller.StudentController.*(..))")public void pointcut(){ }// 环绕通知@Around("pointcut()")public Object aroundAdvice(ProceedingJoinPoint joinPoint) {System.out.println("进入环绕通知~");Object obj = null;// 执行目标方法try {obj = joinPoint.proceed();} catch (Throwable e) {e.printStackTrace();}System.out.println("退出环绕通知~");return obj;}}

实现结果


3 Spring AOP实现原理

Spring AOP 是通过动态代理的⽅式,在运⾏期将 AOP 代码织⼊到程序中的,它的实现⽅式有两种:JDK ProxyCGLIB。因此,Spring 对 AOP 的支持局限于方法级别的拦截。

  • CGLIB是Java中的动态代理框架,主要作⽤就是根据⽬标类和⽅法,动态⽣成代理类。
  • Java中的动态代理框架,⼏乎都是依赖字节码框架(如 ASM,Javassist 等)实现的。
  • 字节码框架是直接操作 class 字节码的框架。可以加载已有的class字节码⽂件信息,修改部分信息,或动态⽣成⼀个 class。

3.1 何为动态代理?

动态代理(Dynamic Proxy)是一种设计模式,它允许 在运行时创建代理对象,并将方法调用转发给实际的对象。 动态代理可以用于实现横切关注点(如日志记录、性能监控、事务管理等)的功能,而无需修改原始对象的代码。
在Java中,动态代理通常使用java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口来实现。

以下是使用动态代理的一般步骤:

  1. 创建一个实现InvocationHandler接口的类,该类将作为代理对象的调用处理程序。在InvocationHandler接口的invoke方法中,可以定义在方法调用前后执行的逻辑。

  2. 使用Proxy类的newProxyInstance方法创建代理对象。该方法接受三个参数:类加载器、代理接口数组和调用处理程序。它将返回一个实现指定接口的代理对象。

  3. 使用代理对象调用方法。当调用代理对象的方法时,实际上会调用调用处理程序的invoke方法,并将方法调用转发给实际的对象。

动态代理

3.2 JDK 动态代理实现

先通过实现 InvocationHandler 接⼝创建⽅法调⽤处理器,再通过 Proxy 来创建代理类。

// 动态代理:使⽤JDK提供的api(InvocationHandler、Proxy实现),此种⽅式实现,要求被代理类必须实现接⼝
public class PayServiceJDKInvocationHandler implements InvocationHandler {// ⽬标对象即就是被代理对象private Object target;public PayServiceJDKInvocationHandler(Object target) {this.target = target;}// proxy代理对象@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 1.安全检查System.out.println("安全检查");// 2.记录⽇志System.out.println("记录⽇志");// 3.时间统计开始System.out.println("记录开始时间");// 通过反射调⽤被代理类的⽅法Object retVal = method.invoke(target, args);//4.时间统计结束System.out.println("记录结束时间");return retVal;}public static void main(String[] args) {PayService target= new AliPayService();// ⽅法调⽤处理器InvocationHandler handler =new PayServiceJDKInvocationHandler(target);// 创建⼀个代理类:通过被代理类、被代理实现的接⼝、⽅法调⽤处理器来创建PayService proxy = (PayService) Proxy.newProxyInstance(target.getClass().getClassLoader(),new Class[]{PayService.class},handler);proxy.pay();}
}

3.3 CGLIB 动态代理实现

public class PayServiceCGLIBInterceptor implements MethodInterceptor {// 被代理对象private Object target;public PayServiceCGLIBInterceptor(Object target) {this.target = target;}@Overridepublic Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {// 1.安全检查System.out.println("安全检查");// 2.记录⽇志System.out.println("记录⽇志");// 3.时间统计开始System.out.println("记录开始时间");// 通过cglib的代理⽅法调⽤Object retVal = methodProxy.invoke(target, args);// 4.时间统计结束System.out.println("记录结束时间");return retVal;}public static void main(String[] args) {PayService target= new AliPayService();PayService proxy= (PayService) Enhancer.create(target.getClass(),new PayServiceCGLIBInterceptor(target));proxy.pay();}
}

3.4 两种方式的区别

  • JDK 实现,要求被代理类必须实现接⼝, 之后是通过 InvocationHandler 及 Proxy,在运⾏时动态的在内存中⽣成了代理类对象,该代理对象是通过实现同样的接⼝实现(类似静态代理接⼝实现的⽅式),只是该代理类是在运⾏期时,动态的织⼊统⼀的业务逻辑字节码来完成。
  • CGLIB 实现,被代理类可以不实现接⼝, 是通过继承被代理类,在运⾏时动态的⽣成代理类对象。

写在最后

本文被 JavaEE编程之路 收录点击订阅专栏 , 持续更新中。
 以上便是本文的全部内容啦!创作不易,如果你有任何问题,欢迎私信,感谢您的支持!

在这里插入图片描述

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

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

相关文章

Fofa搜索技巧(理论加实践的整理)

目录 题记技巧&#xff08;我一般找国内的&#xff0c;所以下边一直加cn&#xff09;1、搜索HTTP响应头中含有"thinkphp"关键词的网站和IP。2、加上标题带有后台的。3、加上时间&#xff0c;现在新网站有thinkphp日志泄露的有很多。4、搜索html正文中含有"管理…

http-server 的安装与使用

文章目录 问题背景http-server简介安装nodejs安装http-server开启http服务http-server参数 问题背景 打开一个文档默认使用file协议打开&#xff0c;不能发送ajax请求&#xff0c;只能使用http协议才能请求资源&#xff0c;所以此时我们需要在本地建立一个http服务&#xff0c…

list最常用的遍历五种方式以及使用场景

目录 遍历方式的适用场景对比 迭代器遍历 列表迭代器 增强for遍历 Lambda表达式 lambda表达式简介 普通for遍历 集合中通用的并且常用的六种方法 遍历方式的适用场景对比 迭代器遍历 &#xff1a;在遍历过程中需要删除元素&#xff0c;请使用迭代器 列表迭代器&#xff1…

Segment Tree 线段树算法(java)

线段树算法 Segment Tree 线段树算法代码演示 蓄水池算法 Segment Tree 线段树算法 什么是线段树算法&#xff1a; 线段树&#xff08;Segment Tree&#xff09;是一种基于树结构的数据结构&#xff0c;用于解决区间查询问题&#xff0c;例如区间最大值、最小值、区间和等。线段…

在阿里云linux上安装MySql数据库

我们先远程连接服务器 然后输入 sudo yum update重新运行一下 然后 sudo yum install mysql-server安装 mysql 服务 其中有两次 y n 选择 都选y就好了 然后 运行 sudo service mysqld start启动MySql 然后 我们查看一下MySql sudo service mysqld status

git rebase 合并提交

一. 合并提交步骤 git log --oneline 查看当前提交记录 git rebase -i HEAD~2 选择最后提交的2条记录进行合并进入编辑界面,将c865404的pick改为f, 表示向前合并也就是向cc5a54合并 编辑完之后:wq 保存并退出git rebase --continuegit push --force origin feature/v1.2 推送…

opencv -11 图像运算之按位逻辑运算(图像融合图像修复和去除)

按位逻辑运算是一种对图像进行像素级别的逻辑操作的方法&#xff0c;使用OpenCV的按位逻辑运算函数可以对图像进行位与&#xff08;AND&#xff09;、位或&#xff08;OR&#xff09;、位非&#xff08;NOT&#xff09;和位异或&#xff08;XOR&#xff09;等操作。 通俗点就是…

docker-compose搭建prometheus+grafana+钉钉告警

前言&#xff1a; 本文将介绍使用docker-compose部署搭建promtheus监控容器、主机、服务等相关状态&#xff1b; 配合granfana面板构建监控大屏&#xff1b; 由于grafana的报警不是很友好&#xff0c;使用dingtalk&#xff0c;配合altermanager&#xff0c;实现钉钉报警。 …

认识Spring(1)

hi,大家好,今天继续为大家带来Spring的相关内容 文章目录 &#x1f9c1;1.理解Spring和IOC&#x1f9c1;2.DI和DF&#x1f378;2.1什么是DI&#x1f378;2.2什么是DF&#x1f378;2.3DI和DF的区别 &#x1f9c1;3 Spring创建和使用&#x1f378;3.1创建Spring项目&#x1f361…

【机器人模拟-02】 模拟移动机器人设置里程计

一、说明 在本教程中,我将向您展示如何设置移动机器人的测程。本教程是“机器人模拟”指南中的第二个教程。测量位移是仿真中的重要内容,设置测程的官方教程在此页面上,但我将逐步引导您完成整个过程。 您可以在此处获取此项目的完整代码。让我们开始吧! 二、ROS 2 中的里程…

密码学学习笔记(十五):ECDSA - 椭圆曲线数字签名算法

椭圆曲线数字签名算法是DSA的一种椭圆曲线变体&#xff0c;它发明的初衷只是避免使用Schnorr签名的专利。椭圆曲线数字签名算法依赖于验证器中的私钥和主机用于验证验证器的公钥。它的缺点和DSA一样&#xff0c;它也没有提供安全性证明。 椭圆曲线算法 DSS&#xff08;数字签…

torch分布式通信基础

torch分布式通信基础 1. 点到点通信2. 集群通信 官网文档&#xff1a;WRITING DISTRIBUTED APPLICATIONS WITH PYTORCH 1. 点到点通信 # 同步&#xff0c;peer-2-peer数据传递 import os import torch import torch.distributed as dist import torch.multiprocessing as mpdef…

php 开发微信 h5 支付 APIv3 接入超详细流程

✨ 目录 &#x1f388; 申请商户号&#x1f388; 申请商户证书&#x1f388; 设置V3密钥&#x1f388; 开通H5支付&#x1f388; 设置支付域名&#x1f388; SDK 下载&#x1f388; 第一次下载平台证书&#x1f388;非第一次下载平台证书&#x1f388; H5下单 &#x1f388; 申…

【前端知识】React 基础巩固(二十八)——StrictMode

React 基础巩固(二十八)——StrictMode StrictMode StrictMode 是一个用来突出显示应用程序中潜在问题的工具 与 Fragment 一样&#xff0c;StrictMode 不会渲染任何可见的 UI为后代出发额外的检测和警告严格模式检查仅在开发模式下运行&#xff0c;不影响生产构建 严格模式检…

【DBA课程-笔记】第 3 章:MongoDB数据库核心知识

内容 一、MongoDB 数据库架构 A. MongoDB数据库体系架构 1. 存储引擎&#xff08;MongoDB Storage Engines&#xff09;&#xff1a; 2. MongoDB 数据逻辑架构 二、MongoDB 存储引擎 A. 查看mongodb服务器的状态 B. 查看引擎信息&#xff08;4.2.1 没有这个命令&#xf…

搭载下一代人工智能技术,微软推出Power Automate流程挖掘产品

在近日的Microsoft Inspire大会中&#xff0c;微软揭晓了他们即将推出的Power Automate流程挖掘产品&#xff0c;并计划在8月1日正式对外开放。 试用地址&#xff1a;https://powerautomate.microsoft.com/zh-cn/#home-signup 这款产品搭载了下一代人工智能技术&#xff0c;有…

基于 Fedora 38 的预期版本 Nobara 38 发布

导读基于 Fedora 38 的预期版本 Nobara 38 终于发布了&#xff0c;它带来了一系列用户友好的修复和功能增强。Nobara 是 Fedora Linux 的修改版本&#xff0c;旨在解决用户面临的常见问题&#xff0c;并提供开箱即用的顺滑的游戏、流媒体和内容创建体验。凭借一系列附加软件包和…

深度学习ai学习方向如何规划,算法竞赛,机器学习,搭建环境等答疑

目录 1了解人工智能的背景知识 2 补充数学或编程知识 3 熟悉机器学习工具库 4 系统的学习人工智能 5 动手去做一些AI应用 1了解人工智能的背景知识 一些虽然存在但是在研究或者工业上不常用的知识&#xff0c;为自己腾出更多的时间来去学习&#xff0c;研究。 人工智能里…

天翎MyApps低代码平台唯品会金牌客服管理系统

项目痛点&#xff1a; 作为一家知名的创新大型电商&#xff0c;唯品会秉承“传承品质生活&#xff0c;提升幸福体验”的企业使命。基于客服铁军锻造项目&#xff0c;实现基于金牌案例的提交、评审、积分&#xff0c;学习功能。 项目中的晋升机制、案例产生学习机制、双激励机制…

linux 基于debian_ubuntu AB系统适配(三)- overlayroot

Overlayroot Overlayroot是一个实用工具,允许您创建一个只读的根文件系统和一个可写的覆盖文件系统。这对于创建一个更安全和稳定的系统很有用,因为对系统所做的任何更改都将存储在覆盖文件系统中,可以很容易地丢弃或重置。 在Debian下,分离的系统在/userdata/rootfs_ove…