【Spring】AOP实现原理

注册AOP代理创建器

在平时开发过程中,如果想开启AOP,一般会使用@EnableAspectJAutoProxy注解,这样在启动时,它会向Spring容器注册一个代理创建器用于创建代理对象,AOP使用的是AnnotationAwareAspectJAutoProxyCreator,它实现了SmartInstantiationAwareBeanPostProcessor,从名字中可以看出这是一个Bean后置处理器BeanPostProcessor,BeanPostProcessor是Spring提供的一个扩展点,里面提供了两个方法,分别为postProcessBeforeInitialization(初始化之前)和postProcessAfterInitialization(初始化之后),可以在Bean初始化前后,进行一些操作(比如为Bean设置属性值)。

关于后置处理器的使用可参考:【Spring】BeanPostProcessor后置处理器

  • Advisor:对切面的封装,使用了@AspectJ注解的类会被Spring封装成Advisor。

AOP的实现主要在代理创建器的postProcessAfterInitialization方法中:

  • postProcessAfterInitialization:在bean初始化之后执行的方法,这时候bean已经实例化完毕,这里会调用wrapIfNecessary方法判断是否有必要为该Bean生成AOP代理对象,如果不需要创建AOP代理对象直接返回即可,反之会获取Advisors,然后创建AOP的代理对象,替换掉原来生成的Bean
// AnnotationAwareAspectJAutoProxyCreator的父类AbstractAutoProxyCreator中实现
public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
        implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {

    /**
     * 在bean初始化之后执行的方法
     */
    @Override
    public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
        if (bean != null) {
            // 构建缓存Key
            Object cacheKey = getCacheKey(bean.getClass(), beanName);
            if (this.earlyProxyReferences.remove(cacheKey) != bean) {
                // 是否有必要创建AOP代理对象
                return wrapIfNecessary(bean, beanName, cacheKey);
            }
        }
        return bean;
    }
}

AOP代理条件判断

Spring需要知道有哪些类需要进行AOP代理、哪些需要跳过AOP代理,比如我们使用@Aspect标注的切面类,只是一个普通Bean,不需要进行AOP代理,而我们的目标类,就需要进行代理,所以这一步会进行判断。

处于以下情况之一是不需要进行AOP代理的,会跳过:

  1. 如果是Advice、Pointcut、Advisor、AopInfrastructureBean类本身及其子类则跳过创建,这些是Spring的基础类,不需要进行AOP代理;
  2. 如果有Aspect注解且不是通过Ajc编译的类,这个就是用@Aspect标注的切面类,也不需要AOP进行代理;
  3. 这个判断条件的作用同上,只不过个判断主要用于在XML中通过 aop:aspect标签形式来配置切面的情况,Spring会生成一个对应的AspectJPointcutAdvisor,切面本身对应的那个Java类是不需要进代理的,所以添加了一个判断,跳过切面本身对应的那个Java类,在使用注解和使用aop:aspect标签时实现不一样,所以这里又加了一个条件判断;

获取Advisor

对于上述情况的bean会跳过,剩下的Bean需要先获取所有的Advisors,从中找出适用于当前Bean的Advisor,如果查找到表示当前Bean需要进行AOP代理,依旧返回原来的Bean对象即可。

Spring会把使用了@AspectJ注解定义的切面包装成Advisor,判断是否有与当前bean匹配的Advisor,判断方式如下:

  1. 根据切点Pointcut的getClassFilter方法对类进行匹配,判断当前Bean的class是否匹配;

  2. 根据切点Pointcut获取MethodMatcher方法匹配器,通过MethodMatcher对当前Bean中的每一个方法进行匹配,也就是使用配置的切点表达式对方法进行匹配;

经过这一步处理,如果匹配到了该Bean的Advisor,说明当前Bean需要进行AOP代理,会返回适用于当前Bean的Advisor集合,接下来会为该Bean创建AOP代理对象。

创建AOP代理对象

创建代理对象

前置知识:JDK动态代理,可参考 【Java】JDK动态代理实现原理

