Spring-Aop源码解析(下)

Spring-Aop源码解析(中)

        上文中解析了Aop中的匹配规则是怎样的,我定义一个Advisor,是如何可以切到我想要的方法或者Bean类从而去生成代理对象,对原生代码进行横向的逻辑插入

        本文来解析Spring是如何支持Aop的,因为我们开发中常用的@Before,@After这些注解并不是Spring的东西,而是org.aspectj.lang.annotation包下的,那么Spring是如何引入并且支持这些注解的

        我们通常在启动Spring项目的时候,会在启动类上加上@EnableAspectJAutoProxy注解来标明开启Aop功能,支持Aspectj模块

        这个注解里面引入了一个AspectJAutoProxyRegistrar.class类,这个类实现了ImportBeanDefinitionRegistrar接口,(ImportBeanDefinitionRegistrar的注册逻辑可以参考Spring配置类源码解析(上)),在ImportBeanDefinition方法中注册了AnnotationAwareAspectJAutoProxyCreator类型的BeanDefinition,这个玩意继承了AbstractAutoProxyCreator,他的父类是一个BeanPostProcessor,在其初始化后的方法中(AbstractAutoProxyCreator.postProcessAfterInitialization),有着支持Aspectj的功能,源码如下

	protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {// 找到所有的Advisor,调用子类的方法AnnotationAwareAspectJAutoProxyCreator.findCandidateAdvisors()//第一步找Spring中的Advisor//第二步找切面类中和Aspectj有关的注解,转换成Advisor再加进去List<Advisor> candidateAdvisors = findCandidateAdvisors();// 进行筛选List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);extendAdvisors(eligibleAdvisors);// 对Advisor进行排序,按Ordered接口、@Order注解进行排序if (!eligibleAdvisors.isEmpty()) {eligibleAdvisors = sortAdvisors(eligibleAdvisors);}return eligibleAdvisors;}

第一步,先找到所有的Advisor(包括Spring中的Advisor,以及Aspectj定义的那些注解)

第二步,对这些Advisor进行筛选,根据我们上文讲的筛选规则(类和方法匹配)去筛选合适的Advisor

第三步,排序这些Advisor,每一个Advisor都有其实现的顺序,到底该在何时插入代理逻辑

下面是查找所有Advisor的逻辑(源码中的代码只取精简部分,额外逻辑可以自己去研究下)

    1.找到Spring容器中现有的Advisor(BeanFactoryAdvisorRetrievalHelper.findAdvisorBeans())public List<Advisor> findAdvisorBeans() {//获取容器中所有的Advisor的namesString[] advisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, Advisor.class, true, false);this.cachedAdvisorBeanNames = advisorNames;List<Advisor> advisors = new ArrayList<>();for (String name : advisorNames) {advisors.add(this.beanFactory.getBean(name, Advisor.class));}return advisors;}2.找到切面类中定义的那些注解,并且将其转换成Advisor(BeanFactoryAspectJAdvisorsBuilder.buildAspectJAdvisors)public List<Advisor> buildAspectJAdvisors() {// aspectBeanNames是用来缓存BeanFactory中所存在的切面beanName的,第一次为null,后面就不为null了,不为null表示之前就已经找到过BeanFactory中的切面了synchronized (this) {List<Advisor> advisors = new ArrayList<>();// 把所有beanNames拿出来遍历,判断某个bean的类型是否是AspectString[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, Object.class, true, false);for (String beanName : beanNames) {Class<?> beanType = this.beanFactory.getType(beanName, false);//是否是切面。加了@Aspect注解的Beanif (this.advisorFactory.isAspect(beanType)) {// 利用BeanFactoryAspectInstanceFactory来解析Aspect类MetadataAwareAspectInstanceFactory factory =new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);//核心逻辑,获取切面Bean中的AdvisorList<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);// 并且会将该切面中所对应的Advisor对象进行缓存this.advisorsCache.put(beanName, classAdvisors);advisors.addAll(classAdvisors);}}return advisors;}}

        结合源码可以得知,想要获取切面中的Advisor,首先他会把Spring单例池中所有的BeanName全部拿出来,然后挨个判断是否是切面Bean,如果是,那么根据这个BeanName构造一个MetadataAwareAspectInstanceFactory ,然后调用getAdvisor方法去获取切面中的Advisor,下面是getAdvisor中的核心源码

    	//切面中是否含有@PointCut注解	for (Method method : getAdvisorMethods(aspectClass)) {//获取AdvisorAdvisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, 0, aspectName);if (advisor != null) {advisors.add(advisor);}}1.找到没有PointCut的方法private List<Method> getAdvisorMethods(Class<?> aspectClass) {List<Method> methods = new ArrayList<>();// 拿到切面类中所没有加@Pointcut的方法(注意,是没有加Pointcut的所有方法)//adviceMethodFilter:pointCut.class == null//然后将这些方法加到methods这个list里面ReflectionUtils.doWithMethods(aspectClass, methods::add, adviceMethodFilter);// 对方法进行排序,按注解和方法名字进行排序if (methods.size() > 1) {//adviceMethodComparator:Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.classmethods.sort(adviceMethodComparator);}return methods;}2.上面的那个methods中接着过滤只加了Aspectj的那些方法public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aspectInstanceFactory,int declarationOrderInAspect, String aspectName) {//getPointcut里面会匹配加了Pointcut.class, Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class//上面这些注解的方法才会被匹配出来,然后生成PointCut// 拿到当前方法所对应的Pointcut对象,但是注意:如果当前方法上是这么写的@After("pointcut()"),那么此时得到的Pointcut并没有去解析pointcut()得到对应的表达式AspectJExpressionPointcut expressionPointcut = getPointcut(candidateAdviceMethod, aspectInstanceFactory.getAspectMetadata().getAspectClass());// expressionPointcut是pointcut// candidateAdviceMethod承载了advice//将method和Advice合成一个Advisorreturn new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod,this, aspectInstanceFactory, declarationOrderInAspect, aspectName);}}

上面的代码总共分为两部分

        1.getAdvisorMethods中先把没有加PointCut的注解的方法过滤出来,这时候里面就相当于把Aspectj中大部分的普通方法这些也都加进去了

        2.getAdvisors中的getPointCut方法中,会将这些方法进行下一步的过滤,会将只加了Aspecj相关注解的那些方法过滤出来,然后将该方法转换成Advice,接着和PointCut拼凑成完整的Advisor,但是这里method还没有转换成Advice,在InstantiationModelAwarePointcutAdvisorImpl的构造方法中我们可以找到Aspectj注解转换成Advisor的逻辑,路径如下InstantiationModelAwarePointcutAdvisorImpl的构造方法->InstantiationModelAwarePointcutAdvisorImpl.instantiateAdvice->ReflectiveAspectJAdvisorFactory.getAdvice,我们截取部分源码即可看到,拿到这些Advisor之后,就会回到初始化后的方法中,然后根据他的class和method去匹配我需要的Advisor,得到这些Advisor之后(上文Spring-Aop源码解析(中)在方法执行的时候会留有疑惑,这些Advisor是怎么来的,本文这里就解答了),会调用ProxyFactory.getProxy去为正在生成的Bean生成一个代理对象,然后放到Spring的单例池中

case AtAround:// @AroundspringAdvice = new AspectJAroundAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);break;
case AtBefore:springAdvice = new AspectJMethodBeforeAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);break;

总结:

        Spring支持开启Aop需要在启动类上加上EnableAspectJAutoProxy注解,这个注解里面注册了一个BeanPostProcessor(AnnotationAwareAspectJAutoProxyCreator),Spring中的所有Bean在生成的时候都会经过这里面的初始化后方法,会拿着得到的Advisor去匹配我正在生成的Bean是否需要代理,如果匹配到了至少一个Advisor,那么就会生成代理对象,然后注意,在方法执行的时候,也会去匹配一遍Advisor,再次对过滤得到的Advisor进行一遍过滤,就比如我在UserService类中的A方法加了@Transactional的注解,B方法没有加,那么我的UserService就会生成一个代理对象,我在执行B方法的时候不需要事务,所以执行B方法的时候不需要额外的代理逻辑,所以还得再匹配一遍,更多的场景读者可以自己思考。

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

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

相关文章

【Unity】Unity项目启动时报找不到Git

【背景】 在一台新机器上云同步已存在的项目后&#xff0c;打开时报找不到Git&#xff0c;导致项目退出。 【分析】 package manager中有一个导入package的功能就是用git clone&#xff0c;正好我的项目中有一个package就是用这种方法引入的。但是新机器上没有git&#xff0…

(九) 盘古UI,日期和时间选择控件,自定义多种场景!

(九) 盘古UI,日期和时间选择控件,自定义多种场景! 盘古UI,较为全面的自定义UI框架,帮助你绝对的快速开发!(长期维护中) 控件位置: 主要控件: com.smart.pangu_ui_lib.widget.PanguSelectDateView 内部使用的时间弹窗:pop: com.smart.pangu_ui_lib.pop.PopSelectDate demo地址…

nginx反向代理.NetCore开发的基于WebApi创建的gRPC服务

一、本文中使用的工具: Vs2022使用.NET 8.0开发基于ASP.NET Core WebApi的gRPC服务; Nginx:1.25.5,下载地址:http://nginx.org/en/download.html 二、gRPC介绍: 由 google 开发,是一款语言中立、平台中立、开源的远程过程调用(RPC)系统。在vs2022中可以直接创建gRP…

设计模式之模板方法模式详解(下)

3&#xff09;钩子方法的使用 1.概述 钩子方法的引入使得子类可以控制父类的行为。 2.结构图 3.代码实现 将公共方法和框架代码放在抽象父类中 abstract class DataViewer {//抽象方法&#xff1a;获取数据public abstract void GetData();//具体方法&#xff1a;转换数据…

【JAVA面试题】探索多线程同步:ReentrantLock与synchronized的对比解析

程序员如何搞副业&#xff1f; 文章目录 程序员如何搞副业&#xff1f;强烈推荐引言&#xff1a;ReentrantLock介绍可重入性&#xff08;Reentrancy&#xff09;&#xff1a;公平性&#xff08;Fairness&#xff09;&#xff1a;条件变量&#xff08;Condition&#xff09;&…

leetcode377--组合总数IV

1. 题意 给你一个由 不同 整数组成的数组 nums &#xff0c;和一个目标整数 target 。 请你从 nums 中找出并返回总和为 target 的元素组合的个数 2. 题解 与爬楼梯相似&#xff0c; 只是一次可以爬的阶梯数变多了&#xff0c;爬楼梯一次只能 c l i b [ 1 , 2 ] clib[1,2…

K8s: 关于Kubernetes中的Pod的生命周期(状态)以及生命周期的钩子函数处理

pod 的生命周期 1 ) pod 几种常用状态 1.1 &#xff09;Pending&#xff08;挂起&#xff09; Pod 已被 Kubernetes 系统接受&#xff0c;但有一个或者多个容器尚未创建亦未运行此阶段包括等待 Pod 被调度的时间和通过网络下载镜像的时间。 1.2 &#xff09;Running&#xff0…

