【Spring】之AOP详解

AOP

什么是AOP?

AOP:Aspect Oriented Programming,面向切面编程。

切面指的是某一类特定问题,因此面向切面编程也可以理解为面向特定方法编程。例如,在任何一个系统中,总有一些页面不是用户可以随便访问的,这就要对客户端发过来的请求进行检验,检验用户是否登录,这个检验用户登录的方法就是一个特定方法。此时,就可以用户AOP的思想来解决问题。

AOP是一种思想,表示对某一类事物的集中处理。

实现一个AOP 

引入依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>

实现AOP

下述代码实现的AOP解决的问题是:对每一个接口的耗时时间使用日志来打印。这个方法的作用是对每个接口进行测试,判断哪个接口需要进行优化,因此相对来说在后台还是比较重要。

@Aspect // 表示这是一个切面类
@Slf4j // 打印日志
@Component // 将此Bean对象装配到IoC容器中
public class TimeAspect {/*** 打印每个接口耗时的日志* @param joinPoint 表示作用的目标方法* @return*/@Around("execution(* com.example.demo.controller.*.*(..))")// 表示作用方式和作用域,即AOP在哪个环节起作用并且对哪些方式起作用public Object timeCost(ProceedingJoinPoint joinPoint) throws Throwable {long start = System.currentTimeMillis();Object result = joinPoint.proceed(); // 执行目标方法long end = System.currentTimeMillis();log.info(joinPoint.getSignature() + "消耗时间" + (end - start) + "ms");return result;}}

 AOP核心概念

切点

PointCut,切点,也称之为切入点。

切点的任务就是提供一组规则,告诉程序哪些方法需要被用来进行功能的增强。

例如在上述程序中的下述代码,就是一个切点:

定义切点

在上述AOP的小demo中,切点是和通知在一起进行书写的。如果在一个程序中,有很多的AOP,这些AOP作用的方法都相同,我们就可以把切点单独定义出来,这样可以减少代码的冗余。

@Pointcut("execution(* controller.*.*())")
public void pointCut() {}

 对于定义好的切点来说,如果通知也是在本类中的话,那么可以直接写成 @Before("切点()"),例如上述切点,通知可以写成@Before("pointCut()")。如果定义的切点并不是本类的话,就需要写成@Before("类的全限定名称 + 切点()")。

如果一个方法有多个被切面时,默认是按照类名来进行排序,不过有@Order注解可以用来进行排序。@Order中的数字越小,前置通知的时间越早,后置通知的时间越迟。

 切点表达式

1. execution表达式:根据方法的签名来匹配

execution(<访问修饰符> <返回类型> <包名.类名.方法.方法参数> <异常>)