Spring提供了两种方式创建代理对象,分别是JDK动态代理和Cglib,使用JDK动态代理需要被代理对象实现接口,否则使用Cglib实现。

以JDK动态代理为例,创建代理对象的过程在JdkDynamicAopProxy中,它实现了InvocationHandler,在通过JDK的动态代理创建对象的时候,需要这个InvocationHandler,通过Proxy的newProxyInstance即可创建AOP代理对象:

final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable {

    @Override
    public Object getProxy(@Nullable ClassLoader classLoader) {
        if (logger.isTraceEnabled()) {
            logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());
        }
        Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
        findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
        // 生成代理对象,proxiedInterfaces为目标代理类,this为InvocationHandler也就是当前的JdkDynamicAopProxy
        return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
    }
}
执行目标方法

在创建了AOP代理对象之后,会使用这个代理对象替换掉原来容器中的Bean,开发过程中拿到的Bean就是这个AOP代理对象了,当执行目标方法时,首先会进入到代理对象的invoke方法(JdkDynamicAopProxy中的invoke方法,InvocationHandler中定义了invoke方法,JdkDynamicAopProxy实现了InvocationHandler接口所以会实现这个方法):

对于AOP,在invoke方法中,会先获取目标方法的所有拦截器,Spring会将适用于当前方法的Advisor转为方法拦截器,然后使用责任链模式,对拦截器进行一个个的调用,当然如果当前方法没有对应的拦截器需要执行,直接通过反射执行目标方法即可。

因为拦截器可以有多个,所以执行拦截器链方法是一个递归调用的过程(在ReflectiveMethodInvocation中实现),它使用了一个变量currentInterceptorIndex记录了当前拦截器的下标:

  1. 判断currentInterceptorIndex是否与拦截器链的大小一致,如果一致说明已经走到了最后一个拦截器,拦截器走完就可以执行目标方法了,此时会通过反射执行目方法;
  2. 如果拦截器链未走完,会对currentInterceptorIndex加1,获取下一个拦截器,继续执行;

举个例子
比如定义了一个切面,里面设置了两个通知,分别是前置通知和环绕通知,要将这个切面作用到某个目标方法,在方法执行前后进行一些操作,Spring会将切面及通知封装为拦截器,在执行目标方法时,拦截器链中就会有两个拦截器,首先执行第一个拦截器,它是一个前置通知,执行完前置通知的方法后,会向后推进进入下一个拦截器:

执行到第二个拦截器,它是一个环绕通知,首先执行环绕通知中的前置操作(环绕通知中目标方法执行之前),运行完毕之后,该方法不会结束,会继续进入拦截器链的处理逻辑,等待目标方法执行之后再继续执行后置操作(环绕通知中目标方法执行之后的操作):

此时已经是拦截器链中最后一个,所以此时可以执行目标方法,执行完目标方法,拦截器链的逻辑已经执行完毕,所以对于第二个拦截器来说,会回到环绕通知中的处理逻辑,开始执行目标方法执行之后的后置操作。

总结
(1)在开启AOP的时候,它会向容器中注册一个AOP的代理对象创建器,它是一个后置处理器,在Spring容器中每个Bean实例化之后,初始化前后会进入到后置处理器对应的方法中,AOP创建代理对象并将原来的Bean替换就是在后置处理器的postProcessAfterInitialization方法中进行的。

(2)在创建AOP代理之前会先判断是否需要为当前Bean创建代理对象,因为并不是所有的Bean都需要进行创建,只有切面中设置要拦截的那些方法所在的Bean才需要创建AOP代理对象,所以一些Spring基础类、使用@Aspect标注的切面本身等Bean都会跳过。

(3)经过上述步骤后,会或获取所有的Advisor,Spring会将创建的切面包装成Advisor,所以可以理解为获取定义的所有切面,从中找出是否有匹配当前Bean的切面,如果有表示需要为Bean创建AOP代理,之后就会根据Bean的信息,比如是否实现了接口,来决定使用JDK动态代理还是Cglib创建代理对象,创建代理对象之后会替换掉原来的Bean,将这个代理对象返回。

(4)代理对象创建完毕之后,执行目标方法时,会进入到代理对象的业务逻辑中,在这里会获取匹配当前目标方法的所有Advisor(比如前置通知、后置通知等)将其转换成一个拦截器链,然后执行拦截器链,执行每个拦截器链的时候会执行对应通知中的方法,当拦截器链执行完毕之后,会通过反射执行真正的目标方法。

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

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

相关文章

计算机考研精炼1000题:笔试面试必备攻略

&#x1f482; 个人网站:【工具大全】【游戏大全】【神级源码资源网】&#x1f91f; 前端学习课程&#xff1a;&#x1f449;【28个案例趣学前端】【400个JS面试题】&#x1f485; 寻找学习交流、摸鱼划水的小伙伴&#xff0c;请点击【摸鱼学习交流群】 ⭐️ 好书推荐 计算机考…

Qt5多线程<12>

1.多线程的简单实现 <1> 在头文件threaddlg.h声明用于界面显示所需的控件,其代码如下: #ifndef THREADDLG_H #define THREADDLG_H#include <QDialog> #include <QPushButton> #include "workthread.h" #define MAXSIZE 5class ThreadDlg : publ…

2023年的低代码:数字化、人工智能、趋势及未来展望

前言 正如许多专家预测的那样&#xff0c;低代码平台在2023年将展现更加强劲的势头。越来越多的企业正在纷纷转向低代码开发&#xff0c;他们希望能够快速开发内部应用程序&#xff0c;并在经济衰退可能出现的情况下保持灵活性。在这个大背景下&#xff0c;低代码平台为企业软件…

各大电商平台关于预制菜品种酸菜鱼销售量

# 导入需要的包 library(rvest) # 用于网页抓取 library(tidyverse) # 用于数据处理 library(stringr) # 用于字符串处理# 设置代理信息 proxy_host <- "www.duoip.cn" proxy_port <- 8000# 设置要爬取的网页 url <- "https://jshk.com.cn/products/sa…

【趣味随笔】YOLO的“进化史”极简版(YOLO v1-->YOLOP)

&#x1f4e2;&#xff1a;如果你也对机器人、人工智能感兴趣&#xff0c;看来我们志同道合✨ &#x1f4e2;&#xff1a;不妨浏览一下我的博客主页【https://blog.csdn.net/weixin_51244852】 &#x1f4e2;&#xff1a;文章若有幸对你有帮助&#xff0c;可点赞 &#x1f44d;…

httpRequest库代码示例

python # 首先导入所需的库 library(httpRequest) # 设置主机名和端口号 proxy_host <- proxy_port <- # 使用httpRequest库的get函数下载图片 response <- httpRequest(", proxyHost proxy_host, proxyPort proxy_port) # 确保请求成功 if (response$sta…

bat脚本批量修改文件名称的方法,以及乱码问题解决

当有大量的文件&#xff0c;需要按照对应数据进行文件名称的修改时候&#xff0c;用bat脚本&#xff0c;是一个快速&#xff0c;并且简单的方法。 使用命令 “"ren "&A2&" "&D2&""” 其中A2为需要修改的单元格&#xff0c;D2为…

ORA-00257: Archiver error. Connect AS SYSDBA only until resolved错误解决

错误的原因&#xff1a;是因为服务器分配空间不足&#xff0c;数据库归档日志满导致系统数据库登陆失败。 解决办法&#xff1a;1.删除以前的日志 2.增大归档日志的容量 3.关闭归档模式 一、删除以前的容量 1.登录账号后&#xff0c;查看ORACLE_BASE目录 【oraclelocalhost~】$…

Spring基础(1):两个概念

最近看了点Spring的源码&#xff0c;于是来稍微扯一扯&#xff0c;希望能帮一部分培训班出身的朋友撕开一道口子&#xff0c;透透气。 广义上的Spring指的是Spring整个项目&#xff0c;包含SpringBoot、SpringCloud、SpringFramework、SpringData等等&#xff0c; 本系列文章…

设计模式之发布订阅、观察者模式

