Spring AOP的实现方式与原理

news/2024/5/17 13:48:53

目录

认识IOC与AOP

AOP的实现方式

@Aspect注解实现AOP

自定义注解实现AOP

Spring AOP原理

代理模式

静态代理和动态代理

JDK动态代理

CGLIB动态代理

Spring AOP实现的哪种代理


认识IOC与AOP

IOC又称为控制反转,也就是控制权发生了反转.在传统的程序中,我们是需要自己来手动创建对象的,但是在Spring实现IOC思想后,就可以直接交给Spring来管理创建.所以控制反转就是将对对象的创建销毁权利交给SPring来处理,我们就不需要关心. 举个例子: 原来,我们需要在A类中调用B类的方法,传统的方式就是我们直接在A中new出B类的对象,然后再调用B类的方法.这里虽然可以实现效果.但是会存在一个很大的问题:如果你的需求发生了变化,你就需要对源代码进行修改,这样不仅是导致代码工作量巨大,还容易发生错误.这种代码属于高耦合.但当实现IOC思想后,我们创建B对象就交给了Spring来管理.在Spring中,B对象被看成一个Bean对象. Bean对象都由spring来创建和管理..这样的话,我们需要获取对象,就从主动new变成了被动等Spring来创建. 从主动变成被动,这就可以理解为控制反转.这样就可以大大降低代码的耦合性. 所以IOC也就是依赖类不由程序猿实例化,而是通过Spring容器来帮助我们new好指定的实例,且将实例注入到需要的类中.

而AOP称为面向切面编程,这里的切面就是特指一些特定的问题.向我们的拦截器,统一结果返回,统一异常处理,这也是AOP思想的一种体现.简单来说,AOP就是对一类事情的集中处理.AOP可以说是对OOP的补充. 面向对象就是将食物的特性和行为抽象成一个对象,将它们的特征和行为封装成一个类,统一调用. 且面向切面就是将其中特定的问题给提取出来,等需要用的时候才切入.就比如有一个people类,它们都有身高,体重,年龄等属性和吃饭,睡觉等行为.但是生病去医院看病这个行为是只有一部分人才会发生的.AOP就是将看病这个行为给提取出来,然后等到需要这个功能的时候再给切入到需要的方法中.这样就可以减少系统的重复代码和模块之间降低耦合度.

AOP的实现方式

Spring AOP有四种实现方式,第一种是通过@Aspect注解来实现的,第二种则是通过自定义注解来实现的,第三种是通过SPring的API来实现的(也就是xml的方式). 第四种就是基于动态代理来实现的,可以说上面是三种都是基于动态代理在实现的.

@Aspect注解实现AOP

@Aspect表示一个切面类,而和它配合使用的还有多个注解:

通知类型:

@Around: 环绕通知, 此注解标注的通知⽅法在⽬标⽅法前, 后都被执⾏   
@Before: 前置通知, 此注解标注的通知⽅法在⽬标⽅法前被执⾏
@After: 后置通知, 此注解标注的通知⽅法在⽬标⽅法后被执⾏, ⽆论是否有异常都会执⾏
@AfterReturning: 返回后通知, 此注解标注的通知⽅法在⽬标⽅法后被执⾏, 有异常不会执⾏
@AfterThrowing: 异常后通知, 此注解标注的通知⽅法发⽣异常后执⾏
@Slf4j
@Aspect
public class TimeAspect {@Around("execution(* com.example.demo.controller.*.*(..))")public Object recordTime(ProceedingJoinPoint joinPoint) {long start = System.currentTimeMillis();Object result;try {result = joinPoint.proceed();} catch (Throwable e) {throw new RuntimeException(e);}log.info(joinPoint.getSignature() + "执行时间: " + (System.currentTimeMillis()-start)+ "ms");return result;}
}

自定义注解实现AOP

这里是使用@annotatoin注解来实现的.我们还需要先实现一个自定义注解再将自定义注解放到我们需要增强的方法上.

自定义注解:

package com.example.demo.aspect;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAspect {
}

 切面类:

@Aspect
@Slf4j
public class AspectDemo1 {@Before("@annotation(com.example.demo.aspect.MyAspect)")public void before1() {log.info("before1方法执行");}@After("@annotation(com.example.demo.aspect.MyAspect)")public void after1() {log.info("after1方法执行");}
}

需要增强的方法:

@RestController
@RequestMapping("/test")
@Slf4j
public class TestController {@MyAspect@RequestMapping("/t1")public void t1() {log.info("t1方法执行");}@MyAspect@RequestMapping("/t2")public void t2() {int a = 10 / 0;log.info("t2方法执行");}
}

