【小白的Spring源码手册】 Bean的扫描、装配和注册,面试学习可用

目录

  • 前言
  • 源码学习
    • Bean配置
      • 1. 注解
      • 2. xml配置
    • Bean扫描、装配、注册
      • 1. 扫描
      • 2. 装配BeanDefinition
      • 3. 校验BeanDefinition
      • 4. 注册BeanDefinition
  • 总结


前言

如今Spring框架功能众多,每次打开Spring源码,要么就是自顶向下从整个框架来了解Spring整体流程,然后一不小心就陷入了细节无法自拔,要么就是从某个核心功能点看起,然后过了几分钟就陷入茫然。总之过程是十分的痛苦,结果却不尽人意。

这一次,我打算从最熟悉的功能模块IoC开始学习。IoC容器在Spring中扮演着重要作用,它控制了所有bean对象的整个生命周期。看源码时,接触到的第一个接口BeanFactory就是IoC容器的顶层接口,能控制对象生命周期;而ApplicationContext对前者进行了扩展,拥有事件发布、国际化信息支持等新特性。在这里,我打算先忽略IoC的顶层流程,根据Bean生成的过程,学习Bean在Spring中是如何被扫描、加载、生成、最后实例化。最后再将Bean的整个声明周期与IoC容器串起来。

通过扫描注解类的Bean实例化流程参考下图:
在这里插入图片描述

下面的源码解析中,省略了很多不必要的类、方法和代码块,将核心代码提了出来,以方便大家忽略非主流程的细节更专注于我们的核心流程。整个流程我已经提取出了可执行的核心代码,大家可以debug熟悉一下定义Bean的流程,地址见文末连接。





源码学习

Bean配置

spring要对bean进行管理,需要扫描相应配置,完成后进行统一管理,spring(5.0+)目前比较流程扫描方式为注解xml,相关配置如下:

1. 注解

配置包:

// spring
@ComponentScan(value = {"com.example.springreading",})
// spring boot,默认对启动类同包下的所有包进行扫描
@SpringBootApplication(scanBasePackages = {"com.example.springreading",})

在spring中,应用上下文会扫描被@Component @Repository @Controller @Service等被Component注解的类。只有被这些注解标注的类,才会被IoC容器管理。

AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext("com.example.springreading");

AnnotationConfigApplicationContext类中有两个关键字段:

  • AnnotatedBeanDefinitionReader reader:用于扫描指定class类(Class<?>… componentClasses)文件。
  • ClassPathBeanDefinitionScanner scanner:用于扫描指定包(String… basePackages)中文件。

它们两者的功能都是一样的,就是扫描类文件,并将Bean定义注册到内部的IoC容器。

// AnnotationConfigApplicationContext字段
private final AnnotatedBeanDefinitionReader reader;
private final ClassPathBeanDefinitionScanner scanner;
// AnnotationConfigApplicationContext父类GenericApplicationContext字段
private final DefaultListableBeanFactory beanFactory; // new DefaultListableBeanFactory()

注意:AnnotationConfigApplicationContext父类中是有维护一个IoC容器的,应用上下文的Bean操作都是通过该容器实现的。

通过类名初始化,AnnotationConfigApplicationContext类会通过reader来进行类扫描和注册,通过包名初始化,则会通过scanner

// reader
public AnnotationConfigApplicationContext(Class<?>... componentClasses) {this();register(componentClasses);refresh();
}
// scanner
public AnnotationConfigApplicationContext(String... basePackages) {this();scan(basePackages);refresh();
}
// 无参构造
public AnnotationConfigApplicationContext() {StartupStep createAnnotatedBeanDefReader = this.getApplicationStartup().start("spring.context.annotated-bean-reader.create");this.reader = new AnnotatedBeanDefinitionReader(this);createAnnotatedBeanDefReader.end();this.scanner = new ClassPathBeanDefinitionScanner(this);
}



2. xml配置

配置包:

<!-- spring.xml配置文件 -->
<!-- 注解依赖扫描 加载@Component等注解标注的bean 这样不用在下面添加bean标签来手动生成bean实例-->
<context:component-scan base-package="com.example.springreading"/>

xml配置文件扫描

ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("/xml/bean.xml");
// XmlBeanDefinitionReader是其内部的bean定义扫描类
XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(registry);

精力有限,本文仅学习注解有关类,xml原理是类似的,大家可以根据兴趣自行学习。





Bean扫描、装配、注册

这里选择通过包名初始化应用上下文,扫描、装配、注册等一系列操作均由ClassPathBeanDefinitionScanner scanner来实现的。

String[] basePackages = new String[]{"com.example.springreading"};
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(basePackages);



1. 扫描

scanner的扫描入口

int scan = scanner.scan(basePackages);
// 由doScan执行具体的扫描操作
doScan(basePackages);

遍历所有包,scanner会遍历所有包,依次加载包下面的所有class类

for (String basePackage : basePackages) {Set<BeanDefinition> candidates = findCandidateComponents(basePackage);// ……
}	

findCandidateComponents方法中,会将扫描到的class文件读取为Resource资源,然后将Resource资源转为元数据读取器MetadataReader,最后通过包装元数据的方式创建BeanDefinition

源码如下:

A. Resource类载入Java class文件:

String packageSearchPath = "classpath*:" + "com/example/springreading" + '/' + "**/*.class";
ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);

B. 生成元数据读取器(MetadataReader)

MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory();
// 遍历resources
MetadataReader metadataReader = metadataReaderFactory .getMetadataReader(resource);

在这里插入图片描述

C. 生成BeanDefinition:

ScannedGenericBeanDefinition beanDefinition= new ScannedGenericBeanDefinition(metadataReader);

到目前为止一个bean定义就被创建了,但是Bean的初始化还没完,我们还有很多bean相关的内容需要完善。



2. 装配BeanDefinition

BeanDefinition接口或实现类说明:

BeanDefinition:是Bean定义的顶层接口类。
AnnotatedBeanDefinition:该接口是BeanDefinition子类接口,用于承载注解内容。
ScannedGenericBeanDefinition:继承GenericBeanDefinition类,是AnnotatedBeanDefinition的实现之一。

在装配BeanDefinition这块,涉及到很多业务经常使用的注解,@Component、@Scope、@Lazy、@Primary、@DependsOn等注解都会在这个阶段被解析并装配进BeanDefinition。这些注解的解析和装配的过程类似。

源码参考:

A. @Scope(value = "singleton", proxyMode = ScopedProxyMode.DEFAULT)
设置作用域,这个bean作用范围是singleton还是prototype

ScopeMetadataResolver scopeMetadataResolver = new AnnotationScopeMetadataResolver();
// 作用域元数据
ScopeMetadata scopeMetadata = scopeMetadataResolver.resolveScopeMetadata(beanDefinition);
// 设置作用域
beanDefinition.setScope(scopeMetadata.getScopeName());

作用域这个注解,是通过Spring的一套公用的注解提取工具提取的,原理是通过当前AnnotatedBeanDefinition元数据读取器 读取元数据,以得到注解类,最后根据注解名字得到指定的注解以及注解的字段内容。

在这里插入图片描述

ScopeMetadataResolver#resolveScopeMetadata内部实现如下:

ScopeMetadata metadata = new ScopeMetadata();
AnnotatedBeanDefinition annDef = (AnnotatedBeanDefinition) beanDefinition;
// 获取元数据中的注解对象
AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(annDef.getMetadata(), Scope.class);
// 获取注解属性值并设置到作用域类中
metadata.setScopeName(attributes.getString("value"));
metadata.setScopedProxyMode(attributes.getEnum("proxyMode"));

B. @Component(value = "businessService")

设置bean的实例名称:beanName,通过读取注解的value来获取bean名,大小写敏感。

除了Component,还有很多注解可以实现beanName设置。譬如大家常见的@Controller、@Service、@Repository等等。

