Bean的解析与注册
本文探讨spring对bean解析,并注册到IOC容器的过程
一、ClassPathBeanDefinitionScanner类(遍历bean集合)
//类路径Bean定义扫描器扫描给定包及其子包protected Set<BeanDefinitionHolder> doScan(String... basePackages) {Assert.notEmpty(basePackages, "At least one base package must be specified");//创建一个集合,存放扫描到的BeanDefinition封装类Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();//遍历扫描所有给定的包路径for (String basePackage : basePackages) {//调用父类ClassPathScanningCandidateComponentProvider的方法//扫描给定类路径,获取符合条件的Bean定义
10 Set<BeanDefinition> candidates = findCandidateComponents(basePackage);//遍历扫描到的Beanfor (BeanDefinition candidate : candidates) {//获取@Scope注解的值,即获取Bean的作用域
14 ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);//为Bean设置作用域candidate.setScope(scopeMetadata.getScopeName());//为Bean生成名称
18 String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);//如果扫描到的Bean不是Spring的注解Bean,则为Bean设置默认值,//设置Bean的自动依赖注入装配属性等if (candidate instanceof AbstractBeanDefinition) {postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);}//如果扫描到的Bean是Spring的注解Bean,则处理其通用的Spring注解if (candidate instanceof AnnotatedBeanDefinition) {//处理注解Bean中通用的注解,在分析注解Bean定义类读取器时已经分析过AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);}//根据Bean名称检查指定的Bean是否需要在容器中注册,或者在容器中冲突
30 if (checkCandidate(beanName, candidate)) {BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);//根据注解中配置的作用域,为Bean应用相应的代理模式
33 definitionHolder =AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);beanDefinitions.add(definitionHolder);//向容器注册扫描到的Bean
37 registerBeanDefinition(definitionHolder, this.registry);}}}return beanDefinitions;}
上次主要分析了第10行findCandidateComponents(basePackage)方法
, 该方法主要是从给定的包路径中扫描符合过滤规则的Bean,并存入集合beanDefinitions
中。
接下来的步骤可以分为以下几个方面:
1.遍历bean集合2.获取@Scope注解的值,即获取Bean的作用域3.为Bean生成名称4.给Bean的一些属性设置默认值5.检查Bean是否已在IOC容器中注册6.根据Bean的作用域,生成相应的代理模式7.把Bean放入IOC容器中
二、获取@Scope注解的值,即获取Bean的作用域
首先来看下 获取Bean作用域的过程,主要是上面第14行ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
这段代码,我们继续跟踪进去:
AnnotationScopeMetadataResolver
类:
public ScopeMetadata resolveScopeMetadata(BeanDefinition definition) {//默认是singletonScopeMetadata metadata = new ScopeMetadata();if (definition instanceof AnnotatedBeanDefinition) {AnnotatedBeanDefinition annDef = (AnnotatedBeanDefinition) definition;//获取@Scope注解的值AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(annDef.getMetadata(), this.scopeAnnotationType);//将获取到的@Scope注解的值设置到要返回的对象中if (attributes != null) {metadata.setScopeName(attributes.getString("value"));//获取@Scope注解中的proxyMode属性值,在创建代理对象时会用到ScopedProxyMode proxyMode = attributes.getEnum("proxyMode");//如果@Scope的proxyMode属性为DEFAULT或者NOif (proxyMode == ScopedProxyMode.DEFAULT) {//设置proxyMode为NOproxyMode = this.defaultProxyMode;}//为返回的元数据设置proxyModemetadata.setScopedProxyMode(proxyMode);}}//返回解析的作用域元信息对象return metadata;}
Bean的作用域是通过@Scope注解实现的,我们先来看下@Scope注解的属性:
可以看到@Scope注解有三个属性,
value 属性就是我们常用的用来设置Bean的单例/多例
scopeName 作用域名称,如singleton、request
proxyMode 是用来设置代理方式的
这里的AnnotationAttributes是注解属性key-value的封装类,继承了LinkedHashMap,所以也是key-value形式的数据结构。
三、为Bean生成名称
回到上面ClassPathBeanDefinitionScanner类的doScan()方法中的第18行
, 把我们获取到的Bean的作用域赋值给Bean。
然后为Bean生成名字,代码String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
跟踪进去,在AnnotationBeanNameGenerator类
中:
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {if (definition instanceof AnnotatedBeanDefinition) {String beanName = determineBeanNameFromAnnotation((AnnotatedBeanDefinition) definition);if (StringUtils.hasText(beanName)) {// Explicit bean name found.return beanName;}}// Fallback: generate a unique default bean name.return buildDefaultBeanName(definition, registry);}
这段代码很好理解,先从注解中获取Bean的名称,如果注解中没有设置,那么生成一个默认的Bean的名称,通过ClassUtils.getShortName(beanClassName)生成一个类名的小写开头驼峰的名字,如studentController。
四、给Bean的一些属性设置默认值
主要是doScan()中的如下两个方法:
//如果扫描到的Bean不是Spring的注解Bean,则为Bean设置默认值,
//设置Bean的自动依赖注入装配属性等
if (candidate instanceof AbstractBeanDefinition) {postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);}
//如果扫描到的Bean是Spring的注解Bean,则处理其通用的Spring注解
if (candidate instanceof AnnotatedBeanDefinition) {//处理注解Bean中通用的注解,在分析注解Bean定义类读取器时已经分析过AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);}
首先看postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName):
跟踪进去会到如下方法:
public void applyDefaults(BeanDefinitionDefaults defaults) {//懒加载setLazyInit(defaults.isLazyInit());//加载模式setAutowireMode(defaults.getAutowireMode());//依赖检查setDependencyCheck(defaults.getDependencyCheck());//bean初始化方法setInitMethodName(defaults.getInitMethodName());setEnforceInitMethod(false);//bean销毁方法setDestroyMethodName(defaults.getDestroyMethodName());setEnforceDestroyMethod(false);}
这里应该很清晰了,给bean设置一些默认值,BeanDefinitionDefaults是一个Bean属性默认值的封装类,从该类获取各个属性的默认值,赋值给bean。
接着我们看AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate)
方法。
跟踪进去:
static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd, AnnotatedTypeMetadata metadata) {AnnotationAttributes lazy = attributesFor(metadata, Lazy.class);//如果Bean定义中有@Lazy注解,则将该Bean预实例化属性设置为@lazy注解的值if (lazy != null) {abd.setLazyInit(lazy.getBoolean("value"));}else if (abd.getMetadata() != metadata) {lazy = attributesFor(abd.getMetadata(), Lazy.class);if (lazy != null) {abd.setLazyInit(lazy.getBoolean("value"));}}//如果Bean定义中有@Primary注解,则为该Bean设置为autowiring自动依赖注入装配的首选对象if (metadata.isAnnotated(Primary.class.getName())) {abd.setPrimary(true);}//如果Bean定义中有@DependsOn注解,则为该Bean设置所依赖的Bean名称,//容器将确保在实例化该Bean之前首先实例化所依赖的BeanAnnotationAttributes dependsOn = attributesFor(metadata, DependsOn.class);if (dependsOn != null) {abd.setDependsOn(dependsOn.getStringArray("value"));}if (abd instanceof AbstractBeanDefinition) {AbstractBeanDefinition absBd = (AbstractBeanDefinition) abd;AnnotationAttributes role = attributesFor(metadata, Role.class);if (role != null) {absBd.setRole(role.getNumber("value").intValue());}AnnotationAttributes description = attributesFor(metadata, Description.class);if (description != null) {absBd.setDescription(description.getString("value"));}}}
这里主要是处理bean上一些常用的注解,如@Lazy、@Primary、@DependsOn。注释很清晰,这里就不赘言了。
五、检查Bean是否已在IOC容器中注册
跟踪doScan()中的第30行if (checkCandidate(beanName, candidate))
方法:
protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) throws IllegalStateException {//是否包含beanName了if (!this.registry.containsBeanDefinition(beanName)) {return true;}//如果容器中已经存在同名bean//获取容器中已存在的beanBeanDefinition existingDef = this.registry.getBeanDefinition(beanName);BeanDefinition originatingDef = existingDef.getOriginatingBeanDefinition();if (originatingDef != null) {existingDef = originatingDef;}//新bean旧bean进行比较if (isCompatible(beanDefinition, existingDef)) {return false;}throw new ConflictingBeanDefinitionException("Annotation-specified bean name '" + beanName +"' for bean class [" + beanDefinition.getBeanClassName() + "] conflicts with existing, " +"non-compatible bean definition of same name and class [" + existingDef.getBeanClassName() + "]");}
可以看到,其实是通过调用IOC容器的containsBeanDefinition(beanName)
方法,来判断该beanName是否已存在,而IOC容器实际上是一个map,这里底层其实就是通过调用map.containsKey(key)
来实现的。
六、为Bean应用相应的代理模式
跟踪doScan()中的definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
方法
static BeanDefinitionHolder applyScopedProxyMode(ScopeMetadata metadata, BeanDefinitionHolder definition, BeanDefinitionRegistry registry) {//获取注解Bean定义类中@Scope注解的proxyMode属性值ScopedProxyMode scopedProxyMode = metadata.getScopedProxyMode();//如果配置的@Scope注解的proxyMode属性值为NO,则不应用代理模式if (scopedProxyMode.equals(ScopedProxyMode.NO)) {return definition;}//获取配置的@Scope注解的proxyMode属性值,如果为TARGET_CLASS//则返回true,如果为INTERFACES,则返回falseboolean proxyTargetClass = scopedProxyMode.equals(ScopedProxyMode.TARGET_CLASS);//为注册的Bean创建相应模式的代理对象return ScopedProxyCreator.createScopedProxy(definition, registry, proxyTargetClass);}
这里就用到第二步中获取到的@Scope注解的proxyMode属性,然后为bean设置代理模式。
七、注册Bean到IOC容器中
跟踪doScan()中的第37行registerBeanDefinition(definitionHolder, this.registry);
方法
//将解析的BeanDefinitionHold注册到容器中public static void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)throws BeanDefinitionStoreException {// Register bean definition under primary name.//获取解析的BeanDefinition的名称String beanName = definitionHolder.getBeanName();//向IOC容器注册BeanDefinition
第9行 registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());// Register aliases for bean name, if any.//如果解析的BeanDefinition有别名,向容器为其注册别名String[] aliases = definitionHolder.getAliases();if (aliases != null) {for (String alias : aliases) {registry.registerAlias(beanName, alias);}}}
直接看第9行的代码,继续跟踪进去:
//向IOC容器注册解析的BeanDefiniton@Overridepublic void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)throws BeanDefinitionStoreException {// 校验 beanName 与 beanDefinition 非空Assert.hasText(beanName, "Bean name must not be empty");Assert.notNull(beanDefinition, "BeanDefinition must not be null");//校验解析的BeanDefinitonif (beanDefinition instanceof AbstractBeanDefinition) {try {((AbstractBeanDefinition) beanDefinition).validate();}catch (BeanDefinitionValidationException ex) {throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,"Validation of bean definition failed", ex);}}BeanDefinition oldBeanDefinition;// 从容器中获取指定 beanName 的 BeanDefinitionoldBeanDefinition = this.beanDefinitionMap.get(beanName);// 如果已经存在if (oldBeanDefinition != null) {// 如果存在但是不允许覆盖,抛出异常if (!isAllowBeanDefinitionOverriding()) {throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,"Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +"': There is already [" + oldBeanDefinition + "] bound.");}// 覆盖 beanDefinition 大于 被覆盖的 beanDefinition 的 ROLE ,打印 info 日志else if (oldBeanDefinition.getRole() < beanDefinition.getRole()) {// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTUREif (this.logger.isWarnEnabled()) {this.logger.warn("Overriding user-defined bean definition for bean '" + beanName +"' with a framework-generated bean definition: replacing [" +oldBeanDefinition + "] with [" + beanDefinition + "]");}}else if (!beanDefinition.equals(oldBeanDefinition)) {if (this.logger.isInfoEnabled()) {this.logger.info("Overriding bean definition for bean '" + beanName +"' with a different definition: replacing [" + oldBeanDefinition +"] with [" + beanDefinition + "]");}}else {if (this.logger.isDebugEnabled()) {this.logger.debug("Overriding bean definition for bean '" + beanName +"' with an equivalent definition: replacing [" + oldBeanDefinition +"] with [" + beanDefinition + "]");}}// 允许覆盖,直接覆盖原有的 BeanDefinition 到 beanDefinitionMap 中。this.beanDefinitionMap.put(beanName, beanDefinition);}else {// 检测创建 Bean 阶段是否已经开启,如果开启了则需要对 beanDefinitionMap 进行并发控制if (hasBeanCreationStarted()) {// Cannot modify startup-time collection elements anymore (for stable iteration)//注册的过程中需要线程同步,以保证数据的一致性(因为有put、add、remove操作)
64 synchronized (this.beanDefinitionMap) {this.beanDefinitionMap.put(beanName, beanDefinition);List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);updatedDefinitions.addAll(this.beanDefinitionNames);updatedDefinitions.add(beanName);this.beanDefinitionNames = updatedDefinitions;// 从 manualSingletonNames 移除 beanNameif (this.manualSingletonNames.contains(beanName)) {Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);updatedSingletons.remove(beanName);this.manualSingletonNames = updatedSingletons;}}}else {// Still in startup registration phasethis.beanDefinitionMap.put(beanName, beanDefinition);this.beanDefinitionNames.add(beanName);this.manualSingletonNames.remove(beanName);}this.frozenBeanDefinitionNames = null;}//检查是否有同名的BeanDefinition已经在IOC容器中注册
88 if (oldBeanDefinition != null || containsSingleton(beanName)) {//更新beanDefinitionNames 和 manualSingletonNamesresetBeanDefinition(beanName);}}
这里就是向IOC容器中注册bean的核心代码,这段代码很长,分开来看,主要分为几个步骤:
1.beanName和beanDefinition的合法性校验
2.根据beanName从IOC容器中判断是否已经注册过
3.根据isAllowBeanDefinitionOverriding变量来判断是否覆盖
4.如果存在根据覆盖规则,执行覆盖或者抛出异常
5.如果不存在,则put到IOC容器beanDefinitionMap中
private final Map<String,BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
到这里,注册bean到IOC容器的过程就基本结束了,实际上IOC注册不是什么神秘的东西,说白了就是把beanName和bean存入map集合中
此时我们再返回看第七步的代码BeanDefinitionReaderUtils
类的registerBeanDefinition()
方法,可以看到 registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition())
;
已经分析完了,剩下的就是把bean的别名也注册进去就大功告成了。
//将解析的BeanDefinitionHold注册到容器中public static void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)throws BeanDefinitionStoreException {// Register bean definition under primary name.//获取解析的BeanDefinition的名称String beanName = definitionHolder.getBeanName();//向IOC容器注册BeanDefinitionregistry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());// Register aliases for bean name, if any.//如果解析的BeanDefinition有别名,向容器为其注册别名String[] aliases = definitionHolder.getAliases();if (aliases != null) {for (String alias : aliases) {registry.registerAlias(beanName, alias);}}}
八、总结
IoC容器其实就是DefaultListableBeanFactory
,它里面有一个map类型的beanDefinitionMap
变量,来存储注册的bean
IoC容器初始化过程:
1、资源定位
扫描包路径下.class文件,将资源转为Resource
2、资源加载
通过ASM框架获取class元数据,封装到BeanDefinition
3、资源解析
获取bean上注解的属性值。如@Scope
4、生成Bean
生成beanName,设置Bean默认值(懒加载、初始化方法等)、代理模式5、注册Bean
把BeanDefinition放入IoC容器DefaultListableBeanFactory
Bean的解析与注册相关推荐
- 【栖梧-源码-spring】@Bean从解析到注册到beanDefinitionMap
[栖梧-源码-spring]@Bean从解析到注册到beanDefinitionMap 序幕 源码阅读技巧 本文说明 类 ConfigurationClassParser#doProcessConfi ...
- Spring IOC原理 Bean标签解析和Definition封装
以下源码版本是 Spring 5.2.x IOC Inversion of Control 控制反转,关键实现是DI Dependency Injection,就必然涉及到有一个容器保存系统中所有托管 ...
- spring beans源码解读之--bean definiton解析器
spring提供了有两种方式的bean definition解析器:PropertiesBeanDefinitionReader和XmLBeanDefinitionReader即属性文件格式的bean ...
- 手写简版spring --5--资源加载器解析文件注册对象
一.目标 在完成 Spring 的框架雏形后,现在我们可以通过单元测试进行手动操作Bean对象的定义.注册和属性填充,以及最终获取对象调用方法.但这里会有一个问题,就是如果实际使用这个 Spring ...
- 框架源码系列九:依赖注入DI、三种Bean配置方式的注册和实例化过程
一.依赖注入DI 学习目标 1)搞清楚构造参数依赖注入的过程及类 2)搞清楚注解方式的属性依赖注入在哪里完成的. 学习思路 1)思考我们手写时是如何做的 2)读 spring 源码对比看它的实现 3) ...
- spring中默认标签Bean标签解析一
在Spring种有两种标签,一种是默认标签,另一种是自定义标签.对两种标签的用法和解析方式存在着很大的不同. 首先分析的是默认标签的解析过程. 解析标签的入口代码 protected void par ...
- 2、组件注册-@Configuration@Bean给容器中注册组件
2.组件注册-@Configuration&@Bean给容器中注册组件 2.1 创建maven项目 spring-annotation pom.xml文件添加 spring-context 依 ...
- 手写Spring-第二章-实现 Bean 的定义、注册、获取
前言 上一章我们实现了一个简化的Bean容器,那么这一章开始,我们就要上一些强度了.这一章主要的目的是,让Spring容器来自动化实现Bean的创建,并且实现单例Bean的复用.在上一章,我们只实现了 ...
- 手写简版spring --2--实现Bean的定义、注册、获取
一.目标 在上一章节我们初步依照 Spring Bean 容器的概念,实现了一个粗糙版本的代码实现.那么本章节我们需要结合已实现的 Spring Bean 容器进行功能完善,实现 Bean 容器关于 ...
最新文章
- python 函数返回值的特殊情况
- 以太坊和EOS的DApps数量飙升但用户量滞后
- 1073 Scientific Notation (20 分)【难度: 一般 / 知识点: 字符串 模拟】
- iOS - 使用 SQLite 数据库实现数据持久化
- founder of girton college
- buck电路matlab,buck变换器介绍_buck变换器matlab仿真
- 处理JS异常的一个想法
- 编写jmeter测试用例_Jmeter性能测试系列篇(十)--批量用例执行结果检查设置
- Syntax error, parameterized types are only available if source level is 1.5 or greater
- .NET Core 取消令牌:CancellationToken
- html5 input select,【Web前端问题】select如何实现既可以像input那样支持输入,又可以从下拉中选择? antd...
- [置顶] EasyUI提交表单
- CCS6的graph变灰解决办法
- 谷歌五笔输入法电脑版_不背字根,如何三天学会五笔输入法
- 代理记账和专职会计哪个更适合企业?
- SASS的安装及简单操作
- 10种优化Mac以获得最佳性能的简便方法
- java sub_java调用zeromq PUB-SUB模式
- 趣味小游戏——扫雷(优化版)
- vmbox设置ubuntu共享文件夹_为什么共享文件夹、打印机访问还是受限?这几个设置解决90%问题...