一、观察者模式 观察者模式定义了对象间的一种一对多的依赖关系&#xff0c;当一个对象的状态发生改变时&#xff0c;所有依赖于它的对象都将得到通知&#xff0c;并自动更新 观察者模式属于行为型模式&#xff0c;行为型模式关注的是对象之间的通讯&#xff0c;观察者模式就…

一个java文件的JVM之旅

准备 我是小C同学编写得一个java文件&#xff0c;如何实现我的功能呢&#xff1f;需要去JVM(Java Virtual Machine)这个地方旅行。 变身 我高高兴兴的来到JVM&#xff0c;想要开始JVM之旅&#xff0c;它确说&#xff1a;“现在的我还不能进去&#xff0c;需要做一次转换&#x…

LeetCode算法题解(回溯、难点)|LeetCode332. 重新安排行程

LeetCode332. 重新安排行程 题目链接&#xff1a;332. 重新安排行程 题目描述&#xff1a; 给你一份航线列表 tickets &#xff0c;其中 tickets[i] [fromi, toi] 表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。 所有这些机票都属于一个从 JFK&#xff08…

uboot启动linux kernel的流程

目录 前言流程图autoboot_commandrun_command_listdo_bootmdo_bootm_statesdo_bootm_linuxboot_prep_linuxboot_jump_linux 前言 本文在u-boot启动流程分析这篇文章的基础上&#xff0c;简要梳理uboot启动linux kernel的流程。 流程图 其中&#xff0c; autoboot_command位于…

Error creating bean with name ‘apiModelSpecificationReader‘ defined in URL

问题&#xff1a; 启动项目的时候&#xff0c;报错了 org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name apiModelSpecificationReader defined in URL [jar:file:/D:/.gradle/caches/modules-2/files-2.1/io.springfox/sp…

【亚马逊云科技产品测评】活动征文|10分钟拥有一台AWS Linux系统

前言 在数字化时代&#xff0c;AWS云服务扮演着至关重要的角色。AWS&#xff08;Amazon Web Services&#xff09;是亚马逊公司旗下的云计算服务平台&#xff0c;为全球各地的企业、组织和个人开发者提供了一系列广泛而深入的云服务。 在AWS云服务中&#xff0c;计算、存储、数…

selenium css定位

selenium-css定位 element_css driver.find_element(By.CSS_SELECTOR, css表达式)css定位说明 selenium中的css定位&#xff0c;实际是通过css选择器来定位到具体元素&#xff0c;css选择器来自于css语法 css定位优点 语法简洁对比其他定位方式&#xff0c;定位效率更快对…

「Java开发指南」如何用MyEclipse搭建Spring MVC应用程序?(二)

本教程将指导开发者如何生成一个可运行的Spring MVC客户应用程序&#xff0c;该应用程序实现域模型的CRUD应用程序模式。在本教程中&#xff0c;您将学习如何&#xff1a; 从数据库表的Scaffold到现有项目部署搭建的应用程序 使用Spring MVC搭建需要MyEclipse Spring或Bling授…

发电机负载测试:专业指南

发电机负载测试是一项关键的测试过程&#xff0c;发电机负载测试的专业指南&#xff0c;帮助您进行有效的测试。 测试前准备&#xff1a;确保发电机和测试设备处于良好的工作状态&#xff0c;检查发电机的电源和燃料供应是否正常&#xff0c;确保测试设备和发电机之间的连接正确…

Unity Mirror学习(二) Command特性使用

Command&#xff08;命令&#xff09;特性 1&#xff0c;修饰方法的&#xff0c;当在客户端调用此方法&#xff0c;它将在服务端运行&#xff08;我的理解&#xff1a;客户端命令服务端做某事&#xff1b;或者说&#xff1a;客户端向服务端发消息&#xff0c;消息方法&#xff…

高能数造电池3D打印智能制造小试线,开启全固态电池数字化新时代

在科技创新的浪潮中&#xff0c;电池制造领域又迎来了一次突破性的进展。近日&#xff0c;高能数造(西安)技术有限公司重磅推出了其最新电池数字制造装备——全固态电池3D打印智能制造小试线 &#xff0c;这一创新性的技术开启了全固态电池的数字化智造新时代&#xff0c;为全固…
最新文章