// 单例模式的类名生成器
BeanNameGenerator beanNameGenerator = AnnotationBeanNameGenerator.INSTANCE;
String beanName = beanNameGenerator.generateBeanName(beanDefinition, context);

要实现通过注解设置BeanName,只需要实现如下几个条件之一:

  • 使用@Component注解,设置value值
  • 使用@Component注解注解的注解,类似@Service,拥有value属性并设值
  • 使用@ManagedBean或@Named(年代有些久远就不提了),有value属性并设值
boolean isStereotype = annotationType.equals("org.springframework.stereotype.Component") ||metaAnnotationTypes.contains("org.springframework.stereotype.Component") ||annotationType.equals("javax.annotation.ManagedBean") ||annotationType.equals("javax.inject.Named");
// 注解的属性值,要有string类型的value属性,且属性值不能为空
AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(amd, type);

当上面的条件不满足时,beanName默认使用bean定义的类名,首字母小写

// 为空时的处理
String decapitalize = Introspector.decapitalize(ClassUtils.getShortName(Objects.requireNonNull(beanDefinition.getBeanClassName())));

C. 其它注解
@Lazy(value = false)
@DependsOn
@Primary
@Description(value = "Description of bean definition")
通过AnnotationConfigUtils工具来加载bean的一些通用bean注解,流程跟Scope注解是相同的。

AnnotationConfigUtils.processCommonDefinitionAnnotations(beanDefinition);
// 解析
AnnotationAttributes lazy = attributesFor(metadata, Lazy.class);
// ……



3. 校验BeanDefinition

在BeanDefinition装配完成后,需要通过AnnotationConfigApplicationContext context来校验是否存在beanName

// DefaultListableBeanFactory#containsBeanDefinition
boolean containsBeanDefinition = context.containsBeanDefinition(beanName);

在上下文校验代码中,由DefaultListableBeanFactory beanFactoryIoC容器内的Map<String, BeanDefinition> beanDefinitionMap完成最终校验(BeanDefinition最终都会被注册到beanDefinitionMap中)。

context.getBeanFactory().containsBeanDefinition(beanName);
// IoC容器中保存初始Bean定义的缓存
// Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
// this.beanDefinitionMap.containsKey(beanName)



4. 注册BeanDefinition

现在Bean的扫描、加载、定义、装配乃至校验都做完了,可以完成注册了。

// 使用BeanDefinitionHolder来包装,如果有别名的话,这里会添加Bean的别名名称,但是通过注解类扫描的Bean,没有别名
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(beanDefinition, beanName);

注册和校验相同,均由DefaultListableBeanFactory beanFactoryIoC容器完成,beanFactory会将该BeanDefinition保存至beanDefinitionMap中。

// 注册方法
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, context);
// AnnotationConfigApplicationContext#registerBeanDefinition
context.registerBeanDefinition(beanName, beanDefinition);
// DefaultListableBeanFactory#registerBeanDefinition
beanFactory.registerBeanDefinition(beanName, beanDefinition);
// 最后添加至缓存中
this.beanDefinitionMap.put(beanName, beanDefinition);





总结

有关Bean的扫描、装配、校验到包装到此就结束了,本文的重点虽然不在IoC容器,但是我们在进行Bean定义的过程中,我们仍然会接触到它,以小见大,从一些基础的功能开始慢慢了解整个Spring框架,我觉得这是一个很好的立足点。

接下来,我会从IoC容器实例化Bean的流程来进一步了解整个Spring IoC机制。

DEMO代码:https://github.com/VsLegend/spring-reading.git

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

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

相关文章

Servlet 初始化参数(web.xml和@WebServlet)

1、通过web.xml方式 <?xml version"1.0" encoding"UTF-8"?> <web-app xmlns"http://xmlns.jcp.org/xml/ns/javaee"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://xmlns.jcp.org/xm…

SpringMVC学习

