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

news2024/9/26 3:30:44

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

// AbstractAutoProxyCreator类
@Override
	public 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 expression
	private 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 stopwatch
		System.out.println("tham around before");
		Object retVal = pjp.proceed();
		// stop stopwatch
		System.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()来添加

@Override
	protected 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,@After
		for (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 afterThrowing
		List<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.coloradmin.cn/o/1130811.html

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

相关文章

通过怪物展示Demo理解游戏设计模式中的迭代器模式

点击上方亿元程序员关注和★星标 引言 大家好&#xff0c;我是亿元程序员&#xff0c;一位有着8年游戏行业经验的主程。 本系列是《和8年游戏主程一起学习设计模式》&#xff0c;让糟糕的代码在潜移默化中升华&#xff0c;欢迎大家关注分享收藏订阅。 今天我们要来聊一聊游戏…

开启生成式AI的探索之旅,亚马逊云科技分享生成式AI热门案例

现今&#xff0c;生成式AI为企业争先讨论的热门话题&#xff0c;上云出海为企业转型的重中之重。无论你是行业新贵还是中小企业&#xff0c;探索新的模式、创新迭代业务都是不容忽视的重点&#xff0c;下面就来介绍几个亚马逊云科技帮助企业创新的案例。 开启生成式AI的探索之旅…

深入理解 MySQL 中的锁和MVCC机制

文章目录 锁&#xff1a;数据访问的保护者1. 了解锁的基本概念2. 锁的使用场景3. 示例&#xff1a;MySQL中的锁 MVCC&#xff1a;多版本并发控制1. MVCC的工作原理2. MVCC的优点3. 示例&#xff1a;MySQL中的MVCC 如何选择合适的锁和MVCC1. 确定隔离级别2. 避免过度使用锁3. 监…

Spring IOC 和 AOP

核心概念 咱们这节就讲完了&#xff0c;在这节中我们讲了两个大概念&#xff0c;一个叫做IOC&#xff0c;一个叫做DI IOC是什么&#xff1f;是用对象的时候不要自己用new而是由外部提供&#xff0c;而spring在进行实现的时候是谁提供&#xff0c;就是IOC容器给你提供。 DI是什…

什么是脚本文件,脚本的执行,脚本格式等

1.脚本文件是什么&#xff1f; 脚本文件是包含一系列计算机命令的文本文件&#xff0c;通常用于自动化任务、自定义功能或执行特定操作。这些命令通常按照一定的编程语法和语义规则编写&#xff0c;以便计算机能够逐行解释和执行它们。脚本文件通常包含了一组操作&#xff0c;…

lua-web-utils和proxy设置示例

以下是一个使用lua-web-utils和proxy的下载器程序&#xff1a; -- 首先安装lua-web-utils库 local lwu require "lwu" ​ -- 获取服务器 local function get_proxy()local proxy_url "duoipget_proxy"local resp, code, headers, err lwu.fetch(proxy_…

工业通信网关常用的工业通信协议

在工业领域中常常有不同的设备协同工作&#xff0c;而这些设备的通信协议和数据格式也有所差异&#xff0c;要想实现不同通信设备之间的数据传输互通&#xff0c;工业网关是一个重要的设备。 什么是工业网关 工业网关是一种能够连接多种不同设备并实现数据的收集、传输、处理和…

Vuepress 三分钟搭建一个精美的文档或博客

大家好&#xff0c;我是凌览。 作为一个程序员&#xff0c;相信每个人都想折腾一个属于自己的博客。技术文章的输出是我们程序员能力的一种体现&#xff0c;也是一种非常好的个人总结。 网上有很多文档编写网站及工具&#xff0c;比如语雀、石墨等等。但多数需要付费&#xf…

工业RFID设备如何实现抗干扰功能?

在工业环境中存在多种干扰因素&#xff0c;如灰尘、水渍、油污等都会影响条码枪的信息识别&#xff0c;虽然RFID技术可以实现非接触的识别&#xff0c;无视油污、灰尘等环境&#xff0c;但仍会收到电流、震动、电磁信号等因素的干扰。下面我们就一起来了解一下&#xff0c;工业…

触摸屏与施耐德PLC之间MODBUS无线通讯

一、 硬件连接 1、PLC通讯接口说明&#xff1a; 2、通讯电缆图&#xff1a; 二、PLC设置 1. 配置端口&#xff1a; 双击串行线路—弹出右侧设置窗口---设置串口通讯参数 2. 添加MODBUS协议。 ① 右击串口线路&#xff0c;选择添加设备&#xff1a; ② 选择现场总线&#xf…

JAVA代码审计-纵向越权漏洞分析

查看这个cms系统后台管理员 添加用户的页面 点击添加管理员 这个模块只有管理员拥有&#xff0c;普通用户没有这个模块。 打开源码分析是否存在越权漏洞。 ------------------------------------------------------------------------------------------------------------ …

引领位置服务驱动:腾讯地图 WebService 服务端 API 实用指南

&#x1f52d; 嗨&#xff0c;您好 &#x1f44b; 我是 vnjohn&#xff0c;在互联网企业担任 Java 开发&#xff0c;CSDN 优质创作者 &#x1f4d6; 推荐专栏&#xff1a;Spring、MySQL、Nacos、Java&#xff0c;后续其他专栏会持续优化更新迭代 &#x1f332;文章所在专栏&…

SQL server中:常见问题汇总(如:修改表时不允许修改表结构、将截断字符串或二进制数据等)

SQL server中&#xff1a;常见问题汇总 1.修改表时提示&#xff1a;不允许修改表结构步骤图例注意 2.将截断字符串或二进制数据。3.在将 varchar 值 null 转换成数据类型 int 时失败。4.插入insert 、更新update、删除drop数据失败&#xff0c;主外键FOREIGN KEY 冲突5.列不允许…

华为昇腾NPU卡 大模型LLM ChatGLM2模型推理使用

参考&#xff1a;https://gitee.com/mindspore/mindformers/blob/dev/docs/model_cards/glm2.md#chatglm2-6b 1、安装环境&#xff1a; 昇腾NPU卡对应英伟达GPU卡&#xff0c;CANN对应CUDA底层&#xff1b; mindspore对应pytorch&#xff1b;mindformers对应transformers 本…

C语言程序设计——题目:一个整数,它加上100后是一个完全平方数,再加上168又是一个完全平方数,请问该数是多少?

题目&#xff1a;一个整数&#xff0c;它加上100后是一个完全平方数&#xff0c;再加上168又是一个完全平方数&#xff0c;请问该数是多少&#xff1f; 程序分析&#xff1a; 假设该数为 x。 1、则&#xff1a;x 100 n2, x 100 168 m2 2、计算等式&#xff1a;m2 - n2…

Spring Cloud Alibaba nacos配置中心

系列文章目录 第一章 Java线程池技术应用 第二章 CountDownLatch和Semaphone的应用 第三章 Spring Cloud 简介 第四章 Spring Cloud Netflix 之 Eureka 第五章 Spring Cloud Netflix 之 Ribbon 第六章 Spring Cloud 之 OpenFeign 第七章 Spring Cloud 之 GateWay 第八章 Sprin…

所求和问题

&#xff08; 1 2 3 ) 2 1 2 2 2 3 2 2 ∗ ( 1 ∗ 2 1 ∗ 3 2 ∗ 3 ) &#xff08;123)^21^22^23^22*(1*21*32*3) &#xff08;123)21222322∗(1∗21∗32∗3) n input() a list(map(int,input().split())) s1 sum(a) ** 2 s2 0 for i in a:s2 i ** 2 ans (s1 …

STM32F4 磁链观测器+PLL 无感无刷电机位置驱动

在前面几节我们介绍了基于stm32f1的定点运算的滑膜观测器反正切以及stm32f4的浮点运算滑膜PLL的案例&#xff0c;大家反馈的还挺好&#xff0c;在做售后的过程中有小伙伴咨询了磁链观测器和隆伯格观测器&#xff0c;针对磁链观测器咨询的较多&#xff0c;我们调试了磁链的程序&…

C语言每日一题(18)数组匹配

牛客网 BC156 牛牛的数组匹配 题目描述 描述 牛牛刚学会数组不久&#xff0c;他拿到两个数组 a 和 b&#xff0c;询问 b 的哪一段连续子数组之和与数组 a 之和最接近。 如果有多个子数组之和同样接近&#xff0c;输出起始点最靠左的数组。 输入描述&#xff1a; 第一行输…