【Spring AOP 原理】

news2024/12/25 13:10:04

首先AOP跟OOP(面向对象编程)、IOC(控制反转)一样都是一种编程思想

跟OOP不同, AOP是面向切面编程, 面对多个不具备继承关系的对象同时需要引入一段公共逻辑的时候, OOP就显得有点笨重了, 而AOP就游刃有余, 一个切面可以横跨多个类或者对象去执行公共逻辑, 极大的提升了开发效率

完全没读过源码的朋友, 建议先浅读下这篇文章【spring的AOP】, 简单了解下SpringAop基础概念

本文会从三个方面讲清楚,Spring AOP到底是怎么回事

  • 1、SpringAOP是如何生效的
  • 2、代理对象是如何创建的
  • 3、增强逻辑是怎样执行的

1、SpringAOP是如何生效的

  • 使用Spring框架时, 我们需要引入spring-aop和aspectjweaver依赖, 然后在配置类上加上@EnableAspectJAutoProxy注解就可以开始使用aop了
  • 使用SpringBoot框架时, 要简单一些, 只需要引入spring-boot-starter-aop依赖就可以开始使用aop了

那aop到底是如何生效的呢? 下面我们追踪源码看下。

首先我们看下@EnableAspectJAutoProxy注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {

}

@Import是不是很熟悉, 简单复习下它能干啥?

  • 导入一个普通bean对象到IOC中
  • 导入一个ImportSelector接口实现类, 将接口方法selectImports返回的数组中的全限定类名称对应的bean注册为到IOC中
  • 导入一个 ImportBeanDefinitionRegistrar接口实现类, 接口方法registerBeanDefinitions可以将一批BeanDefinition注册到IOC中

显然AspectJAutoProxyRegistrar是ImportBeanDefinitionRegistrar接口实现类, 肯定是想在Spring启动过程中往IOC中注册BeanDefinition, 执此想法我们去看看Spring启动流程到底在哪一步做了这个事情。

(下面我看下关键代码, 一起探索一下!)

refresh()方法不用多介绍吧, Spring启动流程就在此方法中完成, 主要看invokeBeanFactoryPostProcessors() 和 registerBeanPostProcessors()。

为啥看这俩呢, 浅读一下【SpringBoot自动配置原理】就了解了。

public void refresh()...{
		...
		try {
			...
			// Invoke factory processors registered as beans in the context.
			invokeBeanFactoryPostProcessors(beanFactory);
			
			// Register bean processors that intercept bean creation.
			registerBeanPostProcessors(beanFactory);
			...
			// Instantiate all remaining (non-lazy-init) singletons.
			finishBeanFactoryInitialization(beanFactory);
			...
	}
}

invokeBeanFactoryPostProcessors主要逻辑就是执行bean工厂的增强和BeanDefinitionRegistry的增强。
Spring AOP 原理

debug发现主要的逻辑都是ConfigurationClassPostProcessor类中完成, 这个类中执行了parse方法, 完成了配置类上的注解解析, 包括@EnableAspectJAutoProxy注解中的@Import(AspectJAutoProxyRegistrar.class), 感觉快到了, 继续追踪。

final class PostProcessorRegistrationDelegate {

	public static void invokeBeanFactoryPostProcessors(...) {
		// BeanDefinitionRegistry相关的增强
		invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
				
	}
	
	private static void invokeBeanDefinitionRegistryPostProcessors(...) {
		for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
			postProcessor.postProcessBeanDefinitionRegistry(registry);
		}
	}
}

public class ConfigurationClassPostProcessor ...{

	public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
			//到这里就开始熟悉起来了  解析主配置类上的所有注解
			parser.parse(candidates);
	}
	
}

果然在这doProcessConfigurationClass中看到了对@Import注解的处理, candidate.isAssignable(ImportBeanDefinitionRegistrar.class)这个分支中将AspectJAutoProxyRegistrar类实例化并加入到了classConfig(importBeanDefinitionRegistrars)中, 至此, 这个类已经注册进来了, 那么后面肯定有执行AspectJAutoProxyRegistrar.registerBeanDefinitions()方法地方

class ConfigurationClassParser {

