Spring(学习笔记)

news/2024/10/4 22:49:46/

<context:annotation-config/>是 Spring 配置文件中的一个标签,用于开启注解配置功能。这个标签可以让 Spring 容器识别并处理使用注解定义的 bean。例如,可以使用 @Autowired 注解自动装配 bean,或者使用 @Component 注解将类标记为 bean 等。

为什么加载上下文的时候,被包含的Bean它的构造函数的代码会执行呀?好像是很多代码都执行了!!!

没有无参构造,有有参构造会报错!

也就是说必须要有无参构造!这是为什么呀 !

@Value注解:

@Value("Essence")
private String name;

、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、

<bean id="people" class="pojo.People" autowire="byType"><property name="name" value="Durant"/><property name="dog" ref="dog"/><property name="cat" ref="cat"/>
</bean><bean id="cat" class="pojo.Cat"/><bean id="dog" class="pojo.Dog"/>

、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、

<bean id="hello" class="pojo.Hello"><property name="str" value="Spring"/>
</bean>

、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、

<bean id="userT" class="pojo.UserT" name="user2,u2"><property name="name" value="张恒"/>
</bean>

getBean();中可以输入u2,user2

、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、

<bean id="address" class="pojo.Address"><property name="address" value="西安"/>
</bean><bean id="student" class="pojo.Student"><property name="name" value="张恒"/><property name="address" ref="address"/><property name="books"><array><value>红楼梦</value><value>西游记</value><value>水浒传</value><value>三国演义</value></array></property><property name="hobbys"><list><value>听歌</value><value>看电影</value><value>敲代码</value></list></property><property name="card"><map><entry key="身份证" value="411628"/></map></property><property name="games"><set><value>穿越火线</value></set></property><property name="wife"><null/></property><property name="info"><props><prop key="学号">2020</prop><prop key="性别">男</prop><prop key="姓名">小明</prop></props></property>
</bean>

  • @Resource如有指定的name属性,先按该属性进行byName方式查找装配;
  • 其次再进行默认的byName方式进行装配;
  • 如果以上都不成功,则按byType的方式自动装配。
  • 都不成功,则报异常。

Spring IoC底层源码分析

Spring IoC容器的启动可以概括为一下两步:

  • 创建BeanFactory
  • 实例化Bean对象

在SourceCodeLearning类中设置好断点后,下面一步步进入Spring底层代码。

ApplicationContext applicationContext = new FileSystemXmlApplication("classpath:spring.xml");

通过FileSystemXmlApplicationContext跟踪上述构造器可以发现,其主要完成了一下三个步骤:

  • 初始化父容器AbstractApplicationContext
  • 设置资源文件的位置setConfigLocations
  • 使用核心方法refresh(),其实是在超类AbstractApplicationContext中定义的一个模版方法(模版方法设计模式)。

refresh()方法的定义--ConfigurationApplicationContext接口中定义了该方法。

ConfigurationApplicationContext的基类是BeanFactory。

AbstractApplicationContext类实现了ConfigurationApplicationContext接口,重写了refresh()方法。部分重要内容如下:

AbstractApplicationContext.refresh()方法是个模版方法,定义了需要执行的一些步骤。并不是实现了所有的逻辑,只是充当了一个模版,由其子类去实现更多个性化的逻辑。

模版方法refresh()中最核心的两步:

(1)创建BeanFactory:

ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

(2)实例化Bean:

finishBeanFactoryInitialization(beanFactory);

创建BeanFactory

创建BeanFactory重点分析AbstractApplicationContext.obtainFreshBeanFactory()方法。其代码实现如下:

从以上代码可以发现,AbstractApplicationContext.obtainFreshBeanFactory()方法分为以下两步:

  • 刷新BeanFactory,即refreshBeanFactory()。
  • 获取BeanFactory,即getBeanFactory()。

这两步中刷新BeanFactory的方法refreshBeanFactory()是核心,接下来进一步分析refreshBeanFactory()方法。这个方法定义在AbstractApplicationContext中,是一个抽象方法,也是一个模版方法,需要AbstractApplicationContext的子类来实现逻辑。其具体实现是在其子类AbstractRefreshableApplicationContext中完成的。refreshBeanFactory()方法实现的部分代码如下:

可以发现,在refreshBeanFactory()方法的实现中,首先检查当前上下文是否已经存在BeanFactory。如果已存在BeanFactory,先销毁Bean和BeanFactory,然后创建新的BeanFactory。

DefaultListableBeanFactory beanFactory = createBeanFactory();这行代码只是创建了一个空的BeanFactory,其中没有任何Bean。因此refreshBeanFactory()方法的核心功能是在loadBeanDefinitions(beanFactory);这行代码中实现的。

loadBeanDefinitions()的具体实现是在AbstractXmlApplication类中。

loadBeanDefinitions(DefaultListableBeanFactory beanFactory)方法中,通过上一步创建的空的BeanFactory来创建一个XmlBeanDefinitionReader对象。XmlBeanDefinitionReader是用来解析XML中定义的bean的。

下面重点讲解loadBeanDefinitions(beanDefinitionReader)方法,这是一个重载的方法,这个方法的入参是刚刚生成的XmlBeanDefinitionReader对象。下面进入重载的loadBeanDefinitions方法进行分析,代码如下:

这个方法主要的功能是解析资源文件的位置,然后调用XmlBeanDefinitionReader对象的loadBeanDefinitions方法解析Bean的定义。

下面将对reader.loadBeanDefinitions(cinfigLocations);这段代码进行解析。

分析AbstractBeanDefinitionReader的方法loadBeanDefinitions,其方法实现如下:

 可以发现loadBeanDefinition()方法会遍历资源数组,最终会调用重载方法loadBeanDefinition(),重载方法的部分实现代码如下:

 

这个方法会解析资源文件的路径,得到Resource[]资源数组,核心逻辑是调用loadBeanDefinitions(resource)方法,进入这个方法查看其代码如下:

loadBeanDefinitions内部工作原理是遍历每个资源,依次调用loadBeanDefinitions(Resource resource)重载的方法。该重载的方法在顶层接口BeanDefinitionReader中

该方法会调用重载方法loadBeanDefinitions(EncodedResource encodedResource)。

loadBeanDefinitions(EncodeResource encodedResource)方法以流的方式读取资源文件,调用doLoadBeanDefinition()方法。doLoadBeanDefinition()是载入定义Bean的核心方法。其部分代码如下:

从doLoadBeanDefinition(InputSource inputSource, Resource resource)方法的定义可以看出,最终注册Bean的地方是在registerBeanDefinitions(doc, resource);这行代码。其代码如下:

registerBeanDefinitions(Document doc, Resource resource)方法的核心逻辑是在documentReader.registerBeanDefinitions(doc, createReaderContext(resource));这一行,这里发生了对Bean的注册。registerBeanDefinitions(Document doc , XmlReaderContext readerContext)方法代码如下:

registerBeanDefinitions(Document doc , XmlReaderContext readerContext)方法是在DefaultBeanDefinitionDocumentReader中实现的。核心是通过doRegisterBeanDefinitions()方法实现的。其代码实现如下:

doRegisterBeanDefinitions(Element root)方法的核心逻辑在parseBeanDefinition(root,this.delegate);这个方法中处理。其代码如下:

parseBeanDefinition(root,this.delegate)方法的核心逻辑是依赖parseDefaultElement(ele, delegate);方法实现的,其代码如下:

根据不同Bean的配置不同,进入不同分支执行。本书的示例是进入processBeanDefinition(ele,delegate)方法。其代码如下:

从上述方法中可知,最关键的是BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder,getReaderContext().getRegistry());的调用。这是注册Bean的关键代码,其代码如下:

registry.registerBeanDefinition(beanName,definitionHolder.getBeanDefinition());这行是将Bean的名字和BeanDefinition对象进行注册的地方。该方法的定义是在BeanDefinitionRegistry中。

本例将进入BeanDefinitionRegistry接口的实现类DefaultListableBeanFactory中,其部分代码如下:

从registerBeanDefinition(String beanName, BeanDefinition beanDefinition)方法代码可以看出,先从beanDefinitionMap这个ConcurrentHashMap对象根据beanName查找是否已经有同名的bean,如果不存在,则会调用beanDefinitionMap.put(beanName,beanDefinition)方法,以beanName为key,beanDefinition为value注册,将这个Bean注册到BeanFactory中,并将所有的BeanName保存到beanDefinitionNames这个ArrayList中。