C语言基础(入门综合案例)

学生信息管理系统 学员管理系统可以实现对学员的添加、全部显示、查询、修改、删除功能 数据存储格式说明 // 宏定义的常量&#xff0c;代表学生的最大个数 #define NUM 100// 结构体类型 struct stu 别名为 STU typedef struct stu {char name[30]; // 姓名int age; …

海外代理IP|Facebook对IP都有哪些要求?要选哪种?

众所周知&#xff0c;Facebook封号大多数情况都是因为IP的原因。Facebook对于用户账号有严格的IP要求和限制&#xff0c;以维护平台的稳定性和安全性。在这种背景下&#xff0c;海外IP代理成为了一种有效的解决方案&#xff0c;帮助用户避免检测&#xff0c;更加快捷安全地进行…

影响钕铁硼磁钢性能的因素及方法

钕铁硼永磁材料自问世以来&#xff0c;就以其优越的磁性能而备受关注&#xff0c;被称为“磁王“&#xff0c;在市场需求的不断地增长下&#xff0c;钕铁硼生产工艺及磁体性能也不断发展和提升。我们一般用剩磁、矫顽力和最大磁能积这几个指标来衡量磁性材料的磁性能。 剩磁 B…

C语言数据结构之链表

目录 前言 \color{maroon}{前言} 前言1.链表的概念及结构2.链表的分类3.无头单向非循环链表的实现4.带头双向循环链表的实现5.顺序表和链表的对比 前言 \color{maroon}{前言} 前言 在上一篇博客中我们提到&#xff0c;线性表包括顺序表和链表&#xff0c;顺序表在上篇博客中已…

力扣HOT100 - 25. K 个一组翻转链表

解题思路&#xff1a; class Solution {public ListNode reverseKGroup(ListNode head, int k) {ListNode dum new ListNode(0, head);ListNode pre dum;ListNode end dum;while (end.next ! null) {for (int i 0; i < k && end ! null; i) {end end.next;}if …

学习笔记-数据结构-线性表(2024-04-17)

设计一个算法实现在单链表中删除值相同的多余节点的算法。 设计思想&#xff1a;双指针 变量说明&#xff1a; head - 参数变量&#xff0c;代表链表的头节点。在调用DelSameNum函数时&#xff0c;需要传递链表的头节点的地址给这个参数&#xff0c;从而允许函数对链表进行操作…

P1747 好奇怪的游戏

好奇怪的游戏 题目背景 《爱与愁的故事第三弹shopping》娱乐章。 调调口味来道水题。 题目描述 爱与愁大神坐在公交车上无聊&#xff0c;于是玩起了手机。一款奇怪的游戏进入了爱与愁大神的眼帘&#xff1a;***&#xff08;游戏名被打上了马赛克&#xff09;。这个游戏类似…

类和对象(中)(构造函数、析构函数和拷贝构造函数)

1.类的六个默认成员函数 任何类在什么都不写时&#xff0c;编译器会自动生成以下6个默认成员函数。 //空类 class Date{}; 默认成员函数&#xff1a;用户没有显示实现&#xff0c;编译器会自动生成的成员函数称为默认成员函数 2.构造函数 构造函数 是一个 特殊的成员函数&a…