Spring AOP原理

我们的Spring AOP是基于动态代理来实现的,这里我们分两部分来进行说明.

代理模式

代理模式就是为其他对象提供一种代理来控制对这个对象的访问. 它的作用就是通过提供一个类,让我们在调用目标方法的是,不再是直接调用,而是通过这个代理类来间接调用.因为再一些情况先,不适合直接引用另一个对象. 代理模式就好比我们的房屋中介一样: 房屋出租时,房东就将房屋授权给中介,由中介来代理看房,房屋咨询等.

静态代理和动态代理

我们可以根据代理的创建时期,代理分为静态代理和动态代理.

静态代理就是有程序猿来创建代理类,再对其进行编译,在程序运行前代理类就是class文件存在了.

动态代理则是在程序运行的时候,运用反射机制来创建.相比于静态代理来说,动态代理更加的灵活.我们不需要针对每一个目标对象单独创建一个代理对象,而是将这个工作推到程序运行再让JVM来执行.

Java中也对动态代理进行了实现,常见的动态代理有JDK动态代理和CGLIB动态代理.

JDK动态代理

它的实现步骤主要是:

1. 定义一个接口和它的实现类.

2.自定义一个类来实现InvocationHandler接口,且重写invoke方法,在invoke方法中我们会调用目标方法且自定义一些逻辑.

3. 通过proxy.newProxyInstance(ClassLoder loader, Class<?>[] interfaces, InvocationHandler h) 方法来创建.

