编程开源技术交流,分享技术与知识

网站首页 > 开源技术 正文

一文彻底让你明白Spring注解的实现原理

wxchong 2024-07-07 00:13:39 开源技术 13 ℃ 0 评论

Spring注解的实现原理其实分很多种情况,我们这里不概括所有的通用的流程,而是结合具体的注解来具体分析。

一,@Configuration注解的实现原理

上图:

下面文字解读一下大体流程:

第一步:Spring容器初始化时,会注册一个后置处理器,解析@Configuration注解的后置处理器是ConfigurationClassPostProcessor。

第二步:Spring容器初始化时,当执行refresh()方法时,会调用第一步注册的后置处理器ConfigurationClassPostProcessor。

第三步:ConfigurationClassPostProcessor后置处理器借助ConfigurationClassParser完成配置类解析。

第四步:ConfigurationClassParser配置类解析过程中会完成嵌套注解,迭代解析扫描包下面的class类的@Component注解的解析。

第五步,注册bean到spring容器。

二,AnnotationConfigApplicationContext注解处理器实现原理

AnnotationConfigApplicationContext注解处理器是Spring boot提供的一个比较常用的注解处理器。我们先看看它的工作原理。

第一步:初始化读取器AnnotationBeanDefinitionReader

第二步:初始化扫描器AnnotationBeanDefinitionScanner

第三步:调用父类GenericApplicationContext的无参构造方法初始化一个BeanFactory。这里初始化使用的是DefaultListableBeanFactory。

第四步:注册Bean配置类

第五步:刷新上下文

这里注册Bean配置类是关键,也是最复杂的一块,我们来单独过一遍。

三,AnnotationConfigApplicationContext注册Bean详细流程

AnnotationConfigApplicationContext注册Bean的具体实现是下面这行代码:

this. reader. register(annotatedGlasses);

这里的reader就是我们上面提到的读取器AnnotatedBeanDefinitionReader,register(annotatedGlasses)方法的具体实现就在这个读取器中。

接下来我们分步骤来看一下register方法的具体实现细节。

第一步:解析Bean注解信息。AnnotationConfigApplicationContext使用AnnotationGenericBeanDefinition来解析Bean的注解信息。解析完成以后得到一个AnnotationGenericBeanDefinition对象abd。

第二步:解析Bean作用域scope及scopeProxyMode。

使用默认的Bean作用域处理器ScopeMetaDataResolver解析得到Bean的scope信息。默认情况下,作用域是singleton,scopeProxyMode是NO。也就是说这个Bean是单例的,并且不使用代理模式。如果我们通过Scope注解指定了作用域的话,则使用我们指定的作用域。

不同的作用域,在解析注解,注册Bean的时候并没有区别。但是在生成Bean的时候就有区别了,以singleton为例,会先判断是否已经生成过实例,如果已经生成过,就不再生成,而是直接返回。

第三步:解析beanName。

这里有个beanName的处理细节在这里说一下。如果我们通过注解的方式指定了beanName,则直接使用我们指定的beanName。如果我们没有指定beanName,Spring会获取class的名字,然后生成一个beanName。

第四步,解析其他Bean的注解信息。

借助AnnotationConfigUtils工具类解析注解的其他可选属性。

主要包括lazyInit属性、primary属性、dependsOn属性、role属性、description属性。

第五步:BeanDefinitionHolder

BeanDefinitionHolder可以看做是对Bean的注解信息的再次封装。为什么要用BeanDefinitionHolder再次封装注解信息呢?我们来看一下BeanDefinitionHolder都做了什么。

首先,增加了一个属性aliases,这是一个字符串数组。

然后,会判断是否需要生成scope作用域的代理,由于默认是不生成作用域代理,直接把BeanDefinitionHolder对象返回了。也就是说仅仅是加了一个属性aliases。

经过BeanDefinitionHolder的处理以后,bean的定义信息结构如下:

beanName

BeanDefinition对象

aliases字符串数组

最后,带着这个BeanDefinitionHolder对象,开始执行Bean的注册。

第六步:注册Bean主流程。

这个流程是关键点,也是最复杂的。

首先,注册的关键类就是这个BeanDefinitionRegistry。这是一个接口,实现了AliasRegistry接口。BeanDefinitionRegistry接口定义了7个方法,都是对BeanDefinition的操作,包括注册Bean,删除Bean等一系列操作。这里我们重点关注注册Bean的方法registerBeanDefinition()。

registerBeanDefinition()方法进来,首先会进行一个校验,这个校验是一个合法性校验。为什么要做这个校验,目前我没研究清楚,有清楚的不妨留言交流,不胜感激。

校验完成以后,接着会查询beanDefinitionMap,如果不存在,接下来才会注册bean。如果存在,会有一系列的逻辑判断,因为我们注入的bean通常不会执行这段逻辑,这里略过。

接下来就是注册Bean了,注册Bean有三个操作。

第一个操作:把当前需要注册的Bean插入beanDefinitionMap,这个map就是Spring存放Bean的容器。这个map的实现是ConcurrentHashMap,初始容量是256。

键值对的key是beanName,value是beanDefinition。

第二个操作:把beanName插入beanDefinitionNames,beanDefinitionNames是一个ArrayList容器,初始容量也是256。

第三个操作:把当前这个Bean从manualSingletonNames容器中移除。manualSingletonNames是一个LinkedHashSet容器,初始容量是16。

OK,到这里,Bean的注册就算结束了。

注册完Bean以后,还有一些其他细节,比如,把frozenBeanDefinitionNames这个数组置为空。至于为什么,目前我还没有研究清楚。但是这些不影响我们对主流程的把控。这些细节,稍后会再次研究,然后另起一篇文章来说明,这里不再赘述。

四,疑惑

DefaultListableBeanFactory定义了下面2个容器,作用是什么呢?

1,allBeanNameByType

2,singletonBeanNameByType

这2个容器的实现是ConcurrentHashMap,初始容量为64。

这个问题我们后续研究,大家有兴趣,也可以自己研究一下。

以上是我的分享,感谢你耐心看完文章,想要学习更多关于Java方面的知识的朋友可以进我自己的后端技术群,群里有很多Java架构资料,大家可以进群免费领取资料,群号:680075317,也可以进群一起交流,比如遇到技术瓶颈、面试不过的,大家一些交流学习!

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表