SpringAOP源码解析之advice构建排序(二)

上一章我们知道Spring开启AOP之后会注册AnnotationAwareAspectJAutoProxyCreator类的定义信息,所以在属性注入之后initializeBean的applyBeanPostProcessorsAfterInitialization方法执行的时候调用AnnotationAwareAspectJAutoProxyCreator父类(AbstractAutoProxyCreator)的postProcessAfterInitialization方法来创建AOP的代理。

// AbstractAutoProxyCreator类
@Overridepublic Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {if (bean != null) {Object cacheKey = getCacheKey(bean.getClass(), beanName);if (this.earlyProxyReferences.remove(cacheKey) != bean) {// 真正进行处理的地方,里面有代码很明显是用来创建代理对象的return wrapIfNecessary(bean, beanName, cacheKey);}}return bean;}

然后我们创建一个Aspect,方便我们这章的分析

@Component
@Aspect
public class ThamNotVeryUsefulAspect {@Pointcut("execution(* com.qhyu.cloud.aop.service.QhyuAspectService.*(..))") // the pointcut expressionprivate void thamAnyOldTransfer() {} // the pointcut signature@Before("thamAnyOldTransfer()")public void before(){System.out.println("tham Before 方法调用前");}@After("thamAnyOldTransfer()")public void after(){System.out.println("tham After 方法调用前");}@AfterReturning("thamAnyOldTransfer()")public void afterReturning(){System.out.println("tham afterReturning");}@AfterThrowing("thamAnyOldTransfer()")public void afterThrowing(){System.out.println("tham AfterThrowing");}@Around("thamAnyOldTransfer()")public Object  around(ProceedingJoinPoint pjp) throws Throwable{// start stopwatchSystem.out.println("tham around before");Object retVal = pjp.proceed();// stop stopwatchSystem.out.println("tham around after");return retVal;}
}

放一张整体的流程图,方便我们查看知通构建的整体流程。

在这里插入图片描述

wrapIfNecessary方法的实现流程

1、首先判断bean是否需要被代理,如果不需要,直接返回原始bean实例 。

2、如果需要代理,则获取bean所有的advisor,并根据advisor的pointcout对bean进行匹配,得到所有需要拦截的方法 。

3、根据bean的类型和配置信息,决定使用哪种类型的代理对象,CGLIB或者JDK动态代理 。

4、将advisor和代理对象绑定,并将代理对象返回。

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {// targetSource是干嘛得if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {return bean;}// advisedBeans不会进行代理if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {return bean;}// 什么情况会shouldSkip,提前解析切面if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {this.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;}// Create proxy if we have advice.Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);if (specificInterceptors != DO_NOT_PROXY) {this.advisedBeans.put(cacheKey, Boolean.TRUE);// 创建代理对象Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));this.proxyTypes.put(cacheKey, proxy.getClass());return proxy;}this.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;}

findCandidateAdvisors获取候选的Advisors

获取候选的Advisors是使用的子类(AnnotationAwareAspectJAutoProxuCreator)的实现,然后我们的基于注解的Aspect(ThamNotVeryUsefulAspect)会执行this.aspectJAdvisorsBuilder.buildAspectJAdvisors()来添加

@Overrideprotected List<Advisor> findCandidateAdvisors() {// Add all the Spring advisors found according to superclass rules.List<Advisor> advisors = super.findCandidateAdvisors();// Build Advisors for all AspectJ aspects in the bean factory.if (this.aspectJAdvisorsBuilder != null) {advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());}return advisors;}

构建AspectJ的Advisors

首先在BeanFactoryAspectJAdvisorsBuilder的this.advisorFactory.getAdvisors(factory)打个断点

在这里插入图片描述

然后我断点进来之后我就发现了这个其实是有顺序的,也就是说明在这个内部实现了第一次排序。

在这里插入图片描述

第一次排序

接下来会进入ReflectiveAspectJAdvisorFactory的getAdvisors方法。其中核心就是for循环中的getAdvisorMethods方法。

public List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory aspectInstanceFactory) {Class<?> aspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();String aspectName = aspectInstanceFactory.getAspectMetadata().getAspectName();validate(aspectClass);// We need to wrap the MetadataAwareAspectInstanceFactory with a decorator// so that it will only instantiate once.MetadataAwareAspectInstanceFactory lazySingletonAspectInstanceFactory =new LazySingletonAspectInstanceFactoryDecorator(aspectInstanceFactory);List<Advisor> advisors = new ArrayList<>();//遍历切面方法,这里会把标注了@Pointcut 注解的排除掉,只剩下通知注解,如@Before,@Afterfor (Method method : getAdvisorMethods(aspectClass)) {Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, 0, aspectName);if (advisor != null) {advisors.add(advisor);}}// If it's a per target aspect, emit the dummy instantiating aspect.if (!advisors.isEmpty() && lazySingletonAspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) {Advisor instantiationAdvisor = new SyntheticInstantiationAdvisor(lazySingletonAspectInstanceFactory);advisors.add(0, instantiationAdvisor);}// Find introduction fields.for (Field field : aspectClass.getDeclaredFields()) {Advisor advisor = getDeclareParentsAdvisor(field);if (advisor != null) {advisors.add(advisor);}}return advisors;}// 遍历切面方法时进行排序,可以理解为第一次排序private List<Method> getAdvisorMethods(Class<?> aspectClass) {List<Method> methods = new ArrayList<>();ReflectionUtils.doWithMethods(aspectClass, methods::add, adviceMethodFilter);if (methods.size() > 1) {methods.sort(adviceMethodComparator);}return methods;}

首先会排除@PointCut的方法

private static final MethodFilter adviceMethodFilter = ReflectionUtils.USER_DECLARED_METHODS.and(method -> (AnnotationUtils.getAnnotation(method, Pointcut.class) == null));

然后创建了一个比较器

private static final Comparator<Method> adviceMethodComparator;static {// Note: although @After is ordered before @AfterReturning and @AfterThrowing,// an @After advice method will actually be invoked after @AfterReturning and// @AfterThrowing methods due to the fact that AspectJAfterAdvice.invoke(MethodInvocation)// invokes proceed() in a `try` block and only invokes the @After advice method// in a corresponding `finally` block.Comparator<Method> adviceKindComparator = new ConvertingComparator<>(new InstanceComparator<>(Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class),(Converter<Method, Annotation>) method -> {AspectJAnnotation<?> ann = AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(method);return (ann != null ? ann.getAnnotation() : null);});Comparator<Method> methodNameComparator = new ConvertingComparator<>(Method::getName);adviceMethodComparator = adviceKindComparator.thenComparing(methodNameComparator);}

很明显是按照Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class的顺序进行顺序的构建。

第二次排序

根据AbstractAdvisorAutoProxyCreator的findEligibleAdvisors的代码可知,第一次排序发生在findCandidateAdvisors方法。第二次排序则发生在次方法中的sortAdvisors。

protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {List<Advisor> candidateAdvisors = findCandidateAdvisors();// Around before after afterReturing afterThrowingList<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);extendAdvisors(eligibleAdvisors);if (!eligibleAdvisors.isEmpty()) {// 这里是通过order进行排序的eligibleAdvisors = sortAdvisors(eligibleAdvisors);}return eligibleAdvisors;}

第一次排序是对一个Aspect中的所有advice进行排序,第二次排序是对Aspect进行排序,可以通过实现Order接口或者@Order注解来设置顺序,如果没有实现order的话,是以加载的顺序来的。一般情况下加载的顺序可能不可控,所以如果有必要的话需要实现order。

创建两个Aspect,然后都没有实现@Order

在这里插入图片描述

顺序是ThamNotVersyUsefulAspect在NotVeryUsefulAspect之前

在这里插入图片描述

但是如果我们把两个放一起,这个时候NotVeryUsefulAspect先加载就会在前面。

在这里插入图片描述

在这里插入图片描述

如果我们设置ThamNotVeryUsefulAspect的Order(98),NotVeryUsefulAspect的Order(99),Order小的将排在前面。

@Component
@Aspect
@Order(99)
public class NotVeryUsefulAspect {
}@Component
@Aspect
@Order(98)
public class ThamNotVeryUsefulAspect {
}

在这里插入图片描述

写在最后

本章主要描述构建通知的顺序,正在的执行过程将在下一章节进行分析。

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

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

相关文章

CDC实时数据同步

一丶CDC实时数据同步介绍 CDC实时数据同步指的是Change Data Capture&#xff08;数据变更捕获&#xff09;技术在数据同步过程中的应用。CDC技术允许在数据源发生变化时&#xff0c;实时地捕获这些变化&#xff0c;并将其应用到目标系统中&#xff0c;从而保持数据的同步性。…

电脑软件:推荐一款非常强大的pdf阅读编辑软件

目录 一、软件简介 二、功能介绍 1、界面美观&#xff0c;打开速度快 2、可直接编辑pdf 3、非常强大好用的注释功能 4、很好用的页面组织和提取功能 5、PDF转word效果非常棒 6、强大的OCR功能 三、软件特色 四、软件下载 pdf是日常办公非常常见的文档格式&#xff0c;…

手写 Promise(2)实例方法与静态方法的实现

一&#xff1a;什么是 Promise Promise 是异步编程的一种解决方案&#xff0c;其实是一个构造函数&#xff0c;自己身上有all、reject、resolve这几个方法&#xff0c;原型上有then、catch等方法。 Promise对象有以下两个特点。 &#xff08;1&#xff09;对象的状态不受…

大学兼职教师管理系统 用JAVA语言开发

一、项目介绍 基于VueSpringBootMySQL的大学兼职教师管理系统包含学生管理、教师管理、课程档案管理、课程评价管理、课程考勤管理、授课管理、课程成绩管理教龄/薪资分析可视化图表&#xff0c;还包含系统自带的用户管理、部门管理、角色管理、菜单管理、日志管理、数据字典管…

【pdf密码】为什么我的PDF文件不能复制文字?

大家现在接触PDF文件越来越多&#xff0c;有的时候在网上下载的PDF文件打开之后&#xff0c;发现选中文字之后无法复制。甚至其他功能也都无法使用&#xff0c;这是怎么回事&#xff1f;该怎么办&#xff1f; 当我们发现文件打开之后&#xff0c;编辑功能无法使用&#xff0c;很…

HTTP介绍 原理 消息结构 客户端请求 服务器响应 HTTP状态码

一、HTTP介绍二、HTTP工作原理HTTP三点注意事项 三、HTTP消息结构四、客户端请求消息五、服务器响应消息HTTP请求方法 七、HTTP响应头信息八、HTTP状态码&#xff08;HTTP Status Code&#xff09;下面是常见的HTTP状态码&#xff1a;HTTP状态码分类HTTP状态码列表 一、HTTP介绍…

LVS+keepalived高可用负载均衡集群

keepalived介绍 keepalived为LVS应运而生的高可用服务。LVS的调度器无法做高可用&#xff0c;于是keepalived这个软件。实现的是调度器的高可用。 但是keepalived不是专门为LVS集群服务的&#xff0c;也可以做其他代理服务器的高可用。 LVS高可用集群的组成 主调度器备调度器&…

C语言程序设计——题目:用*号输出字母C的图案。程序分析:可先用‘*‘号在纸上写出字母C,再分行输出。

题目&#xff1a;用*号输出字母C的图案。 程序分析&#xff1a;可先用*号在纸上写出字母C&#xff0c;再分行输出。 #include<stdio.h> int main() {printf(" *****\n");printf(" *\n");printf("*\n");printf("*\n");printf(&…

Ubuntu deadsnakes 源安装新版 python

前言 适用于 Ubuntu 安装 python3.11 等新版本。 因为比较常用并且不想重新编译就记录一下&#xff0c;方便以后面向CV安装。 安装 添加 deadsnakes ppa 源 sudo add-apt-repository ppa:deadsnakes/ppa更新 apt sudo apt update安装 python3.11 sudo apt install python…

监控与升级

文章目录 主要内容一.部署Metrics1.部署代码如下&#xff08;示例&#xff09;: 2.解释 二.升级控制平面1.先确定要升级的版本代码如下&#xff08;示例&#xff09;: 2.禁止master节点接受新调度代码如下&#xff08;示例&#xff09;: 3.驱逐master节点上的现有任务代码如下&…

PhpStorm快速注释与取消注释

ctrl / 单行注释 ctrl shift / 多行注释 重复以上操作&#xff0c;取消注释。

5G投资下降,遥遥领先的主流5G或被运营商抛弃,“假5G”更获青睐

虽然媒体仍然在宣扬5G的诸多领先技术优势&#xff0c;不过需要付钱的运营商已在行动中做出抉择&#xff0c;那就是放缓主流5G的投资&#xff0c;大举投资曾被称为“假5G”的低频5G&#xff0c;现实迫使运营商做出了如此选择。 媒体披露的数据指2022年中国的5G投资下滑了2.5%&am…

jsoup的使用

本文在写作过程中参考了官方文档&#xff0c;传送门。 一、jsoup概述 jsoup 是一款基于 Java 的HTML解析器&#xff0c;它提供了一套非常省力的API&#xff0c;不但能直接解析某个URL地址、HTML文本内容&#xff0c;而且还能通过类似于DOM、CSS或者jQuery的方法来操作数据&…

DevOps持续集成-Jenkins(4)

❤️作者简介&#xff1a;2022新星计划第三季云原生与云计算赛道Top5&#x1f3c5;、华为云享专家&#x1f3c5;、云原生领域潜力新星&#x1f3c5; &#x1f49b;博客首页&#xff1a;C站个人主页&#x1f31e; &#x1f497;作者目的&#xff1a;如有错误请指正&#xff0c;将…

Qt之自定义事件

在Qt中,自定义事件的步骤大概如下: 1.创建自定义事件,自定义事件需要继承QEvent 2.使用QEvent::registerEventType()注册自定义事件类型,事件的类型需要在 QEvent::User 和 QEvent::MaxUser 范围之间,在QEvent::User之前是预留给系统的事件 3.使用sendEvent() 和 postEv…

创建 Edge 浏览器扩展教程(上)

创建 Edge 浏览器扩展教程&#xff08;上&#xff09; 介绍开始之前后续步骤开始之前1&#xff1a;创建清单 .json 文件2 &#xff1a;添加图标3&#xff1a;打开默认弹出对话框 介绍 在如今日益数字化的时代&#xff0c;浏览器插件在提升用户体验、增加功能以及改善工作流程方…

搜维尔科技:Varjo-最自然和最直观的互动

创建真实生活虚拟设计 Varjo让你沉浸在最自然的混合和虚拟现实环境中。 世界各地的设计团队可以聚集在一个摄影现实的虚拟空间中,以真实的准确性展示新的概念-实时的讨论和迭代。这是一个充满无限创造潜力的新时代,加速了人类前所未有的想象力。 虚拟现实、自动反应和XR设计的…

用*画田字形状,numpy和字符串格式化都可以胜任

numpy的字符型元素矩阵&#xff0c;可以方便画&#xff1b;直接python字符串手撕&#xff0c;也可以轻巧完成。 (本笔记适合熟悉循环和列表的 coder 翻阅) 【学习的细节是欢悦的历程】 Python 官网&#xff1a;https://www.python.org/ Free&#xff1a;大咖免费“圣经”教程《…

Monocular arbitrary moving object discovery and segmentation 论文阅读

基本信息 题目&#xff1a;Monocular Arbitrary Moving Object Discovery and Segmentation 作者&#xff1a; 来源&#xff1a;BMVC 时间&#xff1a;2021 代码地址&#xff1a;https://github.com/michalneoral/Raptor Abstract 我们提出了一种发现和分割场景中独立移动的…

TDengine(taos)数据库导出历史数据

业务需求&#xff1a;导出某个站点的累计充电量&#xff0c;累计放电量&#xff0c;光伏总放电量&#xff0c;进线总功率的所有数据‘ 1、登录taos&#xff0c;使用存数据的库&#xff1b; 提示Database changed&#xff1b;即为使用成功&#xff1b; 2、找到你想要导出的字段…