一、SpringMvc 概述 1.什么是SpringMVC? ( •̀ ω •́ )✧&#xff1a; SpringMVC是基于MVC开发模式的框架&#xff0c;具备IOC和AOP MyBatis用来优化持久层&#xff0c; SpringMVC优化控制器。 spring是用来整合这两个框架 的框架

Maven项目管理

文章目录 Maven项目管理项目结构依赖导入依赖依赖作用域可选依赖排除依赖 继承关系常用命令测试项目打包项目 Maven项目管理 Maven 翻译为"专家"、“内行”&#xff0c;是 Apache 下的一个纯 Java 开发的开源项目。 基于项目对象模型&#xff08;缩写&#xff1a;P…

『OpenStack』云计算平台『Nova』计算服务学习指南

前言 本文将会讲解 OpenStack 平台计算服务组件 Nova &#xff0c;结合抽象概念和简单易懂的实战操作&#xff0c;帮助您更好的理解 Nova 计算服务在 OpenStack 中的作用 系统配置&#xff1a;宿主机 Ubuntu 20.04&#xff08;WSL2&#xff09; 简介 OpenStack 官网链接&…

KnowledgeGPT:利用检索和存储访问知识库上增强大型语言模型10.30

利用检索和存储访问知识库上增强大型语言模型 摘要引言2 相关研究3方法3.1 任务定义3.2 知识检索3.2.1 代码实现3.2.2 实体链接3.2.3 获取实体信息3.2.4 查找实体或值3.2.5 查找关系 3.3 知识存储 4 实验 摘要 大型语言模型&#xff08;LLM&#xff09;在自然语言处理领域展现…

基于供需算法的无人机航迹规划-附代码

基于供需算法的无人机航迹规划 文章目录 基于供需算法的无人机航迹规划1.供需搜索算法2.无人机飞行环境建模3.无人机航迹规划建模4.实验结果4.1地图创建4.2 航迹规划 5.参考文献6.Matlab代码 摘要&#xff1a;本文主要介绍利用供需算法来优化无人机航迹规划。 1.供需搜索算法 …

2024年湖北黄冈建安ABC建筑企业专职安全员报名事项

2024年湖北黄冈建安ABC建筑企业专职安全员报名事项 专职安全员一般是指从事安全管理方面的工作&#xff0c;普遍的是建筑施工行业&#xff0c;建筑工地安全员&#xff0c;专职安全员C证&#xff0c;黄冈建筑安全员ABC-建筑单位在黄冈&#xff0c;只能在黄冈报考建筑安全员ABC。…

前端基础---跳转相关的功能

后端给链接地址并且给token进行跳转 如果点击有key4&#xff0c;说明要跳转到相应的页面 auth是通过后端获取的地址&#xff0c; jdk是后端获取的相应的token&#xff0c; 然后进行拼接&#xff0c;进行window.open&#xff08;&#xff09;进行跳转 if (key 4) {var testUrl …

Py之transformers_stream_generator:transformers_stream_generator的简介、安装、使用方法之详细攻略

Py之transformers_stream_generator&#xff1a;transformers_stream_generator的简介、安装、使用方法之详细攻略 目录 transformers_stream_generator的简介 1、Web Demo T1、original T2、stream transformers_stream_generator的安装 transformers_stream_generator的…

工会排队营销玩法,让消费者乐于参与其中

小编介绍&#xff1a;10年专注商业模式设计及软件开发&#xff0c;擅长企业生态商业模式&#xff0c;商业零售会员增长裂变模式策划、商业闭环模式设计及方案落地&#xff1b;扶持10余个电商平台做到营收过千万&#xff0c;数百个平台达到百万会员&#xff0c;欢迎咨询。 工会…

HZGO-A-031/315、KZGO-A-031/50比例减压阀放大器

RZGO-A-010/210、RZGO-A-010/32、RZGO-A-010/100、RZGE-A-010/210、RZGE-A-010/32、RZGE-A-010/100、CART RZGE-A-010/210、CART RZGE-A-010/32、CART RZGE-A-010/100、RZGO-A-031/210、RZGO-A-033/100、HZGO-A-033/350、HZGO-A-031/315、KZGO-A-031/50、KZGO-A-033/210滑阀型、…