到此,完成了IoC第一部分——创建BeanFactory的代码解析。但是,此时Bean只是完成了Bean名称和BeanDefinition对象的注册,并没有实现Bean的实例化和依赖注入。下面将要分析IoC的第二个关键部分Bean的初始化。

实例化Bean

在创建BeanFactory的过程中,BeanDefinition注册到了BeanFactory中的一个ConcurrentHashMap对象中了,并且以BeanName为key,BeanDefinition为value注册。下面将要分析实例化Bean的过程,即从上文提到的AbstractApplicationContext类的refresh()方法中的finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory)方法开始向底层分析。

首先进入finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory)方法,查看其部分代码如下:

从上述代码可知,beanFactory.preInstantiateSingletons();这行代码是实例化Bean的。

打开preInstantiateSingletons()方法如下:

该方法遍历beanDefinitionNames这个ArrayList对象中的BeanName,循环调用getBean(beanName)方法。该方法实际上就是创建Bean并递归构建Bean间的依赖关系。getBean(beanName)方法最终会调用doGetBean(name,null,null,false),进入该方法查看doGetBean方法的部分代码如下:

可以看到,该方法首先会获取当前Bean依赖关系mbd.getDependsOn();接着根据依赖的BeanName递归调用getBean()方法,直到调用到getSingleton()方法返回依赖Bean,即当前正在创建的Bean ,不断探寻依赖的Bean,直到依赖关系最底层的Bean 没有依赖的对象了,至此整个递归过程结束。getSingleton()方法的参数是createBean()方法的返回值。createBean()是在AbstractAutowireCapableBeanFactory中实现的。createBean(String beanName, RootBeanDefinition mbd,@Nullable Object[] args)方法部分代码如下:

该方法的核心是doCreateBean(beanName,mdbToUse,args)这个方法,doCreateBean将会返回Bean对象的实例。查看doCreateBean的部分代码如下:

这个方法中最重要的两行代码:

(1)instanceWrapper = createBeanInstance(beanName,mbd,args)用来创建实例。

(2)方法populateBean(beanName,mbd,instanceWrapper)用于填充Bean,该方法可以说就是发生了依赖注入的地方。

先看看createBeanInstance()方法其核心实现如下:

createBeanInstance()方法会调用instantiateBean()方法,其部分实现如下:

instantiateBean()方法核心逻辑是beanInstance = getInstantiationStrategy().instantiate(),发挥作用的策略对象是SimpleInstantiationStrategy,在该方法内部调用了静态方法BeanUtils.instantiateClass(),这个方法的部分实现如下:

该方法会判断是否是Kotlin类型,如果不是,则会调用Constructor的newInstance方法,也就是最终使用反射创建了该实例。

到这里,Bean的实例已经创建完成。但是Bean实例的依赖关系还没有设置,下面回到doCreateBean()方法中的populateBean()方法,该方法用于填充Bean,该方法可以说就是发生依赖注入的地方。回到AbstractAutowireCapableBeanFactory类中看一下populateBean()方法的实现。populateBean()部分代码如下:

整个方法的核心逻辑是PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues():null);这行代码,即获取该bean的所有属性,就是配置property元素,即依赖关系。最后执行applyPropertyValues()方法,其实现如下:

关键代码Object resolveValue = valueResolver.resolveValueIfNecessary(pv,originalValue);该方法是获取property对应的值。resolveValueIfNecessary()方法部分代码如下:

resolveValueIfNecessary()方法的核心是resolveReference(),该方法是解决Bean依赖关系的。进入该方法,其代码如下:

这段代码的核心是以下这一行:

bean = this.beanFactory.getParentBeanFactory().getBean();

这里将会发生递归调用,根据依赖的名称,从BeanFactory中递归得到依赖。到这段结束,就可以获取到依赖的Bean。回到applyPropertyValues入口处,获取到依赖的对象值后,将会调用bw.setPropertyValues()方法,这是将依赖值注入的地方。此方法会调用AbstractPropertyAccessor类的setPropertyValues方法,查看AbstractPropertyAccessor.setPropertyValues方法的实现,其部分代码如下:

该方法会循环Bean的属性列表,循环中调用setPropertyValue()方法,该方法是通过AbstractPropertyAccessor.setPropertyValues()方法来实现的,进入该方法的代码,其部分实现如下:

其核心是最后一行nestedPa.setPropertyValue()代码,其部分代码实现如下:

进入processLocalProperty()方法的代码,该方法非常复杂,其核心实现如下:

上述代码调用的ph.setValue()方法是BeanWrapperImpl.setValue()方法,进入这个方法的代码,查看其部分实现如下:

该方法是最后一步,这里可以看到该方法会找到属性的set方法,然后调用Method的invoke方法,完成属性注入。至此IoC容器的启动过程完毕。

 


http://www.ppmy.cn/news/1534630.html

相关文章

掌握 C# 中的 LINQ(语言集成查询)

LINQ&#xff08;Language Integrated Query&#xff0c;语言集成查询&#xff09;是 C# 中的一项强大功能&#xff0c;它使得我们能够使用查询语法处理不同的数据源&#xff0c;如对象、XML、数据库等。LINQ 通过提供统一的查询语法&#xff0c;使开发者能够更加简洁、高效地操…

1G,2G,3G,4G,5G各代通信技术的关键技术,联系和区别

目录 1G2G3G4G5G各代通信技术的联系和区别联系区别 1G 1G的主要特点是无线移动化。关键技术为蜂窝组网&#xff0c;支持频率复用和移动切换&#xff0c;可以实现个人和个人移动状态下不间断的语音通信。 1G通信系统现已关闭&#xff0c;其主要缺点是串好和盗号。 2G 数字化…

Python的file.read方法

Python的file.read方法 file.read() 方法简介file.read() 的工作原理示例&#xff1a;读取整个文件示例&#xff1a;读取指定字符数 文件读取模式使用 read() 处理大文件file.read() 的返回值和 EOFfile.read() 和其他文件读取方法的区别 file.read() 方法简介 file.read() 是…

【接口测试】任务1:登录接口

需要技能竞赛软件测试资料的同学们可s聊我&#xff0c;详细了解 任务实现要求 根据系统管理员—登录—接口API文档&#xff0c;编写接口测试用例&#xff0c;分别使用PostMan及JMeter进行接口测试&#xff0c;需要检查系统接口是否能正常工作&#xff0c;返回值是否正确&#…

WSL (Linux)配置 Rust 开发调试环境

WSL &#xff08;Linux&#xff09;配置 Rust 开发调试环境 安装 Rust&#xff1a;安装 Rust 环境 - Rust语言圣经(Rust Course) 除了执行以上步骤&#xff0c;还得安装编译工具&#xff1a; sudo apt-get update sudo apt-get install build-essential配置 VSCode&#xff1a;…

TypeScript 算法手册 【基数排序】

文章目录 1. 基数排序简介1.1 基数排序定义1.2 基数排序特点 2. 基数排序步骤过程拆解2.1 找出数组中的最大值2.2 从最低位开始&#xff0c;对每一位进行计数排序2.3 对某一位数进行计数排序2.4 将排序结果复制回原数组 3. 基数排序的优化3.1 处理负数3.2 字符串排序案例代码和…

STL之priority_queue篇——深入剖析C++中优先队列的实现原理、核心特性及其底层机制

文章目录 前言一、补充内容&#xff1a;堆1.1 什么是堆1.2 堆的分类与性质1.3 堆的向下调整算法&#xff08;小根堆&#xff09;实现流程&#xff1a;代码&#xff1a; 1.4 堆的向上调整算法&#xff08;小根堆&#xff09;实现流程&#xff1a;代码&#xff1a; 1.5 数组建堆算…

查看 Git 对象存储中的内容

查看 Git 对象存储中的内容 ls -C .git/objects/<dir>ls: 列出目录内容的命令。-C: 以列的形式显示内容。.git/objects/<dir>: .git 是存储仓库信息的 Git 目录&#xff0c;objects 是其中存储对象的子目录。<dir> 是对象存储目录下的一个特定的子目录。 此…