	protected final SourceClass doProcessConfigurationClass(...) throws IOException {
		// Process any @Import annotations
		//果然追踪到这里看到了一行注释, 解析任何@Import注解, 就是这个无疑了
		processImports(configClass, sourceClass, getImports(sourceClass), true);
		}
	}
	
	private void processImports(...) {
		...
		if (candidate.isAssignable(ImportSelector.class)) {
			
		}
		else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
			// Candidate class is an ImportBeanDefinitionRegistrar ->
			// delegate to it to register additional bean definitions
			Class<?> candidateClass = candidate.loadClass();
			ImportBeanDefinitionRegistrar registrar = BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
			ParserStrategyUtils.invokeAwareMethods(
					registrar, this.environment, this.resourceLoader, this.registry);
			configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
		}
	}
}

继续往后追踪代码, 发现在reader.loadBeanDefinitions(configClasses)这个方法中执行了registerBeanDefinitions方法, 将AnnotationAwareAspectJAutoProxyCreator类封装成BeanDefinition注册进registry中了

public class ConfigurationClassPostProcessor ...{

	public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
		//将解析出来的配置类 注册为BeanDefinition
		this.reader.loadBeanDefinitions(configClasses);
	}
	
}

class ConfigurationClassBeanDefinitionReader {

	public void loadBeanDefinitions(...) {
		...
		for (ConfigurationClass configClass : configurationModel) {
			loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
		}
	}

	private void loadBeanDefinitionsForConfigurationClass(...) {
		...
		loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
		//
		loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
	}

	private void loadBeanDefinitionsFromRegistrars(...) {
		registrars.forEach((registrar, metadata) ->
				registrar.registerBeanDefinitions(metadata, this.registry));
	}
}

到这里就已经完成了AOP的注解解析, 继续再往后看registerBeanPostProcessors方法中执行了, getBean方法创建了AnnotationAwareAspectJAutoProxyCreator单例对象

public static void registerBeanPostProcessors(```) {
	```
	for (String ppName : orderedPostProcessorNames) {
		//在这里创建了AnnotationAwareAspectJAutoProxyCreator单例对象
		BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
	}
}

同时在初始化化过程中进行Aware接口检查的时候, 将AspectJAdvisorFactory和BeanFactoryAspectJAdvisorsBuilder对象new出来注入到对应属性字段上了

protected void initBeanFactory(ConfigurableListableBeanFactory beanFactory) {
	super.initBeanFactory(beanFactory);
	if (this.aspectJAdvisorFactory == null) {
		this.aspectJAdvisorFactory = new ReflectiveAspectJAdvisorFactory(beanFactory);
	}
	this.aspectJAdvisorsBuilder = new BeanFactoryAspectJAdvisorsBuilderAdapter(beanFactory, this.aspectJAdvisorFactory);
}

至此aop就可以使用了 , 那创建的这些对象到底有啥用呢? 往下看

2、代理对象是如何创建的

配置生效了, 那aop又是怎么帮我们创建代理对象的呢?

如果看过开篇那个文章应该知道, 创建代理的时机就是bean初始化完成后的后置增强postProcessAfterInitialization方法中

debug看下发现确实用到了AnnotationAwareAspectJAutoProxyCreator对象
Spring AOP 原理
执行了wrapIfNecessary方法, 创建出代理对象

public Object postProcessAfterInitialization(...) {
	if (bean != null) {
		Object cacheKey = getCacheKey(bean.getClass(), beanName);
		if (this.earlyProxyReferences.remove(cacheKey) != bean) {
			return wrapIfNecessary(bean, beanName, cacheKey);
		}
	}
	return bean;
}