比Nginx测试桩更方便,ShenYu网关的Mock插件

有时候为了方便测试&#xff0c;我们需要模拟 HTTP 外部接口的返回结果。通常情况下&#xff0c;我们可以使用 Nginx 测试桩来实现这个目的。然而&#xff0c;Nginx 的使用门槛较高&#xff0c;可能对一些初级开发和测试人员来说有一定的难度。相比之下&#xff0c;Apache Shen…

使用传感器融合的前方碰撞预警-(Forward Collision Warning Using Sensor Fusion)

这个例子matlab自动驾驶工具箱中关于使用传感器融合的前方碰撞预警-(Forward Collision Warning Using Sensor Fusion)例子&#xff0c;其展示了如何通过融合视觉和雷达传感器的数据来跟踪车辆前方的物体&#xff0c;从而实现前向碰撞预警系统的开发与验证。 1 概述 前方碰撞…

JVM相关面试题(每日一练)

1. 什么是垃圾回收机制&#xff1f; 垃圾收集 Garbage Collection 通常被称为“GC”&#xff0c;它诞生于1960年 MIT 的 Lisp 语言&#xff0c;经过半个多世纪&#xff0c;目前已经十分成熟了。 jvm 中&#xff0c;程序计数器、虚拟机栈、本地方法栈都是随线程而生随线程而灭&a…

UML—时序图是什么

目录 前言: 什么是时序图: 时序图的组成元素&#xff1a; 1. 角色(Actor) 2. 对象(Object) 3. 生命线(LifeLine) 4. 激活期(Activation) 5. 消息类型(Message) 6.组合片段(Combined fragment) 时序图的绘制规则:​ 绘制时序图的3步&#xff1a; 1.划清边界&#xf…

mysql之用户管理、权限管理、密码管理

用户管理 创建用户create user 杨20.0.0.13 identified by 123; 用户重命名rename user 杨20.0.0.13 to yang20.0.0.13; 删除用户drop user 杨20.0.0.13; 权限管理 查看用户权限show grants for 杨20.0.0.13; 赋予用户权限grant all privileges on *.* to 杨localhost id…

【Spring Boot 源码学习】RedisAutoConfiguration 详解

Spring Boot 源码学习系列 RedisAutoConfiguration 详解 引言往期内容主要内容1. Spring Data Redis2. RedisAutoConfiguration2.1 加载自动配置组件2.2 过滤自动配置组件2.2.1 涉及注解2.2.2 redisTemplate 方法2.2.3 stringRedisTemplate 方法 总结 引言 上篇博文&#xff0…

微信小程序开发-微信支付退款功能【附有完整代码】

之前有写过详细的微信支付功能&#xff1a;微信支付 我们使用weixin-java-pay的jar包等&#xff0c;配置上的流程同微信支付&#xff0c;可以看上面的文章。 退款使用的WxPayService类的refundV3方法。使用该方法需要在微信支付配置的基础上加上&#xff1a;apiclient_key.pem…

Nginx 部署多个安全域名,多个服务【工作记录】

以下是本人通过Docker 部署的Nginx挂载出来的文件目录 先看下 nginx.conf 配置文件内容&#xff1a;如下 ps&#xff1a;当前文件就是安装后的初始内容&#xff0c;无修改。主要关注最后一行 include /etc/nginx/conf.d/*.conf;表示引入其他目录下的.conf配置文件&#xff1b;…

【Amazon】跨AWS账号资源授权存取访问

文章目录 一、实验框架图二、实验过程说明三、实验演示过程1、在A账号中创建S3存储桶2、在A账号创建S3存储桶访问策略3、在A账号创建信任开发账号的角色4、在B账号为用户添加内联策略5、在B账号中切换角色&#xff0c;以访问A账号中的S3资源 四、实验总结 一、实验框架图 本次…