本章节目录:
(一) Bean与BeanDefinition 的 关 系
(二) 简 单 容 器
(三) 高 级 容 器
(四)容 器 初 始 化 主 要 做 的 事 情
(五)Resource、ResourceLoader、容器之间的微妙关系
(六) ResourceLoader的使用者 :BeanDefinitionReader
(七)BeanDefinition 的 注 册 流 程
(八)最终SpringIoC容器执行流程自述
(一) Bean与BeanDefinition 的 关 系
1.Bean是Spring的一等公民(核心)
1.1:Bean的本质就是Java对象,只是这个对象的声明周期由容器来管理
1.2:不需要为了创建Bean而在原来的Java类上添加任何额外的限制(侵入性极低)
1.3:对Java对象的控制方式体现在配置上(如:注解、xml配置文件)
2.BeanDefinition(Bean的定义):根据配置,生成用来描述Bean的BeanDefinition
常用属性:
作用范围scope(@Scope)
懒加载lazy-init(@Lazy):决定Bean实例是否延迟加载
首选primary(@Primary) : 设置为true的Bean会是优先的实现类
factory-bean和factory-method(@Configuration和@Bean)
BeanDefinition关系图:
1.在Spring中大量运用到了模板模式,图中我们可以看到BeanDefinition是一个接口来的。其定义了主要的功能抽象接口。其主要的实现类有图中三个(RootBeanDefinition、GenericBeanDefinition、ChildBeanDefinition)
2.在Spring2.5之前大部分但是使用的RootBeanDefinition实现类,在2.5版本后出现了GenericBeanDefinition实现类,随之该实现类成了主流。
注:
(在模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式)
(二) 简 单 容 器
BeanFactory的介绍
Spring中要求所有的Ioc容器都要实现BeanFactory接口(org.springframework.beans.factory)
BeanFactory是一个顶级的接口,其内部定义了一个String类型的变量,该变量主要用来获取FactoryBean的实例
该变量的使用例子:
BeanFactory和FactoryBean的区别:
BeanFactory:
它是Spring容器的根接口,定义了Bean工厂的最基础的功能特性。是用作Spring中管理Bean的容器。
FactoryBean:
它的本质也是一个Bean,但是这个Bean不像Service和Dao一样用来注入使用的,它的作用是用来生成这些普通的Bean的。
实现了该接口后呢,Spring容器在初始化时,会把实现了这个接口的Bean取出来,然后使用这个Bean中的getObject()方法生成我们想要的Bean。
BeanFactory的方法
getBean():根据名字获取Bean的实例
getAliases():根据别名来获取Bean的实例
isSingleton():校验Bean是否为单例创建
isPrototype():校验Bean是否为多例创建
getType():根据名字获取Bean实例的Class类型
BeanFactory的关系图:
ListableBeanFactory:
正如它的名字所示,该工厂的方法可以以列表(Listable)的形式提供Bean的相关信息。最大的特点就是可以批量的列出工厂生产的实例信息
比如该工厂的getBeanDefinitionNames()方法,可以获取容器中所有的Bean名字
getBeanDefinitionCount方法,可以获取容器中注册的BeanDefinition总数
(三) 高 级 容 器
高级容器的关系图:
高级容器均实现了ApplicationContext这个接口,为了区别于简单容器,高级容器通常被称为Context及上下文。BeanFactory主要面向于Spring内部框架使用,而ApplicationContext则提供使用的开发者。
ApplicationContext之所以可以称为高级容器,就是因为它比BeanFactory实现了更多的接口(功能)。为了有别于简单容器,ApplicationContext系列被放置于org.springframework.context包下。
ApplicationContext实现的接口有:
EnvironmentCapable:该接口只有一个getEnvironment()方法。主要用来获取Environment。(Environment说白了就是获取容器的启动参数)
ListableBeanFactory:可以通过列表的方式来管理bean
HierarchicalBeanFactory:支持多层级的容器来对每一层Bean的管理
ResourcePatternResolver:该接口继承自ResourceLoader,可以用来加载资源文件
MessageSource:用来管理Message,实现国际化等功能
ApplicationEventPublisher:意味着具有事件发布的能力
ApplicationContext常用容器:
传统的基于XML配置的经典容器:
FileSystemXmlApplicationContext:从文件系统加载配置
ClassPathXmlApplicationContext:从classpath加载配置
XmlWebApplicationContext:用于Web应用程序的容器
目前比较流行的容器:(注解)
AnnotationConfigServletWebServerApplicationContext(Spring Boot模块)
AnnotationConfigReactiveWebServerApplicationContext(Spring Boot模块)
AnnotationConfigApplicationContext(Spring模块)
学习容器可以从refresh()开始,该方法在ApplicationContext接口并没有声明,在ApplicationContext方法都是只读方法(Get),而是在其子类ConfigurableApplicationContext接口开始有的该方法。
ConfigurableApplicationContext
ConfigurableApplicationContext接口继承了Lifecycle接口:用于对容器的生命周期的管理
ConfigurableApplicationContext接口继承了Closeable接口:用于关闭容器释放资源
Spring高级容器中最重要的一个类:AbstractApplicationContext
该类就应用到了模板方法模式:定义好了容器的通用逻辑和模板结构,剩下的部分逻辑由子类实现
(四)容 器 初 始 化 主 要 做 的 事 情
容器的初始化的主要运行流程如下:
首先读取注解与xml配置文件,将这些配置文件载入到内存中,在内存中这些配置文件会被当作一个个的Resource对象,
之后这些对象会被解析成BeanDefinition实例,在最后注册到Spring的容器中
(五)Resource、ResourceLoader、容器之间的微妙关系
Resource:
Resource是一个抽象接口,位于org.springframework.core.io包下。
Resource继承自InputStreamSource接口,该接口只提供了一个方法getInputStream()。
在接口中定义资源的基本操作:
exists():资源是否存在
isReadable():资源是否可读
isOpen():资源是否打开
Resource关系图:
EncodeResource类:主要实现对资源文件的编码处理,其具体的实现在getReader()里,当我们给资源设置了编码属性之后呢,Spring会使用相应的编码作为输入流的编码。
AbstractResource类:主要提供了对Resource接口的大部分方法的公共实现
在Resource接口中提供的大部分都是读方法,只有在其子实现类中才有明确的写方法,比如FileSystemResource类为了支持写的功能,继承了WritableResource接口,该接口提供了getOutputStream()方法。
ResourceLoader:
实现了不同的Resource加载策略,按需返回特定类型的Resource
ResourceLoadzer是一个接口,该接口主要方法有getResource(String location),根据提供的资源路径返回Resource实例。
其次还有getClassLoader()方法,该方法主要将ClassLoader暴露出来,对于想要获取ResourceLoader,所使用的ClassLoader实例的用户可直接调用该方法获得。
ResourceLoader关系图:
其关键的类:DefaultResourceLoader。这个类提供了ResourceLoader接口的实现,最主要的方法就是getResource()方法的实现(获取Resource的具体实现类实例)
ResourceLoader接口的getResource()方法,本身就只能通过资源路径获取一个资源,并不支持Ant风格路径的解析。
如果我们要通过Ant风格路径解析去批量获取Resource,那么需要对getResource方法进行包装重铸。
ResourcePatternResolver:
由于该方法的需求,Spring提供了ResourcePatternResolver接口,该接口提供了getResources方法,支持根据路径匹配模式返回多个Resource实例,同时声明了一个变量声明了多一种路径匹前缀
该协议前缀由子类提供实现。
PathMatchingResourcePatternResolver:
该类为ResourcePatternResolver接口最为常用的子类,该类包含了对ResourceLoader的引用,这也就意味着在对继承自ResourceLoader的方法的实现会代理给该引用。
同时在getResources()方法的实现中,当找到一个匹配的资源Location时就可以使用该ResourceLoader的引用将其解析成Resource实例返回。
也提供了对父类接口ResourcePatternResolver新增的资源匹配前缀提供了实现
也提供了Ant解析风格
ApplicationContext:
在我们观察ResourceLoader的关系图的时候,可以发现我们实习的面孔:ApplicationContext。该类继承了ResourcePatternResolver,也就间接的继承了ResourceLoader接口
AbstraceApplicationContext:
AbstraceApplicationContext类中我们可以看到容器和ResourceLoader的关系,其继承了DefaultResourceLoader。
该类中有个方法getResourcePatternResolver()用来生成PathMatchingResourcePatternResolver这个实例,该方法在AbstraceApplicationContext的构造函数中调用。
在方法生成实例的时候呢我们可以看到它将自己的实例传递了进去
(六) ResourceLoader的使用者 :BeanDefinitionReader
BeanDefinitionReader:(主要负责读取BeanDefinition)
该类会利用ResourceLoader或getResourcePatternResolver将配置信息解析成一个个BeanDefinition并借助一个叫做BeanDefinitionRegistry的注册器接口将BeanDefinition注册到容器中
该类提供了一系列用来加载BeanDefinition的接口,方法主要是针对单/多个配置文件的加载,或者是单/多个Resource实例的加载。
该类主要方法:
getRegistry():可以获取BeanDefinitionRegistry对象,这个类的主要作用将BeanDefinition注册到BeanDefinition的注册表中。
getBeanNameGenerator():Bean的名字生成器,为匿名Bean生成一个名字,就是id
BeanDefinitionReader的关系图:
AbstractBeanDefinitionReader是一个抽象类,该类的主要方法为loadBeanDefinitions()。
AbstractBeanDefinitionReader.loadBeanDefinitions():
org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions()负责将加载到的bean definitions 文件解析为具体的bean实例
从指定的 location 资源路径加载 bean 定义信息。location 可以是简单的路径,但是也可以是 ResourcePatternResolver 类型,这样的话就需要对 ResourcePatternResolver 进行处理了,在初始阶段其包含一个 资源的set。
location 值可能为空,表示调用者对于资源不感兴趣。最终返回发现的bean definitions 的数量
1 public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
2 //获取 资源加载器
3 ResourceLoader resourceLoader = getResourceLoader();
4 if (resourceLoader == null) {
5 throw new BeanDefinitionStoreException(
6 "Cannot load bean definitions from location [" + location + "]: no ResourceLoader available");
7 }
8 //如果资源加载器属于 ResourcePatternResolver,则需要解析
9
10 if (resourceLoader instanceof ResourcePatternResolver) {
11 // 对 location 进行解析,Pattern模式可以生成多个 Resource 类型数组
12 try {
13 Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
14 //具体的对资源进行解析-你可以看到,这里是一个递归调用
15 int count = loadBeanDefinitions(resources);
16 if (actualResources != null) {
17 Collections.addAll(actualResources, resources);
18 }
19 if (logger.isTraceEnabled()) {
20 logger.trace("Loaded " + count + " bean definitions from location pattern [" + location + "]");
21 }
22 return count;
23 }
24 catch (IOException ex) {
25 throw new BeanDefinitionStoreException(
26 "Could not resolve bean definition resource pattern [" + location + "]", ex);
27 }
28 }
29 else {
30 // 使用 绝对URL只能加载单个的资源
31 Resource resource = resourceLoader.getResource(location);
32 int count = loadBeanDefinitions(resource);
33 if (actualResources != null) {
34 actualResources.add(resource)
35 }
36 if (logger.isTraceEnabled()) {
37 logger.trace("Loaded " + count + " bean definitions from location [" + location + "]");
38 }
39 return count;
40 }
41 }
(七)BeanDefinition 的 注 册 流 程
BeanDefinitionRegistry:
该类负责对BeanDefinition进行注册
该类实现了AliasRegistry
接口, 定义了一些对 bean的常用操作
关于AliasRegistry
它大概有如下功能:
-
- 以Map<String, BeanDefinition>的形式注册bean
- 根据beanName 删除和获取 beanDefiniation
- 得到持有的beanDefiniation的数目
- 根据beanName 判断是否包含beanDefiniation
该类的重点方法:registerBeanDefinition(),该方法就是网注册表中注册一个新的BeanDefinition实例。
BeanDefinitionRegistry类的描述:
1 // 它继承自 AliasRegistry
2 public interface BeanDefinitionRegistry extends AliasRegistry {
3
4 // 关键 -> 往注册表中注册一个新的 BeanDefinition 实例
5 void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException;
6 // 移除注册表中已注册的 BeanDefinition 实例
7 void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;
8 // 从注册中心取得指定的 BeanDefinition 实例
9 BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;
10 // 判断 BeanDefinition 实例是否在注册表中(是否注册)
11 boolean containsBeanDefinition(String beanName);
12 // 取得注册表中所有 BeanDefinition 实例的 beanName(标识)
13 String[] getBeanDefinitionNames();
14 // 返回注册表中 BeanDefinition 实例的数量
15 int getBeanDefinitionCount();
16 // beanName(标识)是否被占用
17 boolean isBeanNameInUse(String beanName);
18 }
重点铺垫:
在DefaultListableBeanFactory中,我们可以看到该类实现了BeanDefinitionRegistry接口。
同时它内部还有beanDefinitionMap成员变量,所谓的注册呢最终便是把beanName和BeanDefinition实例作为键值对存放到该Map中(ConcurrentHashMap是为了容器安全)
DefaultBeanDefinitionDocumentReader :
DefaultBeanDefinitionDocumentReader主要方法:
processBeanDefinition():
在DefaultBeanDefinitionDocumentReader 类中的 processBeanDefinition()方法完成了对BeanDefinition的注册
方法中的Registry参数在跟进参数后可以发现返回的是AbstractBeanDefinitionReader中的Registry变量,该Registry变量便是BeanDefinitionRegistry的实例。
1 protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
2 //BeanDefinitionHolder是对BeanDefinition的封装,即Bean定义的封装类
3 //对Document对象中<Bean>元素的解析由BeanDefinitionParserDelegate实现
4 BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
5 if (bdHolder != null) {
6 bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
7 try {
8 //向SpringIOC容器注册解析得到的BeanDefinition,这是BeanDefinition向IOC容器注册的入口
9 //传递的参数bdHolder为BeanDefinition的包装类,另一个参数为registry实例。
10 BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
11 }
12 catch (BeanDefinitionStoreException ex) {
13 getReaderContext().error("Failed to register bean definition with name '" +
14 bdHolder.getBeanName() + "'", ele, ex);
15 }
16 //在完成BeanDefinition注册之后,往容器发送注册完成的事件
17 getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
18 }
19 }
重点:
其中的 BeanDefinitionReaderUtils 调用了 registerBeanDefinition() 方法来将我们的 BeanDefinition 注册到 DefaultListableBeanFactory 中的 beanDefinitionMap 中。
DefaultBeanDefinitionDocumentReader主要方法:
doRegisterBeanDefinitions:
该方法主要操作Document的解析,将Xml通过SAX解析成Document对象,然后通过Delegate代理解析,解析成一个个的document
方法中标有⭐号的两行代码,创建了delegate代理解析对象,然后在末尾红色行代码处将delegate对象传递到方法中进行下一步的解析
protected void doRegisterBeanDefinitions(Element root) {
BeanDefinitionParserDelegate parent = this.delegate; //⭐
this.delegate = createDelegate(getReaderContext(), root, parent);// ⭐
if (this.delegate.isDefaultNamespace(root)) {
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
// We cannot use Profiles.of(...) since profile expressions are not supported
// in XML config. See SPR-12458 for details.
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
if (logger.isDebugEnabled()) {
logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +
"] not matching: " + getReaderContext().getResource());
}
return;
}
}
}
preProcessXml(root);
parseBeanDefinitions(root, this.delegate);
postProcessXml(root);
this.delegate = parent;
}
注:
由document对象解析成BeanDefinition对象之后,将BeanDefinition对象注册进入容器当中,详细可查看 Spring: BeanDefinition的注册(源码跟进)
(八)最终SpringIoC容器执行流程自述
XML配置的(资源定位、加载、解析、注册)全链路分析
Main()
public static void main(String[] args) {
String xmlPath="D:\\Project\\spring-framework-5.2.0.RELEASE\\springdemo\\src\\main\\resources\\spring\\spring-config.xml";
ApplicationContext applicationContext = new FileSystemXmlApplicationContext(xmlPath);
WelcomeService welcomeService = (WelcomeService)applicationContext.getBean("welcomeService");
welcomeService.sayHello("强大的spring框架");
}
执行大图:
大致流程自述:
1.首先我们根据location(Xml路径)new了一个FileSystemXmlApplicationContext的高级容器,
2.FileSystemXmlApplicationContext构造函数调用了另一个构造函数
3.FileSystemXmlApplicationContext在调用构造方法的时候设置了数组类型的配置路径(ConfigLocation)由此我们可以推测出它使用的配置文件加载器是支持Ant路径扫描的多资源加载器,后调用了自身的refresh()方法进行容器的初始化。
4.此时进入到AbstractApplicationContext类中的refresh()方法。该方法中的obtainFreshBeanFactory()创建了新的内部容器,该容器负责Bean的创建和管理。obtainFreshBeanFactory()方法调用了子容器进行初始化。
5.AbstractRefreshableApplicationContext(子容器)实现了初始化。在初始化中构造了创建了主要的容器(DefaultListableBeanFactory),并将其作为参数传递进loadBeanDefinitions()方法中,进行请求加载BeanDefinitons
6.在AbstractXmlApplicationContext的loadBeanDefinitions()方法中用传递过来的参数(DefaultListableBeanFactory)创建了XmlBeanDefinitionReader。并将自身(AbstractXmlApplicationContext)设置给XmlBeanDefinitionReader的
ResourceLoader。随后接着请求加载BeanDefinitions
7.遍历Locations看看是否有多个路径,然后逐一加载配置文件。
8.调用EncodeedResource来包装资源实例
9.XmlBeanDefinitionReader 的 loadBeanDefinitions中对资源进行了编码处理。随后进入了实际干活的doLoadBeanDefinitions()方法中
10.XmlBeanDefinitionReader 的 doLoadBeanDefinitions()方法:将Xml解析成Document对象,然后将对象和资源传递到registerBeanDefinitions(),该方法调用了doRegisterBeanDefinitions()进行资源注册
11.DefaultBeanDefinitionDocumentReader 的 doRegisterBeanDefinitions()方法:该方法创建了Delegate代理解析,传递delegate()调用了parseBeanDefinitions()将Document对象解析成一个个的BeanDefinition对象
12.DefaultBeanDefinitionDocumentReader 的 parseBeanDefinitions()方法:该方法会进行一系列的逻辑判断后调用parseDefaultElement()方法开始根据Spring的XMl命名规范进行解析出BeanDefinition对象
13.解析BeanDefinition对象时会进入到内部:DefaultBeanDefinitionDocumentReader 的 processBeanDefinition()方法,该方法会把BeanDefinition对象包装成BeanDefinitionHolder类,随后调用registerBeanDefinition()方法,
将BeanDefinition实例注册到容器中
14.BeanDefinitionReaderUtils 的 registerBeanDefinition()方法 :该方法真正的开始将BeanDefinition注册到容器。如果BeanDefinition实例先前已经注册过了,则清空先前的注册信息,(如果是单例,将先前已经创建的Bean实例清除掉重新注册)
简述:
Locations --> 高级容器 --> refresh方法(资源刷新) --> 创建内部容器进行Bean创建 / 管理 --> 创建适用于Xml的Reader(资源加载器) --> 遍历资源路径依次加载
--> 资源编码处理 --> 解析资源 --> BeanDefinition包装 --> 存入容器 --> 刷新容器