【Spring】Spring AOP 初识及实现原理解析

  • 博主简介:想进大厂的打工人
  • 博主主页:@xyk:
  • 所属专栏: JavaEE进阶 

目录

文章目录

一、初识AOP

1.1 什么是AOP?

1.2 AOP的组成

1.2.1 切面(Aspect)

1.2.2 切点(Pointcut)

1.2.3 连接点(Join Point)

1.2.4 通知(Advice)

1.3 AOP的使用场景

二、Srping AOP 实现

2.1 添加Spring AOP 依赖

2.2 定义切面和切点

2.3 定义通知

三、Spring AOP 实现原理

3.1 什么是动态代理?

3.2 JDK 动态代理实现

3.3 CGLIB 动态代理实现

3.4 JDK 和 CGLIB 实现的区别


一、初识AOP

1.1 什么是AOP?

AOP(Aspect Oriented Programming):面向切面编程,它是⼀种思想,它是对某⼀类事情的
集中处理。
在我们想要对某一件事情进行集中处理,就可以使用到AOP,它提供一种将程序中的横切关注点模块化的方式。在 AOP 中,我们将这些横切关注点称为“切面”,它们独立于业务逻辑模块,但是可以在程序运行的不同阶段被织入到业务逻辑中。

简单来说,AOP 就是对某一件事进行集中处理的思想方式~

1.2 AOP的组成

1.2.1 切面(Aspect)

切⾯(Aspect)由切点(Pointcut)和通知(Advice)组成,它既包含了横切逻辑的定义,也包
括了连接点的定义。相当于处理某方面具体问题的一个类,包含多个方法,而这些方法就是切点和通知。

1.2.2 切点(Pointcut)

Pointcut 的作⽤就是提供⼀组规则来匹配连接点(Join Point),给满足规则的连接点添加通知(Advice),可以理解为用来进行主动拦截的规则(配置)

1.2.3 连接点(Join Point)

应⽤执⾏过程中能够插⼊切⾯的⼀个点,连接点可以理解为可能会触发AOP规则的所有点。(所有请求)

1.2.4 通知(Advice)

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

Spring 切⾯类中,可以在方法上使⽤以下注解,会设置⽅法为通知方法,在满⾜条件后会通知本
⽅法进⾏调⽤:

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

1.3 AOP的使用场景

在做任何一个系统都需要登录功能,那么几乎想要使用这个系统都需要我们进行验证用户登录状态,我们之前的处理⽅式是每个 Controller 都要写⼀遍⽤户登录验证,然⽽当你的功能越来越多,那么你要写的登录验证也越来越多,⽽这些⽅法⼜是相同的,这么多的⽅法就会代码修改和维护的成本。对于这种功能统⼀,且使⽤的地⽅较多的功能,就可以考虑 AOP来统⼀处理了。

 

除了统一登录判断外,使用AOP还可以实现:

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

二、Srping AOP 实现

Spring AOP 的实现步骤如下:

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

2.1 添加Spring AOP 依赖

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

2.2 定义切面和切点

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

@Component // 随着框架的启动而启动
@Aspect // 告诉框架我是一个切面类
public class UserAspect {// 定义切点(配置拦截规则)@Pointcut("execution(* com.example.demo.controller.UserController.*(..))")public void pointcut(){}
}

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

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

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

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

  • 匹配特定类的所有方法:
  • execution(* com.example.MyClass.*(..)):匹配 com.example.MyClass 类中的所有方法。
  • 匹配特定包下的所有方法:
  • execution(* com.example.*.*(..)):匹配 com.example 包及其子包下的所有方法。
  • 匹配特定方法名的方法:
  • 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 方法。
package com.example.demo.controller;import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequestMapping("/user")
public class UserController {@RequestMapping("/hi")public String sayHi(String name){System.out.println("执行了Hi");return "Hi," + name;}@RequestMapping("/hello")public String sayHello(){System.out.println("执行了Hello");return "Hello,world";}
}

2.3 定义通知

通知定义的是被拦截方法具体要执行的业务。我们上面列出了可以使用哪些通知~这里举出例子

package com.example.demo.aop;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;@Component // 随着框架的启动而启动
@Aspect // 告诉框架我是一个切面类
public class UserAspect {// 定义切点(配置拦截规则)@Pointcut("execution(* com.example.demo.controller.UserController.*(..))")public void pointcut(){}@Before("pointcut()")public void beforeAdvice(){System.out.println("执行了前置通知~");}@After("pointcut()")public void AfterAdvice(){System.out.println("执行了后置通知~");}/*** 环绕通知* @param joinPoint* @return*/@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;}}

环绕通知是在前置通知之前和后置通知之后运行的~

  

三、Spring AOP 实现原理

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