docker容器技术篇:centos7搭建docker swarm集群

centos7搭建docker swarm集群 一 docker swarm 概述 1.1 swarm简介 Docker Swarm是 Docker 的集群管理工具&#xff0c;Swarm 在 Docker 1.12 版本之前属于一个独立的项目&#xff1b;其主要作用是把Docker集群抽象为一个整体&#xff0c;并且通过一个统一管理这些 Docker 主…

密码学 | 数字证书:应用

&#x1f951;原文&#xff1a;数字签名和数字证书的原理解读 - 知乎 &#x1f951;前文&#xff1a;密码学 | 数字签名 数字证书 - CSDN &#x1f951;提示&#xff1a;把客户端想成 Alice&#xff0c;服务器端想成 Bob 即可。客户端实际上指的是客户端浏览器。 下面&#…

前端入门:HTML(CSS边框综合案例)

案例&#xff1a; 源代码&#xff1a; css-borders.html: <body> <div id"square"> </div> <br> <div id"triangle"> </div> <br> <div id"trapezium"> </div> <br> <div id…

【C语言】指针篇-深入探索数组名和指针数组(2/5)- 必读指南

&#x1f308;个人主页&#xff1a;是店小二呀 &#x1f308;C语言笔记专栏&#xff1a;C语言笔记 &#x1f308;C笔记专栏&#xff1a; C笔记 &#x1f308;喜欢的诗句:无人扶我青云志 我自踏雪至山巅 文章目录 认识指针与数组之间的关系(涉及二级指针)**数组名****指针访问…

面试后,公司如何决定你的去留

在现代职场中&#xff0c;求职者在经历了一系列严格的面试流程后&#xff0c;往往会进入一段等待期。在这段时间里&#xff0c;他们满怀希望地等待企业的最终反馈。但有一个现象普遍存在&#xff1a;无论面试过程如何&#xff0c;最终决定权总是掌握在公司手中&#xff0c;由公…