Spring初级容器初始化
简单的demo
创建一个bean对象Student:
public class Student {
private String name = "xiaofei";
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
在resources下创建applicationContext.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="student" class="com.xiaofei.bean.Student"/>
</beans>
创建一个BeanFactoryDemo:
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
public class BeanFactoryDemo {
public static void main(String[] args) {
XmlBeanFactory beanFactory =
new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
Student student = (Student) beanFactory.getBean("student");
System.out.println(student.getName());
}
}
XmlBeanFactory原理分析
上面的demo简单流程如下:
- 首先,通过ClassPathResource将applicationContext.xml配置文件封装起来,我们可以知道的是,
ClassPathResource肯定会从resources目录下解析配置文件,从配置文件中解析bean标签,并获取bean标签上的id属性和class属性的值。 - 通过class属性的值即类全限定名称,就可以通过反射创建bean,也就是创建了一个Student对象出来,然后再将Student对象放到Spring容器当中,Student对象在容器中的名称为属性id的值,Spring容器的初始化简单来说也就是干这些事。
- 然后,当我们调用getBean方法时就会从Spring容器中加载bean了,Spring会根据给定bean的名称到Spring容器中获取bean,比如,demo中就是通过student这个名称,从Spring容器中获取Student对象。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pgEujhZi-1690957915685)(Spring_sound_code.assets/image-20230801160419889.png)]
Resource接口
什么是Resource接口?
- Spring统一把所有使用到的资源都抽象成了Resource,不同来源的资源对应着不同的Resource实现类。
Resource接口的方法:
- 定义了对资源状态判断的方法:
- exists方法判断资源是否存在
- isFile方法判断资源是否是文件类型的
- isOpen方法判断资源是否处于打开的状态
- isReadable方法判断资源是否是可读状态的
- 提供了资源到File、URI以及URL的转换方法:
- getFile方法可以将资源转换为File
- getURI方法可以将资源转换为URI
- getURL方法可以将资源转换为URL
- 提供了获取文件名称的getFilename方法
- 提供了获取资源的描述信息的getDescription方法
- getDescription方法一般它可以用于日志信息的打印。
InputStreamSource
package org.springframework.core.io;
import java.io.IOException;
import java.io.InputStream;
public interface InputStreamSource {
InputStream getInputStream() throws IOException;
}
可以看到,InputStreamSource接口中只有一个方法getInputStream,而且方法返回的就是一个输入流lnputStream。
从前面的类继承图中我们也看到了,Resource是继承了InputStreamSource接口的,所以,这也就意味着所有资源只要封装成了Resource,就可以通过调用InputStreamSource中的getinpustream方法,获取资源对应的输入流InputStream了。
而资源的来源又是多种多样的:
- ClassPathResource就是用来加载classpath路径下的资源文件的。
- UrlResource加载URL
- FileSystemResource加载文件
- ByteArrayResource加载Byte数组
- InputStreamResource加载InputStream
XmlBeanFactory构造方法分析
在上面通过Resource接口的实现类获取到资源后,接下来就是对XmlBeanFactory进行初始化操作:
可以看到先执行了super父类构造器,其实执行到了AbstractAutowireCapableBeanFactory:
ignoreDependencyInterface方法
ignoreDependencyInterface方法很简单,就是将这三个类(BeanNameAware、BeanFactoryAware、BeanClassLoaderAware
)都放到了ignoredDependencyInterfaces中,而ignoredDependencyInterfaces其实就是一个Set集合:
BeanNameAware、BeanFactoryAware、BeanClassLoaderAware:
- 这三个接口中的方法名称,和相应类的名称极度的相似,其实,Aware接口也称为感知接口,当bean实现了这些感知接口时,Spring在实例化这些bean的时候,就会调用感知接口中的方法注入相应的数据。
ignoreDependencyInterface方法作用:
ignoreDependencyInterface方法的作用,是为了让那些实现了感知接口的bean属性只能由Spring容器赋值,而不是可以人为的从外部随意的注入进来。
如果一个bean实现了BeanName、BeanFactoryAware或BeanClassLoaderAware接口的话,那这个bean中的属性如果想要通过Spring进行自动装配赋值的话,这个属性对应的setter方法,就不能和感知接口中的方法相同。
如果相同的话,Spring就不会为该属性自动装配赋值,而是让Spring内部调用这些感知接口的方法,来为这些属性设置值。
这也是这些感知接口存在的意义,毕竟, bean都实现了这些感知接口了,而感知接口恰好已经通过方法
ignoreDependencyInterface添加到忽略感知接口的集合中了,这就相当于这些属性的赋值权利交给Spring内部来决定了。
Spring这样的设计其实也有一定的合理性,比如你实现了BeanNameAware接口,对应的beanName属性的值,当然就是当前这个bean在Spring容器中的名称
此时,如果你从外部的xml或者注解中注入了一个其它的名称,Spring理所应当就会忽略掉这个外来值的自动装配了,确保bean名称的唯—性。
比如:
-
执行结果:
可以看到输出的值并不是我们所设置的xiaofeiBeanName,而是beanNameAwareImpl
XmlBeanDefinitionReader
执行完了super父类构造器后,下面执行reader.loadBeanDefinitions(resource)
继续看loadBeanDefinitions方法
XmlBeanDefinitionReader首先会将Resource封装为EncodedResource,EncodedResource相比于我们传进来的Resource只不过多了一些字符集和编码相关的设置,然后通过Resource中的输入流创建了InputSource,接下来进入到真正加载资源的方法doLoadBeanDefinitions中。
可以看到,doLoadBeanDefinitions方法中大体来说就干了两件事,一是通过我们传进来的参数inputSource和resource创建xml文件的Document对象(jdk里的),另外就是解析Document对象将解析到的bean注入到Spring的容器中。
其实Spring就是通过DOM来解析xml文件的,但是解析xml也是先需要对xml进行校验
XML的校验方式:DTD和XSD
-
XSD:XML Schemas Definition,也就是XML模式的定义,通过XSD的声明文件可以约束你在xml文件中不能随便乱写,以保证xml文件格式的正确性。
-
比如spring-beans.xsd,而xml文件的组成不光是xml文件本身,而且还包括它的约束语言,比如spring-beans.xsd就是xml约束语言XSD所规定的声明文件。
-
DTD:Document Type Definition,也就是文档类型的定义
-
可以看到,DTD在xml文件中是通过额外的一段配置来体现,当然,我们也可以注意到在DTD模式下也会配置相应的网址,通过这样网址,Spring会去下载对应的DTD的声明文件spring-beans.dtd,以便来约束我们按照一定的格式来配置xml文件。
-
如果我们要运行一个基于Spring开发的应用程序,但是,程序在运行的时候一旦断网了,就会导致Spring没法从网上下载相应的DTD或XSD声明文件,那xml文件是否符合我们预期的格式也就无从得知了,另外,如果网速不好的话也会影响我们程序的稳定性,这是无法接受的。
-
所以,Spring的研发团队也考虑到这个问题了,索性就把相应的DTD和XSD文件都放到Spring对应的jar包中,这样的话当Spring程序运行的时候,只需要通过相应的代码程序,从jar包中获取相应的DTD或XSD声明文件就可以了,就不需要那么费力的从网上下载了。
首先,我们将applicationContext.xml封装为资源ClassPathResource,然后再通过EncodedResource和InputSource的进一步封装之后,交给DOM的APl进行解析并获取xml对应的Document。并且,Spring在解析xml文件前还需要对xml文件进行校验
标签的解析
-
默认标签解析的入口在
DefaultBeanDefinitionDocumentReader#parseBeanDefinitions
方法;-
调用链路是
processBeanDefinition:306, DefaultBeanDefinitionDocumentReader parseDefaultElement:197, DefaultBeanDefinitionDocumentReader parseBeanDefinitions:176, DefaultBeanDefinitionDocumentReader doRegisterBeanDefinitions:149, DefaultBeanDefinitionDocumentReader registerBeanDefinitions:96, DefaultBeanDefinitionDocumentReader registerBeanDefinitions:511, XmlBeanDefinitionReader doLoadBeanDefinitions:391, XmlBeanDefinitionReader loadBeanDefinitions:338, XmlBeanDefinitionReader loadBeanDefinitions:310, XmlBeanDefinitionReader <init>:79, XmlBeanFactory <init>:67, XmlBeanFactory main:10, BeanFactoryDemo
-
-
到方法parseDefaultElement中,看下Spring是如何解析默认标签的:
-
重点还是最核心的bean标签的解析,直接到processBeanDefinition方法中看下 :
-
- 步骤一:解析bean标签元素,在parseBeanDefinitionElement里:
-
最后返回BeanDefinitionHolder对象, parseBeanDefinitionElement方法其实就是将beanDefinition,连带着解析得到的别名aliasesArray一起封装到了BeanDefinitionHolder中,BeanDefinitionHolder我们可以理解为是持有BeanDefinition的一个对象而已
- 步骤二:将解析到的bean注册到Spring容器中
-
进入到 registerBeanDefinition:
-
beanDefinitionMap就是一个ConcurrentHashMap类型的Map,beanDefinitionMap就是Spring容器,Spring容器从本质上来说就是一个Map
- 步骤一:解析bean标签元素,在parseBeanDefinitionElement里: