【23.12.30期--Spring篇】Spring的AOP介绍(详解)

在这里插入图片描述

Spring的AOP介绍

  • ✔️简述
  • ✔️扩展知识
    • ✔️AOP是如何实现的


✔️简述


AOP(Aspect-Oriented Programming),即面向切面编程,用人话说就是把公共的逻辑抽出来,让开发者可以更专注于业务逻辑开发。


和IOC-样,AOP也指的是一种思想。AOP思想是OOP (Obiect-Oriented Programming) 的补充OOP是面向类和对象的,但是AOP则是面向不同切面的。一个切面可以横跨多个类和对象去操作,极大的丰富了开发者的使用方式,提高了开发效率。


譬如,一个订单的创建,可能需要以下步骤:


1.权限校验
2.事务管理
3.创建订单
4.日志打印


如果使用AOP思想,我们就可以把这四步当成四个“切面”,让业务人员专注开发第三个切面,其他二个切面则是基础的通用逻辑,统一交给AOP封装和管理。


Spring AOP有如下概念(列举下,不用刻意记):


术语翻译释义
Aspect切面切面由切入点和通知组成,它既包含了横切逻辑的定义,也包括了切入点的定义。切面是一个横切关注点的模块化,一个切面能够包含同一个类型的不同增强方法,比如如说事务处理和日志处理可以理解为两人切面。
PointCut切入点切入点是对连接点进行拦截的条件定义,决定通知应该作用于截哪些方法。 (充当where角色,即在哪里做)
Advice通知通知定义了通过切入点拦截后,应该在连接点做什么,是切面的具体行为。 (充当what角色,即做什么)
Target目标对象目标对象指将要被增强的对象,即包含主业务逻辑的类对象。或者说是被一个或者多个切面所通知的对象。
JoinPoint连接点连接点是程序在运行时的执行点,这个点可以是正在执行的方法,或者是正在抛出的异常。因为Spring只支持方法类型的连接点,所以在Spring中连接点就是运行时刻被拦截到的方法。连接点由两个信息确定: 1 . 方法(表示程序执行点,即在哪个目标方法) 2 . 相对点(表示方位,即目标方法的什么位置,比如调用前,后等)
Weaving织入织入是将切面和业务逻辑对象连接起来,并创建通知代理的过程。织入可以在编译时,类加载时和运行时完成。在编译时进行织入就是静态代理,而在运行时进行织入则是动态代理。

对于通知类型来说:


以下是一个2列5行的表格:

术语释义
Before Advice连接点执行前执行的逻辑
After returning advice连接点正常执行(未抛出异常) 后执行的逻辑
After throwing advice连接点抛出异常后执行的逻辑
After finally advice无论连接点是正常执行还是抛出异常,在连接点执行完毕后执行的逻辑
Around advice该通知可以非常灵活的在方法调用前后执行特定的逻辑

✔️扩展知识


✔️AOP是如何实现的


SpringBean的初始化流程下一篇会单独做详细说明。


从Bean的初始化流程中来讲,Spring的AOP会在bean实例的实例化已完成,进行初始化后置处理时创建代理对象,即下面代码中的applyBeanPostProcessorsAfterlnitialization部分。


protected Object initializeBean(final String beanlame, final Object bean, RootBeanDefinition mbd) {
	//....
	//检查Aware
	invokeAwareMethods(beanName , bean);
	
	//调用BeanPostProcessor的前置处理方法
	Object wrappedBean = bean;
	if (mbd == null  || !mbd.isSynthetic())  {
		wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanlame);
	}

	//调用InitializingBean的afterPropertiesSet方法或自定义的初始化方法及自定义init-method方法
	try {
		invokeInitMethods(beanName,wrappedBean, mbd);
	} catch (Throwable ex) {
		throw new BeanCreationException(
			(mbd != null ? mbd.getResourceDescription() : null),
			beanName,"Invocation of init method failed", ex);
	}

	//调用BeanPostProcessor的后置处理方法
	if (mbd == null || !mbd.issynthetic()) {
		wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanlame);
	}
	return wrappedBean;
}

applyBeanPostProcessorsAfterlnitialization中会遍历所有BeanPostProcessor,然后调用其postProcessAfterlnitialization方法,而AOP代理对象的创建就是在AbstractAutoProxyCreator这个类的postProcessAfterlnitialization口中:


@override
public Object postProcessAfterInitialization(0bject bean, String beanlame) throws BeansException {
	if ((bean != null) {
		Object cacheKey = getCacheKey(bean.getClass(),beanName);
		if (this.earlyProxyReferences.remove(cacheKey) != bean) {
			if (this.earlyProxyReferences.remove(cacheKey) != bean) ;
		}
	}
	return bean;
}


这里面最重要的就是wrapIfNecessary方法了:


/**
*    如果需要,对bean进行包装。
*    
* 	@param bean 要包装的目标对象
* 	@param beanName bean的名称
* 	@param cacheKey 缓存键
* 	 @return 包装后的对象,可能是原始对象或代理对象
*/
protected Object wrapIfNecessary(Object bean, String beanlame, Object cachekey) {
	//如果beanName不为nul1且在目标源bean集合中,则直接返回原始对象
	if (beanName != null && this.targetSourcedBeans.contains(beanName)) {
		return bean;
	}
	
	//如果缓存键对应的值为 Boolean.FALSE,则直接返回原始对象
	if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
		return bean;
	}

	// 如果bean的类型为基础设施类,或者应跳过该类型的代理,则将缓存键对应的值设置为Boolean.FALSE并返回原始对象
	if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
		this.advisedBeans.put(cacheKey Boolean.FALSE);
		return bean;
	}

	//如果存在advice,为bean创建代理对象
	Objectl] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanlame, null);
	if (specificInterceptors != DO NOT PROXY) {
		//将缓存键对应的值设置为 Boolean.TRUE
		this .advisedBeans.put(cacheKey, Boolean.TRUE);
		//创建代理对象
		Object proxy = createProxy(
			bean.getClass(), beanlame, specificInterceptors, new SingletonTargetSource(bean));
			//将代理对象的类型与缓存键关联起来
			this .proxyTypes.put(cacheKey,proxy.getClass());
			return proxy;
	}

	// 如果没有advice,将缓存键对应的值设置为Boolean.FALSE并返回原始对象
	this.advisedBeans.put(cacheKey,Boolean.FALSE);
	return bean;
}

createProxy的主要作用是根据给定的bean类,bean名称、特订拦截器和目标源,创建代理对象:


/**
*    根据给定的bean类、bean名称、特定拦截器和目标源,创建代理对象。
*     
*     @param beanClass 要代理的目标对象的类
*     @param beanName bean的名称
*     @param specificInterceptors   特定的拦截器数组
*     @param targetSource   目标源
*     @return 创建的代理对象
*/
protected Object createProxy(
	Class<?> beanClass, String beanName, Object[] specificInterceptors, TargetSource targetSource)  {
		
		//如果beanFactory是ConfigurableListableBeanFactory的实例,将目标类暴露给它
		if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
			AutoProxyUtils.exposeTargetClass((ConfigurablelistableBeanFactory) this.beanFactory, beanllame, beanClass);
		}

		// 创建ProxyFactory实例,并从当前代理创建器复制配置
		ProxyFactory proxyFactory = new ProxyFactory();
		proxyFactory.copyFrom(this);

		//如果不强制使用CGLIB代理目标类,根据条件决定是否使用CGLIB代理
		if (!proxyFactory.isProxyTargetClass())  {
			if (!proxyFactory.isProxyTargetClass()) {
				proxyFactory.setProxyTargetClass(true);
			} else {
				//根据bean类评估代理接口
				evaluateProxyInterfaces(beanClass,proxyFactory);
			}
		}

		// 构建advisor数组
		Advisor[] advisors = buildAdvisors(beaname, specificInterceptors);
		//将advisors添加到ProxyFactory中
		proxyFactory.addAdvisors(advisors);
		//设置目标源
		proxyFactory.setTargetSource(targetSource);
		//设置目标源
		proxyFactory.setTargetSource(targetSource);

		// 设置代理是否冻结
		proxyFactory.setFrozen(this.freezeProxy);
		//如果advisors已经预过滤,则设置ProxyFactory为预过滤状态
		if (advisorsPreFiltered())  {
			proxyFactory.setPreFiltered(true);
		}


		//获取代理对象,并使用指定的类加载器
		return proxyFactory.getProxy(getProxyClassLoader());
	}

Spring AOP 是通过代理模式实现的,具体有两种实现方式,一种是基于Java原生的动态代理种是基于cglib的动态代理。对应到代码中就是,这里面的Proxy有两种实现,分别是CglibAopProxy和JdkDynamicAopProxy。


Spring AOP默认使用标准的JDK动态代理进行AOP代理。这使得任何接口可以被代理。但是JDK动态代理有一个缺点,就是它不能代理没有接口的类。




所以SpringAOP就使用CGLIB代理没有接口的类。


在这里插入图片描述

当然代理这种设计模式也有动态代理和静态代理之分


参考链接: Java动态代理如何实现

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Android APK未签名提醒

最近新建了一个项目&#xff0c;在build.gradle中配置好了签名&#xff0c;在执行打包的时候打出的包显示已签名&#xff0c;但是在上传市场的时候提示未签名。于是排查了好久&#xff0c;发现在build.gradle中配置的minsdk 24&#xff0c;会导致不使用V1签名&#xff0c;于是我…

【洛谷学习自留】p7621 超市购物

2023/12/29 解题思路&#xff1a; 简单的计算&#xff0c;难度主要集中在格式化输出和四舍五入的问题上。 1.建立一个计数器&#xff0c;for循环遍历单价和数量的乘积&#xff0c;存入计数器。 2.计算计数器的最终值乘以0.85h后的结果&#xff0c;为了保证四舍五入正确&…

小程序入门-登录+首页

正常新建一个登录页面 创建首页和TatBar&#xff0c;实现登录后底部出现两个按钮 代码 "pages": ["pages/login/index","pages/index/index","pages/logs/logs" ],"tabBar": {"list": [{"pagePath"…

数模学习day05-插值算法

插值算法有什么作用呢&#xff1f; 答&#xff1a;数模比赛中&#xff0c;常常需要根据已知的函数点进行数据、模型的处理和分析&#xff0c;而有时候现有的数据是极少的&#xff0c;不足以支撑分析的进行&#xff0c;这时就需要使用一些数学的方法&#xff0c;“模拟产生”一些…

Linux文件fd剖析

学习之前&#xff0c;首先要认识什么是文件&#xff1f; 空文件也是要在内存中占据空间的&#xff0c;因为它还有属性数据。文件 属性 内容文件操作 对内容 对属性 或者对内容和属性的操作标定一个文件的时候&#xff0c;必须使用&#xff1a;路径文件名&#xff0c;文件具…

Spring-4-代理

前面提到过&#xff0c;在Spring中有两种类型的代理&#xff1a;使用JDK Proxy类创建的JDK代理以及使用CGLIB Enhancer类创建的基于CGLIB的代理。 你可能想知道这两种代理之间有什么区别&#xff0c;以及为什么 Spring需要两种代理类型。 在本节中&#xff0c;将详细研究代理…

Android 理解Context

文章目录 Android 理解ContextContext是什么Activity能直接new吗&#xff1f; Context结构和源码一个程序有几个ContextContext的作用Context作用域获取ContextgetApplication()和getApplicationContext()区别Context引起的内存泄露错误的单例模式View持有Activity应用正确使用…

安全配置审计概念、应用场景、常用基线及扫描工具

软件安装完成后都会有默认的配置&#xff0c;但默认配置仅保证了服务正常运行&#xff0c;却很少考虑到安全防护问题&#xff0c;攻击者往往利用这些默认配置产生的脆弱点发起攻击。虽然安全人员已经意识到正确配置软件的重要性&#xff0c;但面对复杂的业务系统和网络结构、网…

儿童学python语言能做什么,儿童学python哪个机构好

这篇文章主要介绍了儿童学python哪个线上机构好&#xff0c;具有一定借鉴价值&#xff0c;需要的朋友可以参考下。希望大家阅读完这篇文章后大有收获&#xff0c;下面让小编带着大家一起了解一下。 少儿编程python 文章目录 前言 CSP-J与CSP-S少儿编程证书含金量排名&#xff0…

SSH -L:安全、便捷、无边界的网络通行证

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 SSH -L&#xff1a;安全、便捷、无边界的网络通行证 前言1. SSH -L基础概念SSH -L 的基本语法&#xff1a;端口转发的原理和作用&#xff1a; 2. SSH -L的基本用法远程访问本地示例&#xff1a;访问本…

git 常用操作合集

✨专栏介绍 在当今数字化时代&#xff0c;Web应用程序已经成为了人们生活和工作中不可或缺的一部分。而要构建出令人印象深刻且功能强大的Web应用程序&#xff0c;就需要掌握一系列前端技术。前端技术涵盖了HTML、CSS和JavaScript等核心技术&#xff0c;以及各种框架、库和工具…

贴片电容和薄膜电容的区别

一、贴片电容和薄膜电容的定义 贴片电容是指体积较小、形状像片的电容器&#xff0c;广泛应用于电路板和电子元器件中。薄膜电容是指以金属膜作为电极的电容器&#xff0c;广泛应用于高频和精密电路中。 二、贴片电容和薄膜电容的应用 贴片电容广泛应用于数码产品、无线通信…

JavaScript中实现页面跳转的几种常用方法

Hi i,m JinXiang ⭐ 前言 ⭐ 本篇文章主要介绍在JavaScript中实现页面跳转的几种常用方法以及部分理论知识 &#x1f349;欢迎点赞 &#x1f44d; 收藏 ⭐留言评论 &#x1f4dd;私信必回哟&#x1f601; &#x1f349;博主收将持续更新学习记录获&#xff0c;友友们有任何问题…

Linux文件的扩展属性 attr cap

文件属性 Linux文件属性分为常规属性与扩展属性&#xff0c;其中扩展属性有两种&#xff1a;attr与xattr. 一般常规的文件属性由stat API 读取&#xff0c;一般是三种权限&#xff0c;ower, group&#xff0c;时间等。 扩展属性attr 用户态API ioctl(fd, FS_IOC32_SETFLAGS…

git回滚操作,常用场景

文章目录 git回滚操作1.git reset --hard 【版本号】2.回滚后的版本v2又想回到之前的版本v32.1 git reflog 3.git checkout -- 文件名4.git reset HEAD 文件名 git回滚操作 假设我们现在有三个版本 现在回滚一个版本 1.git reset --hard 【版本号】 发现只剩下两个版本了 2.…

二叉树简单实现(C语言版)

一.简单建二叉树 在学习二叉树的基本操作前&#xff0c;需先要创建一棵二叉树&#xff0c;然后才能学习其相关的基本操作。由于现在大家对二 叉树结构掌握还不够深入&#xff0c;为了降低大家学习成本&#xff0c;此处手动快速创建一棵简单的二叉树&#xff0c;快速进入二叉树 …

二叉树顺序结构与堆的概念及性质(c语言实现堆)

上次介绍了树&#xff0c;二叉树的基本概念结构及性质&#xff1a;二叉树数据结构&#xff1a;深入了解二叉树的概念、特性与结构 今天带来的是&#xff1a;二叉树顺序结构与堆的概念及性质&#xff0c;还会用c语言来实现堆 文章目录 1. 二叉树的顺序结构2.堆的概念和结构3.堆…

Kafka:本地设置

这是设置 Kafka 将数据从 Elasticsearch 发布到 Kafka 主题的三部分系列的第一部分;该主题将被 Neo4j 使用。第一部分帮助您在本地设置 Kafka。第二部分将讨论如何设置Elasticsearch将数据发布到Kafka主题。最后 将详细介绍如何使用连接器订阅主题并使用数据。 Kafka Kafka 是…

SpringBoot项目部署及多环境

1、多环境 2、项目部署上线 原始前端 / 后端项目宝塔Linux容器容器平台 3、前后端联调 4、项目扩展和规划 多环境 程序员鱼皮-参考文章 本地开发&#xff1a;localhost&#xff08;127.0.0.1&#xff09; 多环境&#xff1a;指同一套项目代码在把不同的阶段需要根据实际…

守护青山绿水 千巡翼Q20无人机变身护林员

守护青山绿水 千巡翼Q20无人机变身护林员 无人机目前在林业上的应用主要在森林资源调查、森林资源监测、森林火灾监测、森林病虫害监测防治、野生动物监测等方面。传统手段在森林资源调查中需要耗费大量人力物力&#xff0c;利用无人机技术可快速获得所需区域高精度信息&#…
最新文章