  1. 默认情况下,实现了接⼝的类,使⽤ AOP 会基于 JDK ⽣成代理类
  2. 没有实现接⼝的类,会基于 CGLIB ⽣成代理类
     

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 来创建代理类。

import org.example.demo.service.AliPayService;
import org.example.demo.service.PayService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.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 动态代理实现

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.example.demo.service.AliPayService;
import org.example.demo.service.PayService;
import java.lang.reflect.Method;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 和 CGLIB 实现的区别

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

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

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

相关文章

React入门学习笔记2

jsx语法规则 定义虚拟DOM时&#xff0c;不要写引号。标签中混入JS表达式时要用{ }。样式的类名指定不要用class&#xff0c;要用className。内联样式&#xff0c;要用style{{key&#xff1a;value}}的形式去写。只有一个根标签标签必须闭合标签首字母 )若小写字母开头&#xf…

无涯教程-Lua - 环境安装

在Windows上安装 为Windows环境开发了一个单独的名为" SciTE"的IDE,可以从https://code.google.com/p/luaforwindows/下载部分。 运行下载的可执行文件以安装Lua IDE。 由于它是一个IDE&#xff0c;因此您可以使用它来创建和构建Lua代码。 如果您有兴趣在命令行模…

MongoDB文档-进阶使用-MongoDB索引-createindex()与dropindex()-在MongoDB中使用正则表达式来查找

阿丹&#xff1a; 之前研究了MongoDB的基础增删改查。在学会基础的数据库增删改查肯定是不够的。这个时候就涉及到了数据库搜索的时候的效率。需要提高数据的搜索效率。 MongoDB索引 在所以数据库中如果没有数据索引的时候。如果需要查找到一些数据。都会去主动扫描所有可能存…

AP2400 LED照明电源驱动 DC-DC降压恒流IC 过EMC线路图 PCB线路图 车灯摩灯

产品特点 宽输入电压范围&#xff1a;5V&#xff5e;100V 可设定电流范围&#xff1a;10mA&#xff5e;6000mA 固定工作频率&#xff1a;150KHZ 内置抖频电路&#xff0c;降低对其他设备的 EMI干扰 平均电流模式采样&#xff0c;恒流精度更高 0-100%占空比控制&#xff0…

flutter-GridView使用

先看效果 代码实现 import package:app/common/util/k_log_util.dart; import package:app/gen/assets.gen.dart; import package:app/pages/widget/top_appbar.dart; import package:flutter/cupertino.dart; import package:flutter/material.dart; import package:flutter_…

最新2024届【海康威视】内推码【GTK3B6】

最新2024届【海康威视】内推码【GTK3B6】 【内推码使用方法】 1.请学弟学妹们登录校招官网&#xff0c;选择岗位投递简历&#xff1b; 2.投递过程中填写内推码完成内推步骤&#xff0c;即可获得内推特权。 内推码&#xff1a;GTK3B6 内推码&#xff1a;GTK3B6 内推码&…

需求飙升120%!芭比产品火爆出圈,意大利人争相购买!

据外媒报道&#xff0c;真人版《芭比》成为今年夏天最火的电影&#xff0c;仅在美国和加拿大&#xff0c;该影片的票房收入就超过3.5亿美元。在意大利《芭比》也备受追捧&#xff0c;目前的票房收入突破1670万欧元&#xff0c;成为2023年观看人数第三多的电影。 除了电影界之外…

2023奇安信天眼设备--面试题

1.在天眼分析平台网络协议中sip、dip、sport、dport字段表示的含义是什么&#xff1f; sip 源IP、dip 目的IP、sport 源端口、dport 目的端口 2.在天眼分析平台DNS协议中dns type字段表示的含义是? dns type表示DNS请求类型 0代表DNS请求&#xff0c;1代表DNS响应 3.dns_typ…

java实现钉钉群机器人@机器人获取信息后,机器人回复(机器人接收消息)

1.需求 鉴于需要使用钉钉群机器人回复&#xff0c;人们提出的问题&#xff0c;需要识别提出的问题中的关键词&#xff0c;后端进行处理实现对应的业务逻辑 2.实现方式 用户群机器人&#xff0c;附带提出的问题&#xff0c;后端接收消息后识别消息内容&#xff0c;读取到关键…

【0805作业】Linux中 AB终端通过两根有名管道进行通信聊天

作业一&#xff1a;打开两个终端&#xff0c;要求实现AB进程对话【两根管道】 打开两个终端&#xff0c;要求实现AB进程对话 A进程先发送一句话给B进程&#xff0c;B进程接收后打印B进程再回复一句话给A进程&#xff0c;A进程接收后打印重复1.2步骤&#xff0c;当收到quit后&am…

【TypeScript】初识TypeScript和变量类型介绍

TypeScript 1&#xff0c;TypeScript是什么?2&#xff0c;类型的缺失带来的影响3&#xff0c;Ts搭建环境-本博主有专门的文章专说明这个4&#xff0c;使用tsc对ts文件进行编译5&#xff0c;TS运行初体验简化Ts运行步骤解决方案1解决方案2&#xff08;常见&#xff09; 开始学习…

【数据结构|二叉树遍历】递归与非递归实现前序遍历、中序遍历、后序遍历

递归与非递归实现二叉树的前序遍历、中序遍历、后序遍历。 二叉树图 定义 前序遍历&#xff08;Preorder Traversal&#xff09;&#xff1a; 前序遍历的顺序是先访问根节点&#xff0c;然后按照先左后右的顺序访问子节点。对于上面的二叉树&#xff0c;前序遍历的结果是&…