 可以看到。访问修饰符和异常是可以不写的。

2. @annotation表达式:根据自定义注解的方式进行匹配。

execution表达式的方式适合匹配一些有规则的方法,如果我们需要进行匹配规则的点在某几个包下的某几个类的某几个方法,那么这种规则就不再使用。相对来说,这种使用自定义注解的方式更为适用,也就是对于每个需要匹配的方法,我们在其上面加一个注解即可。

实现步骤:①自定义注解②使用@annotation表达式描述切点③在连接点的方法上添加自定义注解。

自定义注解的实现:

/*** 自定义注解*/@Target(ElementType.METHOD) // 元注解,表示注解作用的方法
@Retention(RetentionPolicy.RUNTIME) // 元注解,表示注解的声明周期
public @interface MyAnnotation {}

使用@annotation表达式描述切点,同时为了测试@Order注解的作用,定义了顺序。

@Component // 表示要注入IoC容器中
@Slf4j // 表示打印日志
@Aspect // 表示这是一个切面类
@Order(1) // 定义顺序
public class MyAspect {/*** 定义切点*/@Pointcut("@annotation(com.example.demo.aop.MyAnnotation)")public void pointcut() {}/*** 定义前置通知*/@Before("pointcut()")public void before() {log.info("执行前置通知");}/*** 定义后置通知*/@After("pointcut()")public void after() {log.info("执行后置通知");}/*** 定义环绕通知* @param joinPoint 表示目标方法* @return* @throws Throwable*/@Around("pointcut()")public Object around(ProceedingJoinPoint joinPoint) throws Throwable {log.info("执行环绕前通知");Object result = joinPoint.proceed();log.info("执行环绕后通知");return result;}}

使用@annotation表达式描述切点,同时为了测试@Order注解的作用,定义了顺序。

@Component // 表示要注入IoC容器中
@Slf4j // 表示打印日志
@Aspect // 表示这是一个切面类
@Order(2) // 定义顺序
public class HisAspect {/*** 定义前置通知* 由于使用的是其他类定义的切点,因此要用全限定名称 + 类型*/@Before("com.example.demo.aop.pointcut()")public void before() {log.info("执行前置通知");}/*** 定义后置通知* 由于使用的是其他类定义的切点,因此要用全限定名称 + 类型*/@After("com.example.demo.aop.pointcut()")public void after() {log.info("执行后置通知");}/*** 定义环绕通知* 由于使用的是其他类定义的切点,因此要用全限定名称 + 类型* @param joinPoint 表示目标方法* @return* @throws Throwable*/@Around("com.example.demo.aop.pointcut()")public Object around(ProceedingJoinPoint joinPoint) throws Throwable {log.info("执行环绕前通知");Object result = joinPoint.proceed();log.info("执行环绕后通知");return result;}}

启动类,用来测试结果 

@Controller
@Slf4j
public class TestController {@RequestMapping("/hi")@MyAnnotationpublic void hi() {log.info("目标方法");}}

连接点

满足切点规则的点,就是连接点。

例如在上述切面中,三层架构中controller层的所有方法都会被切面切入。

通知

通知就是在AOP中具体执行的业务逻辑,然后程序猿将其抽象成一个方法。

在上述的AOP中,这个方法就是一个通知。

public Object timeCost(ProceedingJoinPoint joinPoint) throws Throwable {long start = System.currentTimeMillis();Object result = joinPoint.proceed(); // 执行目标方法long end = System.currentTimeMillis();log.info(joinPoint.getSignature() + "消耗时间" + (end - start) + "ms");return result;}

前置通知(@Before):表示通知在目标方法执行前被执行。

后置通知(@After):表示通知在目标方法执行后被执行。

环绕通知(@Around):表示通知再目标方法执行前后被执行。

返回后通知(@AfterReturning):表示通知在目标方法执行后被执行,如果有异常就不会执行。

异常后通知(@AfterThrowing): 表示通知在目标方法异常后被执行。

如果一个方法有多个通知,那么通知的执行顺序就是:

正常情况下:环绕前通知 → 前置通知 → 返回后通知 → 后置通知 → 环绕后通知;

异常情况下:环绕前通知 → 前置通知 → 异常后通知 → 后置通知。

切面

切面 = 切点 + 通知。

通过切面我们可以知道,AOP在哪些方法中执行什么样的业务逻辑。

代理模式

SpringAOP是基于动态代理实现的。

代理的目的,其实就是对目标方法进行功能增强。

定义

代理,就是为目标对象提供一种代理以控制对这个对象的访问。它的作用就是通过一个代理类,让我们在调用目标方法的时候,不是直接对目标方法进行调用,而是通过代理类间接调用。

在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

使用代理前:

使用代理后:

 

 主要角色

业务接口类(Subject):表示目标对象要做什么事,可以是抽象类或接口。

业务实现类(RealSubject):表示目标对象具体做的业务逻辑,也就是被代理对象要做的业务。

代理类(Proxy):当来调用目标对象时,通过访问代理类来实现。

静态代理

由程序员创建代理类或使用特定工具自动生成源代码再对其进行编译,程序运行前代理类的.class文件就以及存在,并且已经确定好要代理的对象。

以房东和中介为例。房东属于目标对象,中介属于代理对象。房东有租房子和卖房子两项业务,但是对于一个中介来说,他在开启业务之前,只跟房东确定好了租房子的业务,而卖房子的并没有确定好。因此,当有客户时,客户想买,但是中介代理的只有租,不能卖。这就类似于静态代理,当代理对象在程序运行前,就确定好要代理什么内容,程序开始后,代理对象只能代理程序运行前确定好的,对于没有确定的,它就不能代理。

HouseSubject接口
// 业务接口类public interface HouseSubject {/*** 出租房屋*/void rentHouse();/*** 出售房子*/void saleHouse();}

 RealHouseSubject类

// 目标对象、被代理对象public class RealHouseSubject implements HouseSubject{/*** 出租房屋*/@Overridepublic void rentHouse() {System.out.println("我是房东,我要出租房子");}/*** 出售房子*/@Overridepublic void saleHouse() {System.out.println("我是房东,我要出售房子");}}

 HouseProxy类

// 代理类public class HouseProxy implements HouseSubject{// 目标对象private HouseSubject houseSubject;public HouseProxy(HouseSubject houseSubject) {this.houseSubject = houseSubject;}@Overridepublic void rentHouse() {System.out.println("开始进行代理");houseSubject.rentHouse();System.out.println("结束代理");}}

 客户端来访,使用代理对象

public class Main {public static void main(String[] args) {HouseSubject subject = new RealHouseSubject();HouseProxy houseProxy = new HouseProxy(subject);houseProxy.rentHouse();}}

在上述代码中不难看出, 代理类在程序运行前就确定好代理什么目标对象,代理目标对象的什么业务。当程序开始后,客户端来进行交易,代理类就只能代理确定好的目标对象的业务,其他一概不能操作。

动态代理

相比于静态代理来说,动态代理较为灵活。

在动态代理中,不需要为每一个目标对象都单独创建一个代理对象,而是把这个创建代理对象的任务放到程序运行时有JVM来实现,也就是说动态代理在程序运行时,根据需要动态创建。

仍然以房东和中介举例。房东属于目标对象,中介属于代理对象。房东只需要把要干啥给出,而此时中介并不需要是哪个房东,要干哪个事。当客户来了之后,诉说自己的想要什么,中介再根据具体要干的活去找哪个房东有这个东西。假如说,客户要买房,那中介就找哪个房东要卖房;客户要长租,中介就找哪个房子会长租。这就类似于动态代理,代理对象并不知道会来什么业务,也不知道目标对象有什么业务,只要客户来了,根据需要去创建一个具体的代理,然后执行业务。

动态代理分为:JDK动态代理和CGLIB动态代理。

JDK动态代理

1. 定义一个接口以及实现类,也就是静态代理中的HouseSubject、RealHouseSubject。

2. 定义一个代理类,实现InvocationHandler接口,并重写invoke方法,在invoke方法中我们会调用目标方法并实现一些业务逻辑。

3. 通过Proxy.newProxyInstance(ClassLoader loader, Class<?>[ ] interfaces, InvocationHandler h)方法创建代理对象。

HouseSubject接口
// 业务接口类public interface HouseSubject {/*** 出租房屋*/void rentHouse();/*** 出售房子*/void saleHouse();}
RealHouseSubject类
// 目标对象、被代理对象public class RealHouseSubject implements HouseSubject{/*** 出租房屋*/@Overridepublic void rentHouse() {System.out.println("我是房东,我要出租房子");}/*** 出售房子*/@Overridepublic void saleHouse() {System.out.println("我是房东,我要出售房子");}}
JDKInvocation类,即Proxy类
/*** JDK动态代理的实现*/public class JDKInvocation implements InvocationHandler {// 目标对象private Object target;public JDKInvocation(Object target) {this.target = target;}/**** @param proxy 代理对象** @param method 代理对象需要实现的方法,即其中需要重写的方法** @param args method所对应方法的参数** @return** @throws Throwable*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("开始代理");Object result = method.invoke(target, args); // 通过反射来实现的System.out.println("结束代理");return result;}}
客户端来访,创建代理对象
public class Main {public static void main(String[] args) {HouseSubject subject = new RealHouseSubject();HouseSubject proxy = (HouseSubject) Proxy.newProxyInstance(subject.getClass().getClassLoader(),// 类加载器,用于加载代理对象new Class[]{HouseSubject.class},// 被代理类实现的一些接口,同时也决定了JDK只能实现接口new JDKInvocation(subject));// 实现了InvocationHandler接口的对象proxy.saleHouse();}}

 CGLIB动态代理

1. 定义一个接口以及实现类,也就是静态代理中的HouseSubject、RealHouseSubject。

2. 自定义MethodInterceptor并重写intercept方法,interceptor方法就相当于中介干活,即代理对象,用来增强目标方法,和JDK动态代理中的invoke方法类似。

3. 当有客户端来访时,通过Enhancer类的create()创建真正代理对象。

引入依赖
<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version>
</dependency>
HouseSubject接口
// 业务接口类public interface HouseSubject {/*** 出租房屋*/void rentHouse();/*** 出售房子*/void saleHouse();}
RealHouseSubject类
// 目标对象、被代理对象public class RealHouseSubject implements HouseSubject{/*** 出租房屋*/@Overridepublic void rentHouse() {System.out.println("我是房东,我要出租房子");}/*** 出售房子*/@Overridepublic void saleHouse() {System.out.println("我是房东,我要出售房子");}}
CGLibInterceptor类,即Proxy类
/*** CGLib动态代理的实现* 类似于JDK的invoke方法*/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 result = method.invoke(target, objects);System.out.println("结束代理");return result;}}
客户端来访,创建代理对象
public class Main {public static void main(String[] args) {HouseSubject subject = new RealHouseSubject();HouseSubject proxy = (HouseSubject) Enhancer.create(subject.getClass(),// 被代理的类或接口的类型new CGLibInterceptor(subject));// 自定义方法拦截器MethodInterceptorproxy.saleHouse();}}

对于JDK动态代理和CGLIB动态代理两者,JDK动态代理只能代理接口,而CGLIB动态代理可以代理类和接口。 

不管是JDK动态代理还是CGLIB动态代理,都可以明显的发现,对于代理对象来说,都不知道要代理的内容是什么,当运行时,来了什么内容,要对哪个目标对象做业务,那么代理对象就代理哪个目标对象。

静态代理和动态代理进行对比之后,就可以发现动态代理的高效性,不用针对每一个目标对象都去构建一个代理对象。

源码分析

SpringAOP是基于动态代理实现的,而动态代理又有JDK和CGLIB两种方法进行实现。SpringAOP则是两种方法结合使用。

如果是代理接口,JDK和CGLIB都可以使用;如果是代理类,那么只有JDK可以使用。在SpringBoot2.x之后,默认使用CGLIB代理。当然,如果在配置文件中设置spring.aop.proxy-target-class=false,那就变成了JDK代理,但是如果目标对象是类的话,那么还是CGLIB动态代理。

 对于AOP的介绍就到这里了,AOP是Spring两大主要内容之一了(另一个是IoC),因此非常重要,不论是日常工作中,还是八股文中,都是一个比较重要的内容。在文章中,简单介绍了一下AOP的内容,及背后的设计模式,还有SpringAOP的源码,接下来会对Spring实现的一些功能进行简单介绍,例如统一格式返回,拦截器,统一异常以及权限管理中后端利用AOP思想来实现拦截URL的过程。

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

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

相关文章

部分背包问题

题源看着是背包&#xff0c;其实是贪心 题目描述 阿里巴巴走进了装满宝藏的藏宝洞。藏宝洞里面有 (N≤100) 堆金币&#xff0c;第 i 堆金币的总重量和总价值分别是mi​,vi​(1≤mi​,vi​≤100)。阿里巴巴有一个承重量为T(T≤1000) 的背包&#xff0c;但并不一定有办法将全部的…

Matlab|配电网三相不平衡潮流计算【隐式Zbus高斯法】【可设定变压器数量、位置、绕组方式】

目录 主要内容 部分代码 结果一览 1.以33节点为例 2.以12节点系统为例 下载链接 主要内容 该模型基于隐式Zbus高斯法实现对配电网的三相不平衡潮流计算&#xff0c;通过选项可实现【不含变压器】和【含变压器】两种方式下的潮流计算&#xff0c;并且通过参数设置…

题目:学习gotoxy()与clrscr()函数

题目&#xff1a;学习gotoxy()与clrscr()函数    There is no nutrition in the blog content. After reading it, you will not only suffer from malnutrition, but also impotence. The blog content is all parallel goods. Those who are worried about being cheated …

游戏引擎中的声音系统

一、声音基础 1.1 音量 声音振幅的大小 压强p&#xff1a;由声音引起的与环境大气压的局部偏差 1.2 音调 1.3 音色 1.4 降噪 1.5 人的听觉范围 1.6 电子音乐 将自然界中连续的音乐转换成离散的信号记录到内存中 采样 - 量化 - 编码 香农定理&#xff1a;采样频率是信…

如何查询网站是否被搜索引擎收录

怎么看网站有没有被百度收录 对于网站所有者来说&#xff0c;了解自己的网站是否被百度搜索引擎收录是非常重要的。只有被收录&#xff0c;网站才能在百度搜索结果中展现&#xff0c;从而获取流量和曝光。下面介绍几种方法&#xff0c;让您快速了解自己的网站是否被百度收录。…

SpringBoot+uniApp宠物领养小程序系统 附带详细运行指导视频

文章目录 一、项目演示二、项目介绍三、运行截图四、主要代码1.保存宠物信息代码2.提交订单信息代码3.查询评论信息代码 一、项目演示 项目演示地址&#xff1a; 视频地址 二、项目介绍 项目描述&#xff1a;这是一个基于SpringBootuniApp框架开发的宠物领养微信小程序系统。…

Elasticsearch的倒排索引是什么?

文章目录 什么是ES&#xff1f;什么是倒排索引&#xff1f;为什么叫做倒排索引&#xff1f;分词器的使用 什么是ES&#xff1f; Elasticsearch是基于 Apache Lucene【lusen】的搜索引擎&#xff0c;支持Restful API风格【可以使用常见的HTTP请求来访问】&#xff0c;并且搜索速…

在js中本地存储的数组如何转成对象

一、此方法仅适用一维数组&#xff1b; 二、效果图 使用后 三、js代码。 function gong(s){console.log(s);let data;let kk1;// 检查ask_id是否不为空 if (s.ask_id null ) { kk1}else{kk2let dd;dds.data;sessionStorage.setItem(wenda,JSON.stringify(dd[0]))window.l…

1、jvm基础知(一)

什么是JVM&#xff1f; 1、定义&#xff1a;JVM 指的是Java虚拟机&#xff08; Java Virtual Machine &#xff09;。JVM 本质上是一个运行在计算机上的程序&#xff0c; 他的职责是运行Java字节码文件&#xff0c;Java虚拟机上可以运行Java、Kotlin、Scala、Groovy等语言 任…

如何使用极狐GitLab Maven 仓库?

本文作者&#xff1a;徐晓伟 GitLab 是一个全球知名的一体化 DevOps 平台&#xff0c;很多人都通过私有化部署 GitLab 来进行源代码托管。极狐GitLab 是 GitLab 在中国的发行版&#xff0c;专门为中国程序员服务。可以一键式部署极狐GitLab。 本文主要讲述了如何使用极狐GitLa…

VSCode - 离线安装扩展python插件教程

1&#xff0c;下载插件 &#xff08;1&#xff09;首先使用浏览器打开 VSCode 插件市场link &#xff08;2&#xff09;进入插件主页&#xff0c;点击右侧的 Download Extension 链接&#xff0c;将离线安装包下载下来&#xff08;文件后缀为 .vsix&#xff09; 2&#xff0c;…

学透Spring Boot 003 —— Spring 和 Spring Boot 常用注解(附面试题和思维导图)

这是 学透 Spring Boot 专栏 的第三篇&#xff0c;欢迎关注我&#xff0c;与我一起学习和探讨 Spring Boot 相关知识&#xff0c;学透 Spring Boot。 从面试题说起 今天我们通过一道和Spring Boot有关的常见面试题入手。 面试题&#xff1a;说说 Spring Boot 中有哪些常用注解…

13.Python图形用户界面

我们之前的程序运行结果都被输出到命令提示符&#xff08;终端&#xff09;窗口&#xff0c;界 面比较简陋。本章讲解如何将其输出到图形界面。 1 Python中的图形用户界面开发库 注Qt是一个跨平台的C应用程序开发框架&#xff0c;被广泛用于开发GUI 程序&#xff0c;也可用于开…

OSPF协议详解

静态缺点 1、中大型复杂网络----配置量大 2、不能实时收敛 动态-----可以实时收敛 IGP----内部网关路由协议 RIP OSPF EIGRP ISIS EGP----外部网关路由协议 BGP IGP &#xff08;选路佳 占用资源 收敛快&#xff09;----一个协议好需满足这三个 距离矢量 DV RIP…

node.js的模块化 与 CommonJS规范

一、node.js的模块化 (1)什么是模块化&#xff1f; 将一个复杂的程序文件依据一定的规则拆分成为多个文件的过程就是模块化 在node.js中&#xff0c;模块化是指把一个大文件拆分成独立并且相互依赖的多个小模块&#xff0c;将每个js文件被认为单独的一个模块&#xff1b;模块…

HDLbits 刷题 -- Alwaysblock2

学习&#xff1a; For hardware synthesis, there are two types of always blocks that are relevant: Combinational: always (*)Clocked: always (posedge clk) Clocked always blocks create a blob of combinational logic just like combinational always blocks, but…

基于ArgoCD和Testkube打造GitOps驱动的Kubernetes测试环境

本文介绍了一项新工具&#xff0c;可以基于Gitops手动或者自动实现Kubernetes集群应用测试&#xff0c;确保集群的健康状态与Git仓库定义的一致。原文: GitOps-Powered Kubernetes Testing Machine: ArgoCD Testkube 简介&#xff1a;GitOps 云原生测试面临的挑战 现代云原生应…

[蓝桥杯 2014 省 A] 波动数列

容我菜菲说一句&#xff0c;全网前排题解都是rubbish&#xff0c;当然洛谷某些也是litter 不好意思&#xff0c;最近背单词背了很多垃圾的英文&#xff0c;正题开始 [蓝桥杯 2014 省 A] 波动数列 题目描述 输入格式 输入的第一行包含四个整数 n , s , a , b n,s,a,b n,s,a…

iPhone设备中定位应用程序崩溃问题的日志分析技巧

​ 目录 如何在iPhone设备中查看崩溃日志 摘要 引言 导致iPhone设备崩溃的主要原因是什么&#xff1f; 使用克魔助手查看iPhone设备中的崩溃日志 奔溃日志分析 总结 摘要 本文介绍了如何在iPhone设备中查看崩溃日志&#xff0c;以便调查崩溃的原因。我们将展示三种不同的…

使用python完成excel文件的合并,并完成简单的数据统计

Python脚本实现了以下功能&#xff1a; 合并多个Excel文件&#xff1a;脚本遍历当前目录下的所有.xlsx文件&#xff0c;读取每个文件的内容并合并到一个大的DataFrame中&#xff0c;然后将合并后的数据写入到名为combined_data.xlsx的新文件中。 统计指定列的重复值&#xff1…