【Spring AOP 原理】

首先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 definitionsClass<?> 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) {//将解析出来的配置类 注册为BeanDefinitionthis.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并封装为InterceptorList<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() - 1if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {return invokeJoinpoint();}//拿到当前的AdvisorObject 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...{@Overridepublic 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... {@Overridepublic Object invoke(MethodInvocation mi) throws Throwable {try {//先调下一个增强return mi.proceed();} finally {// 最后执行after增强invokeAdviceMethod(...);}}
}public class MethodBeforeAdviceInterceptor implements MethodInterceptor... {private final MethodBeforeAdvice advice;@Overridepublic Object invoke(MethodInvocation mi) throws Throwable {// 执行before增强this.advice.before(...);// 继续调下一个增强return mi.proceed();}}

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

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

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

相关文章

Vue3集成搜索引擎智能提示API

需求&#xff1a; 如何在项目中实现像百度搜索框一样的智能提示效果&#xff0c;如下图所示&#xff1a; 相关知识&#xff1a; 下面是各厂商提供的免费API 厂商请求百度http://suggestion.baidu.com/su?wd中国&cbwindow.baidu.sug必应http://api.bing.com/qsonhs.as…

python3的基本数据类型:可变集合的用法

一. 简介 前面学习了 python3中的一种基本数据类型-集合&#xff0c;文章如下&#xff1a; python3的基本数据类型&#xff1a;集合的创建与分类-CSDN博客 本文继续学习 Python3中的集合&#xff0c;主要学习 可变集合的用法。 二. python3的基本类型&#xff1a;可变集合的…

从零开始:我的鸿蒙学习之旅(二)

前言 记录我在学习鸿蒙操作系统过程中的成长&#xff0c;旨在激励我自己&#xff0c;也希望能激发读者们的学习热情&#xff0c;一起愉快地探索鸿蒙开发的世界&#xff01; 我说说这几天的学习成果吧&#xff0c;将开发入门的第一部分的剩下小节以及第二部分的第一小结写完了…

SSM学习记录(一)之SSM整合

SSM学习记录&#xff08;一&#xff09;之SSM整合 一、SSM整合二、SSM整合的核心问题1、SSM需要几个IoC容器2、每个IoC容器对应哪些类型组件3、IoC容器之间的关系和调用方向4、具体有多少配置以及对应的容器的关系5、IoC初始化方式和配置位置 一、SSM整合 微观&#xff1a;将学…

【从理论到应用】HTTP请求响应详解 (请求数据格式,请求方式,Web开发中的体现)

目录 一.HTTP协议 二.HTTP请求数据格式 请求方式 三.Web开发中的HTTP请求与响应 接收HTTP请求 同一响应格式 四.使用第三方工具发送HTTP请求&#xff08;Apifox、postman、Yapi&#xff09; 一.HTTP协议 HTTP&#xff08;Hypertext Transfer Protocol&#xff0c;超…

猎板PCB罗杰斯板材的应用案例

以下是几个猎板 PCB 与罗杰斯板材结合的具体案例&#xff1a; 案例一&#xff1a;5G 通信基站天线 PCB 在 5G 通信基站的天线系统中&#xff0c;对高频信号的传输和处理要求极高。猎板 PCB 采用罗杰斯板材&#xff0c;凭借其稳定的低介电常数&#xff08;如 RO4003C 板材&…

基于Java Springboot快递物流管理系统

一、作品包含 源码数据库全套环境和工具资源部署教程 二、项目技术 前端技术&#xff1a;Html、Css、Js、Layui 数据库&#xff1a;MySQL 后端技术&#xff1a;Java、Spring Boot、MyBatis 三、运行环境 开发工具&#xff1a;IDEA 数据库&#xff1a;MySQL8.0 数据库管…

力扣662:二叉树的最大宽度

给你一棵二叉树的根节点 root &#xff0c;返回树的 最大宽度 。 树的 最大宽度 是所有层中最大的 宽度 。 每一层的 宽度 被定义为该层最左和最右的非空节点&#xff08;即&#xff0c;两个端点&#xff09;之间的长度。将这个二叉树视作与满二叉树结构相同&#xff0c;两端…

Servlet的使用

一.Servelt简介 1.为什么需要servlet:因为前端三件套无法操控数据库,即与用户进行交互操作 2.servlet由服务器端调用和执行的(由tomcat解析和调用的),由java语言编写,本质就是java类 3.功能强大,可以完成几乎所有的网站功能,按照Servlet规范开发 二.手动开发Servelt 1.Servl…

【嵌入式C语言】GCC概述+C语言编译过程

目录 前言1 课程介绍1.1 计算机程序语言的学习思路?1.2 基本程序设计思想:1.3 C语言工具的特性:1.4 推荐教材 2 GCC的使用及其常用选项介绍2.1 GCC概述gcc -vgcc -ogcc -v -o 2.2 C语言编译过程2.2.1 预处理2.2.2 编译2.2.3 汇编2.2.4 链接2.2.5 问题 2.3 宏的使用 前言 重新学…

C语言 数组排序 – 插入法排序 - C语言零基础入门教程

目录 一.简介二.数组插入法排序原理三.数组插入法排序实战四.猜你喜欢 零基础 C/C 学习路线推荐 : C/C 学习目录 >> C 语言基础入门 一.简介 经过前面的学习&#xff0c;我们已经学会了数组遍历&#xff0c;在开发中&#xff0c;我们经常回碰到对数组进行排序&#xff0c…

vulnhub- Machine_Matrix_v3靶机的测试报告

目录 一、测试环境 1、系统环境 2、使用工具/软件 二、测试目的 三、操作过程 1、信息搜集 2、Getshell 3、提权 四、结论 一、测试环境 1、系统环境 渗透机&#xff1a;kali2021.1(192.168.200.131) 靶 机&#xff1a;Linux matrix 4.16.3-porteus(192.168.200.1…

2024-11-13 Unity Addressables1——概述与导入

文章目录 1 概述1.1 介绍1.2 主要作用1.3 Addressables 与 AssetBundle 的区别 2 导入3 配置3.1 方法一3.2 方法二 1 概述 1.1 介绍 ​ Addressables 是可寻址资源管理系统。 ​ Unity 从 2018.2 版本开始&#xff0c;建议用于替代 AssetBundle 的高阶资源管理系统。在 Unit…

操作系统lab4-页面置换算法的模拟

操作系统lab4-页面置换算法的模拟 文章目录 操作系统lab4-页面置换算法的模拟实验目的实验内容实验分析 代码测试用例运行结果 实验目的 1、掌握请求分页存储管理的常用理论&#xff1a;页面置换算法。 2、理解请求分页中的按需调页机制。 实验内容 独立地用高级语言编写和…

springboot的依赖实现原理:spring-boot-starter-parent解析

01 dependencyManagement的作用 在使用springboot时我们会在项目pom引入以下配置和依赖 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.18</version> &l…

基于Java Springboot图书馆管理系统

一、作品包含 源码数据库文档全套环境和工具资源部署教程 二、项目技术 前端技术&#xff1a;Html、Css、Js 数据库&#xff1a;MySQL 后端技术&#xff1a;Java、Spring Boot、MyBatis 三、运行环境 开发工具&#xff1a;IDEA/eclipse 数据库&#xff1a;MySQL8.0 数据…

MySQL联合索引(abc)命中测试

1.建表 mysql创建一张表&#xff0c;表名&#xff1a;‘test_models’ id列为 主键&#xff0c;int类型 &#xff0c;自增a,b,c,d,e 全部是int&#xff08;11&#xff09;为&#xff08;a,b,c&#xff09;添加一个联合索引 index_abc 执行语句&#xff1a;创建表 CREATE TA…

Linux信号

1. 什么是进程&#xff1f; 从内核的角度&#xff0c;进程是系统分配资源的单位。当一个程序(静态)被加载到内存&#xff0c;操作系统为程序分配一个PCB&#xff08;进程控制块&#xff09;。 PCB&#xff1a;Process Control Block。在Linux中PCB叫做task_struct的结构体&am…

07-案例-图书管理

欢迎来到“雪碧聊技术”CSDN博客&#xff01; 在这里&#xff0c;您将踏入一个专注于Java开发技术的知识殿堂。无论您是Java编程的初学者&#xff0c;还是具有一定经验的开发者&#xff0c;相信我的博客都能为您提供宝贵的学习资源和实用技巧。作为您的技术向导&#xff0c;我将…

js中typeOf无法区分数组对象

[TOC]&#xff08;js中typeOf无法区分数组对象) 前提&#xff1a;很多时候我们在JS中用typeOf来判断值类型&#xff0c;如&#xff1a;typeOf ‘abc’//string ,typeOf 123 //number; 但当判断对象为数组时返回的仍是’object’ 这时候我们可以使用Object.prototype.toString.c…