实现InvocationHandler接口:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class JDKInvocationHandler implements InvocationHandler {//⽬标对象即就是被代理对象private Object target;public JDKInvocationHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws
Throwable {// 代理增强内容System.out.println("我是中介, 开始代理");//通过反射调⽤被代理类的⽅法Object retVal = method.invoke(target, args);//代理增强内容System.out.println("我是中介, 代理结束");return retVal;}
}

创建一个代理对象并使用:

public class DynamicMain {public static void main(String[] args) {HouseSubject target= new RealHouseSubject();//创建⼀个代理类:通过被代理类、被代理实现的接⼝、⽅法调⽤处理器来创建HouseSubject proxy = (HouseSubject) Proxy.newProxyInstance(target.getClass().getClassLoader(),new Class[]{HouseSubject.class},new JDKInvocationHandler(target));proxy.rentHouse();}
}

通过这里,我们知道JDK动态代理在使用Proxy类的newProxyInstance方式创建代理类,它方法的第二个参数是interfaces,也就是被代理类实现的一些接口(这里就决定了JDK动态代理只能实现接口的类)

CGLIB动态代理

JDK 动态代理有⼀个最致命的问题是其只能代理实现了接⼝的类. 有些场景下, 我们的业务代码是直接实现的, 并没有接⼝定义. 为了解决这个问题, 我们可以⽤ CGLIB 动态代理机制来解决    

CGLIB实现步骤:

1. 定义一个被代理类

 2. 自定义MethodInterceptor,且重写intercept方法,intercept方式用来增强目标方法,和JDK的invoke方法类似.

2. 通过Enhancer类的create()来创建代理类.

自定义一个类实现MethodInerceptor接口:

import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CGLIBInterceptor implements MethodInterceptor {//⽬标对象, 即被代理对象private Object target;public CGLIBInterceptor(Object target){this.target = target;}@Overridepublic Object intercept(Object o, Method method, Object[] objects, 
MethodProxy methodProxy) throws Throwable {// 代理增强内容System.out.println("我是中介, 开始代理");//通过反射调⽤被代理类的⽅法Object retVal = methodProxy.invoke(target, objects);//代理增强内容System.out.println("我是中介, 代理结束");return retVal;}
}

创建代理类,并使用:

public class DynamicMain {public static void main(String[] args) {HouseSubject target= new RealHouseSubject();HouseSubject proxy= (HouseSubject) 
Enhancer.create(target.getClass(),new CGLIBInterceptor(target));proxy.rentHouse();}
}

这里Enhancer.create()生成对象的方法参数:

type: 被代理类的类型

callback: 自定义的被代理类MethodInterceptor

因为这里被代理类的类型参数为任意参数,所以CGLIB可以代理任意类.

Spring AOP实现的哪种代理

Spring Framework 和Spring Boot它们底层实现的都是JDK和CGLib动态代理. 但是它们不同之处在于:

Spring Framework如果代理的目标类是实现了接口的类,那就是使用JDK,如果代理的是没有实现接口的类,则会使用CGlib(而是,这里的实现的接口里面至少有一个自定义的方法,不然还是会CGLIB代理,因为这里就算我们将proxyTargetClass配置为了false,经过一些判断还是会为true)

Spring boot2.0 之后的版本默认配置的使用CGlib代理.代理的类无论是实现了接口还是没有实现都用CGlib代理,如果需要使用JDK代理,则需要去配置. 而Spring boot2.0之前的版本和Spring Framework一样.

它们之所以不同,就是因为代理工厂里面proxyTargetClass属性的默认配置不同. 如果为false 则实现了接口的类使用JDK,没有则使用CGLIB.如果为true,不管实现没实现都使用CGLIB.  也就是因为SpringFramework和Springboot2.0之前,ProxyTargetClass默认是false,而Springboot2.0之后默认是true.如果需要使用其他的代理,我们修改proxyTargetClass配置即可.



http://www.mrgr.cn/p/21303384

相关文章

设计模式——迭代器模式15

迭代器模式提供一种方法访问一个容器对象中各个元素&#xff0c;而又不需暴露该对象的内部细节。 设计模式&#xff0c;一定要敲代码理解 抽象迭代器 /*** 迭代抽象* */ public interface Iterator<A> {A next();boolean hasNext(); }迭代器实现 /*** author ggbond*…

数据加密技术在数据安全中的作用

随着信息技术的飞速发展,数据已成为现代社会最宝贵的资产之一。然而,数据的快速增长也带来了安全风险,包括数据泄露、篡改和滥用等。数据加密技术作为保护数据安全的重要手段,其重要性日益凸显。PrimiHub一款由密码学专家团队打造的开源隐私计算平台,专注于分享数据安全、…

生态短讯 | Tapdata 与 TDengine 完成产品兼容性互认证,打造物联网实时数据生态

近日,深圳钛铂数据有限公司自主研发的钛铂实时数据平台与 TDengine完成相互兼容性测试,兼容性良好,系统功能正常,运行稳定。近月,深圳钛铂数据有限公司(以下简称钛铂数据)自主研发的实时数据平台(Tapdata Live Data Platform)与北京涛思数据科技有限公司(以下简称涛思…

YOLTV8 — 大尺度图像目标检测框架(欢迎star)

YOLTV8 — 大尺度图像目标检测框架【ABCnutter/YOLTV8: &#x1f680;】 针对大尺度图像&#xff08;如遥感影像、大尺度工业检测图像等&#xff09;&#xff0c;由于设备的限制&#xff0c;无法利用图像直接进行模型训练。将图像裁剪至小尺度进行训练&#xff0c;再将训练结果…

Qt 6.5.5 链接和QML与C++交互的若干问题

需求描述 Qt Quick开发桌面组件,使用讯飞API(提供头文件、静态库、动态库),希望部署到Windows平台,在Qt Creator开发。 QML与C++交互 主要参考:QML与CPP,https://blog.csdn.net/gongjianbo1992/article/details/87965925 另有参考:信号与槽,https://blog.csdn.net/ife…

VK3602K SOP8抗干扰2键/2路/2按键/2通道触摸感应芯片,应用于加湿器触摸IC等大小家电产品

产品品牌:永嘉微电/VINKA 产品型号:VK3602K 封装形式:SOP8 概述 VK3602K具有2个触摸按键,可用来检测外部触摸按键上人手的触摸动作。该芯片具有较高的集成度,仅需极少的外部组件便可实现触摸按键的检测。 提供了2路直接输出功能,可通过IO脚选择输出电平。芯片内部采用特殊…

【运维自动化-配置平台】如何创建业务及拓扑(集群-模块)

业务&#xff0c;是蓝鲸 CD 体系中比较重要的概念和维度&#xff0c;日常使用中主机、进程、业务拓扑的管理都需要依赖已经存在的业务&#xff0c;其他蓝鲸体系产品也基本上都是围绕业务的维度来提供对应的服务和相关的鉴权。1、创建业务/业务集 请确保有创建业务的权限&#…

BGE M3-Embedding 模型介绍

BGE M3-Embedding是BAAI开源的embedding模型,支持多语言,多粒度,多功能检索,本文介绍模型的相关信息BGE M3-Embedding来自BAAI和中国科学技术大学,是BAAI开源的模型。相关论文在https://arxiv.org/abs/2402.03216,论文提出了一种新的embedding模型,称为M3-Embedding,它…

redis自学(37)集群伸缩

集群伸缩 添加一个节点到集群: Redis-cli--cluster提供了很多操作集群的命令,可以通过下面方式查看:比如,添加节点的命令先输入新增的ip和端口号,后输入集群已经有的ip和端口号好指定添加到哪个集群。默认是增加master节点,加上--cluster-slave是变成了slave。--cluster-…

LLM-大模型演化分支树、GPT派发展阶段及训练流程图、Infini-Transformer说明

大模型是怎么演进的&#xff1f; Encoder Only: 对应粉色分支&#xff0c;即BERT派&#xff0c;典型模型&#xff1a; BERT 自编码模型&#xff08;Autoencoder Model&#xff09;&#xff1a;通过重建句子来进行预训练&#xff0c;通常用于理解任务&#xff0c;如文本分类和阅…

winform入门篇 第13章 菜单栏

菜单栏 本章内容 菜单栏 工具栏 右键菜单 重点是右键菜单的实现。 菜单栏 MenuStrip&#xff0c;支持可视化编辑 添加 MenuStrip 添加菜单、菜单项、分隔线给菜单项设置属性 —Name 字段名&#xff0c;Text 文本显示,Image:图标 给菜单项添加事件处理(双击即可) 1.添加菜单…

如何在HTML中使用JavaScript:从基础到高级的全面指南!

JavaScript是一种轻量级的编程语言,通常用于网页开发,以增强用户界面的交互性和动态性。然而在HTML中,有多种方法可以嵌入和使用JavaScript代码。本文就带大家深入了解如何在HTML中使用JavaScript。 一、使用 script 标签 要在HTML中使用JavaScript,我们需要使用<script…

蓝桥杯 2019 省A 糖果 动态规划/二进制

#include <bits/stdc.h> // 包含标准库中的所有头文件 using namespace std;int main() {int n,m,k; // 定义变量n&#xff08;糖果包数&#xff09;、m&#xff08;口味数&#xff09;、k&#xff08;每包糖果的个数&#xff09;cin>>n>>m>>k; // 输入…

高效便捷!解锁阿里云跨账号专线互联的全新实施方案

作者&#xff1a;小丫、琉璃 背景 为持续提升金融云环境的合规标准以及可用区内产品服务的性能和稳定性&#xff0c;阿里云将对杭州地域BCD三个金融云可用区进行基础设施架构升级与改造&#xff0c;对应可用区云产品将于 2024 年后停止服务&#xff0c;需要将业务迁移到新可用…

HAL STM32 I2C方式读取MT6701磁编码器获取角度例程

HAL STM32 I2C方式读取MT6701磁编码器获取角度例程 &#x1f4cd;相关篇《Arduino通过I2C驱动MT6701磁编码器并读取角度数据》&#x1f388;《STM32 软件I2C方式读取MT6701磁编码器获取角度例程》&#x1f4cc;MT6701当前最新文档资料&#xff1a;https://www.magntek.com.cn/u…

react 的拖动面板SplitPane的使用

1、我刚开始,是准备使用npm install react-split-pane 来引入的。 但是引入的过程报错了npm ERR! code ERESOLVE npm ERR! ERESOLVE unable to resolve dependency tree npm ERR! npm ERR! While resolving: active-workspace@6.0.7 npm ERR! Found: react@18.2.0 npm ERR…

2024年更新迭代最快的宿主软件FL Studio 24.0.99.4094中文激活版

FL Studio 24.0.99.4094中文激活版是我见过更新迭代最快的宿主软件&#xff0c;没有之一。FL Studio12、FL Studio20、FL Studio21、FL Studio24等等。有时甚至我刚刚下载好了最新版本&#xff0c;熟悉了新版本一些好用的操作&#xff0c;Fl Studio就又推出了更新的版本&#x…

Visual Studio调试C/C++指南

1. 前言 Visual Studio&#xff08;VS&#xff09;是微软开发的一款集成开发环境(IDE)软件&#xff0c;支持C/C、C#、VB、Python等开发语言&#xff0c;开发桌面、Web等应用程序。VS功能极其强大&#xff0c;使用极其便利&#xff0c;用户数量最多&#xff0c;被誉为"宇宙…

陇剑杯 省赛 攻击者1 CTF wireshark 流量分析

陇剑杯 省赛 攻击者1 题目 链接&#xff1a;https://pan.baidu.com/s/1KSSXOVNPC5hu_Mf60uKM2A?pwdhaek 提取码&#xff1a;haek ├───LogAnalize │ ├───linux简单日志分析 │ │ linux-log_2.zip │ │ │ ├───misc日志分析 │ │ acce…

7-01. 创建主菜单 UI

创建 Menu Canvas创建 Panel添加底图添加标题、版本、按钮如果希望图片周围没有黑框,需要把图片的 Read/Write 改成透明再添加一个说明 Panel再添加一个开始 Panel创建 MenuUI将三个 Panel 拖动到 MenuUI 上 对于 Panel 里面的每个 Button,调用 SwitchPanel 解决点击按钮中间…