protected Object wrapIfNecessary(...) {
	...
	// 查找并封装所有匹配的增强方法
	Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(...);
	// 创建代理对象
	Object proxy = createProxy( bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
	...
}

创建代理对象时, 在getAdvicesAndAdvisorsForBean方法中查找出所有匹配的增强方法, 并封装为Advisor, 这里我们看到使用了BeanFactoryAspectJAdvisorsBuilder去构建Advisor对象

protected List<Advisor> findCandidateAdvisors() {
	List<Advisor> advisors = super.findCandidateAdvisors();
	if (this.aspectJAdvisorsBuilder != null) {
		//使用了
		advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
	}
	return advisors;
}

拿到所有Advisor后, 就去创建代理对象, 核心代码如下, 能看到Spring Aop支持jdk和cglib两种代理 【Java的动态代理】

public AopProxy createAopProxy(...) throws AopConfigException {
	if (...) {
		//cglib代理
		return new ObjenesisCglibAopProxy(config);
	} else {
		//jdk代理
		return new JdkDynamicAopProxy(config);
	}
}

3、增强逻辑是怎样执行的

来个demo

@Aspect
@Component
public class LoggingAspect {

    @Pointcut("@annotation(com.test.springBoot.ann.AopAdvise)")
    public void pointCut() {
    }

    @Before("pointCut()")
    public void before() throws Throwable {
        System.out.println("增强方法之前");
    }

    @After("pointCut()")
    public void after() throws Throwable {
        System.out.println("增强方法之后");
    }

}

同样debug的方式看下, 发现有三个增强方法, 这里拿到了所有的Advisor并封装为Interceptor, 第一个是pointCut, 第二个是after, 第三个是before, 这个顺序很重要, 后面会依赖于这个顺序做链式调用编排
在这里插入图片描述
又将chain封装到了CglibMethodInvocation对象中, 调用proceed做链式调用编排

class CglibAopProxy implements AopProxy, Serializable {
	
	public Object intercept(...) throws Throwable {
		//拿到了所有的Advisor并封装为Interceptor
		List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
		// 封装了CglibMethodInvocation对象, 调用proceed做链式调用编排
		retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
	
	}
	
}

主要是用下面两个对象进行编排的

  • ReflectiveMethodInvocation 方法执行编排器
  • MethodInterceptor 增强方法执行对象


public class ReflectiveMethodInvocation ... {

	public Object proceed() throws Throwable {
		//	判断  -1 = chain.size() - 1
		if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
			return invokeJoinpoint();
		}
	
		//拿到当前的Advisor
		Object interceptorOrInterceptionAdvice = this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
		
		
		if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
			// 如果当前的Advisor是一个连接点 
			// 比如 before/after/afterReturn等, 就执行invoke方法
			...
			return dm.interceptor.invoke(this);
			...
			
		} else {
			// 否则就是切入点方法
			return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
		}
	}
}

我们可以看到invoke把编排器传了进去, 还记的上面的顺序把, 按顺序依次去看实现类
ExposeInvocationInterceptor 中上来先调用下一个process
AspectJAfterAdvice 中上来先调用下一个process
MethodBeforeAdviceInterceptor 中, 执行了before增强方法, 调用下一process
再次进入process方法中 判断条件 -1 = chain.size() - 1 成立, 执行invokeJoinpoint(业务方法)
返回到MethodBeforeAdviceInterceptor执行完了
再返回到AspectJAfterAdvice执行after方法
再返回到ExposeInvocationInterceptor设置毒丸


public final class ExposeInvocationInterceptor implements MethodInterceptor...{
	@Override
	public Object invoke(MethodInvocation mi) throws Throwable {
		MethodInvocation oldInvocation = invocation.get();
		invocation.set(mi);
		try {
			// 先调下一个增强
			return mi.proceed();
		} finally {
			// 最后设置毒丸
			invocation.set(oldInvocation);
		}
	}
}

public class AspectJAfterAdvice  implements MethodInterceptor... {
	@Override
	public Object invoke(MethodInvocation mi) throws Throwable {
		try {
			//先调下一个增强
			return mi.proceed();
		} finally {
			// 最后执行after增强
			invokeAdviceMethod(...);
		}
	}
}

public class MethodBeforeAdviceInterceptor implements MethodInterceptor... {

	private final MethodBeforeAdvice advice;

	@Override
	public Object invoke(MethodInvocation mi) throws Throwable {
		// 执行before增强
		this.advice.before(...);
		// 继续调下一个增强
		return mi.proceed();
	}

}


也就是说基于栈, 先入后出的特点, 实现了执行顺序 before -> 业务方法 -> after -> 切入点(毒丸方法) 的增强
Spring AOP 原理

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

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

相关文章

第六节、Docker 方式部署指南 github 上项目 mkdocs-material

一、简介 MkDocs 可以同时编译多个 markdown 文件,形成书籍一样的文件。有多种主题供你选择,很适合项目使用。 MkDocs 是快速,简单和华丽的静态网站生成器,可以构建项目文档。文档源文件在 Markdown 编写,使用单个 YAML 配置文件配置。 MkDocs—markdown项目文档工具,…

论文 | The Capacity for Moral Self-Correction in LargeLanguage Models

概述 论文探讨了大规模语言模型是否具备“道德自我校正”的能力&#xff0c;即在收到相应指令时避免产生有害或偏见输出的能力。研究发现&#xff0c;当模型参数达到一定规模&#xff08;至少22B参数&#xff09;并经过人类反馈强化学习&#xff08;RLHF&#xff09;训练后&…

初识GIS

文章目录 一、什么叫地理信息1、定义2、主要特点3、分类 二、什么叫GIS1、定义2、GIS对空间信息的储存2.1、矢量数据模型2.2、栅格数据模型 3、离散栅格和连续栅格的区别 三、坐标系统1、为什么要存在坐标系统&#xff1f;2、地理坐标系2.1、定义与特点2.2、分类 3、投影坐标系…

通过 HTTP 获取远程摄像头视频流并使用 YOLOv5 进行目标检测

在本教程中&#xff0c;我们将通过 HTTP 获取远程摄像头视频流&#xff0c;并使用 YOLOv5 模型进行实时目标检测。我们会利用 Python 的 OpenCV 库获取视频流&#xff0c;使用 YOLOv5 模型进行目标检测&#xff0c;并使用多线程来提高实时性和效率。 项目地址&#xff1a;like…

Android Framework AMS(17)APP 异常Crash处理流程解读

该系列文章总纲链接&#xff1a;专题总纲目录 Android Framework 总纲 本章关键点总结 & 说明&#xff1a; 说明&#xff1a;本章节主要解读APP Crash处理。关注思维导图中左上侧部分即可。 本章节主要是对Android的APP Crash处理有一个基本的了解。从进程启动到UncaughtH…

javaWeb小白项目--学生宿舍管理系统

目录 一、检查并关闭占用端口的进程 二、修改 Tomcat 的端口配置 三、重新启动 Tomcat 一、javaw.exe的作用 二、结束javaw.exe任务的影响 三、如何判断是否可以结束 结尾&#xff1a; 这个错误提示表明在本地启动 Tomcat v9.0 服务器时遇到了问题&#xff0c;原因是所需…

深度学习在边缘检测中的应用及代码分析

摘要&#xff1a; 本文深入探讨了深度学习在边缘检测领域的应用。首先介绍了边缘检测的基本概念和传统方法的局限性&#xff0c;然后详细阐述了基于深度学习的边缘检测模型&#xff0c;包括其网络结构、训练方法和优势。文中分析了不同的深度学习架构在边缘检测中的性能表现&am…

SpringBoot(十七)创建多模块Springboot项目

在gitee上查找资料的时候,发现有不少Springboot项目里边都是嵌套了多个Springboot项目的。这个玩意好,在协作开发的时候,将项目分成多个模块,有多个团队协作开发,模块间定义标准化通信接口进行数据交互即可。 这个好这个。我之前创建的博客项目是单模块的SpringBoot项目,…

STM32WB55RG开发(2)----STM32CubeProgrammer烧录

STM32WB55RG开发----2.STM32CubeProgrammer烧录 概述硬件准备视频教学样品申请源码下载参考程序自举模式UART烧录USB烧录 概述 STM32CubeProgrammer (STM32CubeProg) 是一款用于编程STM32产品的全功能多操作系统软件工具。 它提供了一个易用高效的环境&#xff0c;通过调试接口…

使用Java爬虫获取商品订单详情:从API到数据存储

在电子商务日益发展的今天&#xff0c;获取商品订单详情成为了许多开发者和数据分析师的需求。无论是为了分析用户行为&#xff0c;还是为了优化库存管理&#xff0c;订单数据的获取都是至关重要的。本文将详细介绍如何使用Java编写爬虫&#xff0c;通过API获取商品订单详情&am…

高性能分布式缓存Redis-分布式锁与布隆过滤器

一、分布式锁 我们先来看一下本地锁 在并发编程中&#xff0c;我们通过锁&#xff0c;来避免由于竞争而造成的数据不一致问题。通常&#xff0c;我们以 synchronized 、Lock 来使用它&#xff08;单机情况&#xff09; 来看这段代码 Autowired RedisTemplate<String,Str…

SpringSecurity+jwt+captcha登录认证授权总结

SpringSecurityjwtcaptcha登录认证授权总结 版本信息&#xff1a; springboot 3.2.0、springSecurity 6.2.0、mybatis-plus 3.5.5 认证授权思路和流程&#xff1a; 未携带token&#xff0c;访问登录接口&#xff1a; 1、用户登录携带账号密码 2、请求到达自定义Filter&am…

从社交媒体到元宇宙:Facebook未来发展新方向

Facebook&#xff0c;作为全球最大的社交媒体平台之一&#xff0c;已经从最初的简单互动工具发展成为一个跨越多个领域的科技巨头。无论是连接人与人之间的社交纽带&#xff0c;还是利用大数据、人工智能等技术为用户提供个性化的体验&#xff0c;Facebook一直引领着社交网络的…

javascript用来干嘛的?赋予网站灵魂的语言

javascript用来干嘛的&#xff1f;赋予网站灵魂的语言 在互联网世界中&#xff0c;你所浏览的每一个网页&#xff0c;背后都有一群默默工作的代码在支撑着。而其中&#xff0c;JavaScript就像是一位技艺精湛的魔术师&#xff0c;它赋予了网页生命力&#xff0c;让原本静态的页…

Wordpress常用配置,包括看板娘跨域等

一个Wordpress的博客已经搭建完成了&#xff0c;那么为了让它看起来更有人间烟火气一点&#xff0c;有一些常用的初始配置&#xff0c;这里整理一下。 修改页脚 页脚这里默认会显示Powered by Wordpress&#xff0c;还有一个原因是这里要加上备案信息。在主题里找到页脚&…

The Internals of PostgreSQL 翻译版 持续更新...

为了方便自己快速学习&#xff0c;整理了翻译版本&#xff0c;目前翻译的还不完善&#xff0c;后续会边学习边完善。 文档用于自己快速参考&#xff0c;会持续修正&#xff0c;能力有限,无法确保正确!!! 《The Internals of PostgreSQL 》 不是 《 PostgreSQL14 Internals 》…

FlinkPipelineComposer 详解

FlinkPipelineComposer 详解 原文 背景 在flink-cdc 3.0中引入了pipeline机制&#xff0c;提供了除Datastream api/flink sql以外的一种方式定义flink 任务 通过提供一个yaml文件&#xff0c;描述source sink transform等主要信息 由FlinkPipelineComposer解析&#xff0c…

MybatisPlus知识

mybatis与mybatisplus的区别&#xff1a; mybatisplus顾名思义时mybatis的升级版&#xff0c;提供了更多的API和方法&#xff0c;是基于mybatis框架基础上的升级&#xff0c;更加方便开发。mybatisplus继承BaseMapper接口并调用其中提供的方法来操作数据库&#xff0c;不需要再…

利用飞书多维表格自动发布版本

文章目录 背景尝试1&#xff0c;轮询尝试2&#xff0c;长连接 背景 博主所在的部门比较奇特&#xff0c;每个车型每周都需要发版&#xff0c;所以实际上一周会发布好几个版本。经过之前使用流水线自动发版改造之后&#xff0c;发版的成本已经大大降低了&#xff0c;具体参考&a…

Qwen2-VL:发票数据提取、视频聊天和使用 PDF 的多模态 RAG 的实践指南

概述 随着人工智能技术的迅猛发展&#xff0c;多模态模型在各类应用场景中展现出强大的潜力和广泛的适用性。Qwen2-VL 作为最新一代的多模态大模型&#xff0c;融合了视觉与语言处理能力&#xff0c;旨在提升复杂任务的执行效率和准确性。本指南聚焦于 Qwen2-VL 在三个关键领域…