一、核心容器类图
1.1 BeanFactory
BeanFactory 作为最顶层的一个接口类,它定义了 IOC 容器的基本功能规范,BeanFactory 有三
个重要的子类:ListableBeanFactory、HierarchicalBeanFactory 和 AutowireCapableBeanFactory 。
ListableBeanFactory 接口表示这些 Bean 是可列表化的,而 HierarchicalBeanFactory 表示的是这些 Bean 是有继承关系的,也就是每个 Bean 有可能有父 Bean。AutowireCapableBeanFactory 接口定义 Bean 的自动装配规则。这三个接口共同定义了 Bean 的集合、Bean 之间的关系、以及 Bean 行为。
BeanFactory 里只对 IOC 容器的基本行为作了定义,根本不关心你的 Bean 是如何定义怎样加载的,需要看具体的 IOC 容器实现,Spring 提供了许多 IOC 容器的 实 现 。 比 如 AnnotationConfigApplicationContext、GenericApplicationContext ,ClasspathXmlApplicationContext 等
ApplicationContext 是 Spring 提供的一个高级的 IOC 容器,它除了能够提供 IOC 容器的基本功能外,还为用户提供了以下的附加服务。从 ApplicationContext 接口的实现,我们看出其特点:
1、支持信息源,可以实现国际化。(实现 MessageSource 接口)
2、访问资源。(实现 ResourcePatternResolver 接口,后面章节会讲到)
3、支持应用事件。(实现 ApplicationEventPublisher 接口)
1.2 BeanDefintion
SpringIOC 容器管理了我们定义的各种 Bean 对象及其相互的关系,Bean 对象在 Spring 实现中是以 BeanDefinition 来描述的
1.3 BeanDefintionReader
在XML时代,Bean 的解析主要就是对 Spring 配置文件的解析。这个解析过程主要通过BeanDefintionReader 来完成。结构:
二、IOC容器的初始化
2.1 整体思路
个人总结IOC 容器的初始化,以最终目标BeanDefintion为target,包括四个基本的过程。
(1)程序入口,各种环境其实入口不同
(2)BeanDefinition 的 Resource 定位
(3)加载
(4)注册
2.2 程序入口
1.ClassPathXmlApplicationContext
Main函数执行ClassPathXmlApplicationContext构造函数型,来到了refresh方法
2.AnnotationConfigApplicationContext
Main函数执行AnnotationConfigApplicationContext 构造函数型,来到了refresh方法
3.用Tomcat等容器机制拉起容器运行
通过web.xml或spi机制来到了refresh方法
4.SpringBoot启动等等…
SpringApplication.run方法,最终会来到来到了refresh方法
2.3 资源定位
2.3.1.ClassPathXmlApplicationContext
public ClassPathXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent){
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
2.3.1.1 设置解析器
调用父类父类容器的构造方法(super(parent)方法)为容器设置好 Bean 资源加载器(new出来的解析器)
public AbstractApplicationContext() {
this.resourcePatternResolver = getResourcePatternResolver();
}
2.3.1.2 定位Bean配置信息路径
再 调 用 父 类 AbstractRefreshableConfigApplicationContext 的setConfigLocations(configLocations)方法设置 Bean 配置信息的定位路径,定位路径
2.3.1.3 refresh方法启动容器
Spring IOC 容器载入 Bean 配置信息从 其 子 类 容 器 的 refreshBeanFactory() 方 法 启 动 , 所 以 整 个 refresh() 中
“ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();”
这句以后代码的都是注册容器的信息源和生命周期事件,我们前面说的载入就是从这句代码开始启动。
refresh()方法的主要作用是:在创建 IOC 容器前,如果已经有容器存在,则需要把已有的容器销毁和关闭,以保证在 refresh 之后使用的是新建立起来的 IOC 容器。它类似于对 IOC 容器的重启,在新建立好的容器中对容器进行初始化,对 Bean 配置资源进行载入。
2.4 加载
2.4.1载入配置路径
refresh()>obtainFreshBeanFactory()>refreshBeanFactory()==>loadBeanDefinitions()
2.4.1.1 XML环境
实现类在AbstractXmlApplicationContext
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
//创建 XmlBeanDefinitionReader, 即创建 Bean 读取器, 并通过回调设置到容器中去, 容器使用该读取器读取 Bean 配置资源
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
//为 Bean 读取器设置 Spring 资源加载器, AbstractXmlApplicationContext 的
//祖先父类 AbstractApplicationContext 继承 DefaultResourceLoader, 因此, 容器本身也是一个资源加载器
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
//为 Bean 读取器设置 SAX xml 解析器
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
//当 Bean 读取器读取 Bean 定义的 Xml 资源文件时, 启用 Xml 的校验机制
initBeanDefinitionReader(beanDefinitionReader);
//Bean 读取器真正实现加载的方法
loadBeanDefinitions(beanDefinitionReader);
}
使用的是XmlBeanDefinitionReader,最后这句调用了父类AbstractBeanDefinitionReader的 loadBeanDefinitions()方法 ,通过源码分析做了两个事:
(1)转到loadBeanDefinitions重载方法,调用资源加载器的获取资源方法 resourceLoader.getResource(location),获取到要加载的资源 Resource[] resources
(2)上面这个getResource方法最终又回到了AbstractXmlApplicationContext里 (委托给resourcePatternResolver)
下面分析如何通过localtion(路径)在AbstractXmlApplicationContext解析
2.4.2 解析配置的路径
2.4.2.1 XML环境
AbstractApplicationContext=>getResources=>this.resourcePatternResolver.getResources=>getResourceLoader().getResource(locationPattern)
public Resource getResource(String location) {
for (ProtocolResolver protocolResolver : this.protocolResolvers) {
Resource resource = protocolResolver.resolve(location, this);
if (resource != null) {
return resource;
}
}
//如果是类路径的方式, 那需要使用 ClassPathResource 来得到 bean 文件的资源对象
if (location.startsWith("/")) {
return getResourceByPath(location);
} else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
} else {
try {
// 如果是 URL 方式, 使用 UrlResource 作为 bean 文件的资源对象
URL url = new URL(location);
return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
} catch (MalformedURLException ex) {
//如果既不是 classpath 标识, 又不是 URL 标识的 Resource 定位, 则调用
//容器本身的 getResourceByPath 方法获取 Resource
return getResourceByPath(location);
}
}
}
这样,就可以从类路径上对 IOC 配置文件进行加载,当然我们可以按照这个逻辑从任何地方加载,在 Spring 中我们看到它提供的各种资源抽象,比如ClassPathResource、URLResource、FileSystemResource 等来供我们使用。上面我们看到的是定位Resource 的一个过程,而这只是加载过程的一部分。
2.4.3 读取配置文件内容
2.4.3.1 XML环境
XmlBeanDefinitionReader里面有巨多的重载loadBeanDefinitions方法,子类父类来来回回跳转十次终于进入重点,会到下面这个:
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
//省略。。。
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
//省略。。。
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
Document doc = doLoadDocument(inputSource, resource);
int count = registerBeanDefinitions(doc, resource);
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + count + " bean definitions from " + resource);
}
return count;
}
//省略巨多catch
}
读取成IO流,将 XML 文件转换为 DOM 对象, 解析过程由 documentLoader 实现封装成Document对象!
2.4.4 解析内容成IOC成分
2.4.4.1 XML环境
1.上一步解析成Document之后马上调用了registerBeanDefinitions解析文档对象:
//按照 Spring 的 Bean 语义要求将 Bean 配置资源解析并转换为容器内部数据结构
public int registerBeanDefinitions(Document doc, Resource resource) {
//得到 BeanDefinitionDocumentReader 来对 xml 格式的 BeanDefinition 解析
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
//获得容器中注册的 Bean 数量
int countBefore = getRegistry().getBeanDefinitionCount();
//解析过程入口, 这里使用了委派模式, BeanDefinitionDocumentReader 只是个接口
//具体的解析实现过程有实现类 DefaultBeanDefinitionDocumentReader 完成
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
//统计解析的 Bean 数量
return getRegistry().getBeanDefinitionCount() - countBefore;
}
在完成通用的 XML 解析之后,按照 Spring Bean 的定义规则对 Document 对象进行解析,其解 析 过 程 是 在 接 口BeanDefinitionDocumentReader 的 实 现 类DefaultBeanDefinitionDocumentReader 中实现
//在解析 Bean 定义之前, 进行自定义的解析, 增强解析过程的可扩展性
preProcessXml(root);
//从 Document 的根元素开始进行 Bean 定义的 Document 对象
parseBeanDefinitions(root, this.delegate);
//在解析 Bean 定义之后, 进行自定义的解析, 增加解析过程的可扩展性
postProcessXml(root);
重点是parseBeanDefinitions(root, this.delegate);进行 Bean 定义的 Document 对象解析
2.判断判断文档对象里的各种设置好的标签,import、Bean、Alias等等
DefaultBeanDefinitionDocumentReader的parseDefaultElement方法
//使用 Spring 的 Bean 规则解析 Document 元素节点
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
//如果元素节点是<Import>导入元素, 进行导入解析
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
//如果元素节点是<Alias>别名元素, 进行别名解析
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
processAliasRegistration(ele);
}
//元素节点既不是导入元素, 也不是别名元素, 即普通的<Bean>元素,
//按照 Spring 的 Bean 规则解析元素
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate);
}
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// recurse
doRegisterBeanDefinitions(ele);
}
}
Bean下属的那些子元素什么的都会解析成key-value放到BeanDefintion对象里持有之后用,每个就不细讲了,可以逐个跟进去看就ok,一节节解析,值得注意的是:
在解析元素过程中没有创建和实例化 Bean 对象,只是创建了 Bean 对象的定义类BeanDefinition,将元素中的配置信息设置到 BeanDefinition 中作为记录,当依赖注入时才使用这些记录信息创建和实例化具体的 Bean 对象。
经过对 Spring Bean 配置信息转换的 Document 对象中的元素层层解析,Spring IOC 现在已经将 XML形式定义的 Bean 配置信息转换为 Spring IOC 所识别的数据结构——BeanDefinition
2.5 注册
紧接上一步解析的最后会得到调用BeanDefinitionReaderUtils 的 registerBeanDefinition() 方 法 向 IOC 容 器 注 册 解 析 的 Bean 。
//将解析的 BeanDefinitionHold 注册到容器中
public static void registerBeanDefinition(
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry){
//获取解析的 BeanDefinition 的名称
String beanName = definitionHolder.getBeanName();
//向 IOC 容器注册 BeanDefinition
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
//如果解析的 BeanDefinition 有别名, 向容器为其注册别名
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String alias : aliases) {
registry.registerAlias(beanName, alias);
}
}
}
这里面的registry实际就是DefaultListableBeanFactory,这个是spring里真一直在用的容器,注册源码:
//存储注册信息的 BeanDefinition
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
//向 IOC 容器注册解析的 BeanDefiniton
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition){
//校验解析的 BeanDefiniton,省略
BeanDefinition oldBeanDefinition = this.beanDefinitionMap.get(beanName);
if (oldBeanDefinition != null) {
if (!isAllowBeanDefinitionOverriding()) {
throw new BeanDefinitionStoreException();
} else if (oldBeanDefinition.getRole() < beanDefinition.getRole()) {
if (this.logger.isWarnEnabled()) {
this.logger.warn();
}
} else if (!beanDefinition.equals(oldBeanDefinition)) {
if (this.logger.isInfoEnabled()) {
this.logger.info();
}
}
else {
if (this.logger.isDebugEnabled()) {
this.logger.debug();
}
}
this.beanDefinitionMap.put(beanName, beanDefinition);
} else {
if (hasBeanCreationStarted()) {
//注册的过程中需要线程同步, 以保证数据的一致性
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;
if (this.manualSingletonNames.contains(beanName)) {
Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);
updatedSingletons.remove(beanName);
this.manualSingletonNames = updatedSingletons;
}
}
}
else {
this.beanDefinitionMap.put(beanName, beanDefinition);
this.beanDefinitionNames.add(beanName);
this.manualSingletonNames.remove(beanName);
}
this.frozenBeanDefinitionNames = null;
}
//检查是否有同名的 BeanDefinition 已经在 IOC 容器中注册
if (oldBeanDefinition != null || containsSingleton(beanName)) {
//重置所有已经注册过的 BeanDefinition 的缓存
resetBeanDefinition(beanName);
}
}
排除各种校验与情况处理,其实核心就是为了执行:
this.beanDefinitionMap.put(beanName, beanDefinition);
this.beanDefinitionNames.add(beanName);
this.manualSingletonNames.remove(beanName);
把beanDefinition存到HashMap,把名字存进去等.
现在 IOC 容器中已经建立了整个 Bean 的配置信息,这些BeanDefinition 信息已经可以使用,并且可以被检索,IOC 容器的作用就是对这些注册的 Bean 定义信息进行处理和维护。这些的注册的 Bean 定义信息是 IOC 容器控制反转的基础,正是有了这些注册的数据,容器才可以进行依赖注入。
2.6 容器初始化小结
总结一下 IOC 容器初始化的基本步骤:
1、初始化的入口在容器实现中的 refresh()调用来完成。
2、对 Bean 定义载入 IOC 容器使用的方法是 loadBeanDefinition()
其中的大致过程如下:
1.通过 ResourceLoader 来完成资源文件位置的定位,DefaultResourceLoader是默认的实现,同时上下文本身就给出了 ResourceLoader 的实现,可以从类路径,文件系统,URL 等方式来定为资源位置。
2.如果是XmlBeanFactory 作为 IOC 容器,那么需要为它指定 Bean 定义的资源,也 就 是 说 Bean 定 义 文 件 时 通 过 抽 象 成 Resource 来 被 IOC 容 器 处 理 的 。
3.容 器 通 过BeanDefinitionReader 来 完 成 定 义 信 息 的 解 析 和 Bean 信 息 的 注 册 , 往 往 使 用 的 是XmlBeanDefinitionReader 来 解 析 Bean 的 XML 定 义 文 件 - 实 际 的 处 理 过 程 是 委 托 给BeanDefinitionParserDelegate 来完成的,从而得到 bean 的定义信息
4.这些信息在 Spring 中使用BeanDefinition对象来表示-这个名字可以让我们想到loadBeanDefinition(),registerBeanDefinition()这些相关方法。它们都是为处理 BeanDefinitin 服务的,容器解析得到 BeanDefinition 以后,需要把它在 IOC 容器中注册,这由 IOC 实现 BeanDefinitionRegistry 接口来实现。
5.注册过程就是在 IOC 容器内部维护的一个 HashMap 来保存得到的 BeanDefinition 的过程。这个 HashMap 是 IOC 容器持有Bean 信息的场所,以后对 Bean 的操作都是围绕这个 HashMap 来实现的。
6.然后我们就可以通过 BeanFactory 和 ApplicationContext 来享受到 Spring IOC 的服务了,在使用 IOC容器的时候,我们注意到除了少量粘合代码,绝大多数以正确 IOC 风格编写的应用程序代码完全不用关心如何到达工厂,因为容器将把这些对象与容器管理的其他对象钩在一起。
7.基本的策略是把工厂放到已知的地方,最好是放在对预期使用的上下文有意义的地方,以及代码将实际需要访问工厂的地方。Spring本身提供了对声明式载入web应用程序用法的应用程序上下文,并将其存储在ServletContext中的框架实现。
三、依赖注入与实例化
当 Spring IOC 容器完成了 Bean 定义资源的定位、载入和解析注册以后,IOC 容器中已经管理了 从Bean抽象出的数据数据结构BeanDefinition,此时 IOC 容器还没有对所管理的 Bean 进行依赖注入与实例化(默认的懒加载)。
3.1 触发的时机
1)、用户第一次触发 getBean()方法时,IOC 容器触发依赖注入。
2)、非懒加载。当用户在配置文件中将元素配置了 lazy-init=false 属性,即让容器在解析注册 Bean 定义时进行预实例化,触发依赖注入。
3.2 getBean()的入口
顶层BeanFactory接口里是就有这个方法的,spring现在里面就一个主要BeanFactory实现类DefaultListableBeanFactory,各大上下文也是内置的它,最终再交给父类AbstractBeanFactory的getBean
真正的逻辑是在doGetBean方法里
3.3 别名name解析
执行transformedBeanName方法,具体的内容就是判断我们传递的name是不是以“&”开头的,要是的需要去掉再返回。需要注意的是,我们传递的name可能是bean的别名,也可能是BeanFactory,所以transformedBeanName的操作的目的就是取到真正的bean的名字,以便开始后续的流程。
3.4 三级缓存中取
去缓存中获取对象(三级缓存)
singletonObjects 单例缓存池,用于保存创建完成的对象
1.先去单例缓存池中获取,看是否有创建好的对象,有直接返回对象.
2.在单例缓存池singletonObjects中没有找到对象, 且singletonsCurrentlyInCreation 保存了当前真正创建的对象,那么可能标识二级缓存中有对象 就会尝试从二级缓存中获取
3.去二级缓存earlySingletonObjects 中获取,若没有获取到,就要去三级缓存中获取
4.singletonFactories 去三级缓存中获取,那么什么时候放入到容器中的了?
在调用CreateBeanInstance 之后,populateBean之前放进去的.(以ObjectFactory形式放进去的)
注意这里如果取出来有的话,可能是FactoryBean,在这里我们需要处理工厂bean,则调用工厂的获取就可以完毕了
3.5 先从父工厂求解决
获取父工厂,判断父工厂不为空而且当前容器不包含这个BeanDefinition,就走父工厂的getBean,递归开始。
3.6 Bean本身信息处理
1.合并Bean的定义,存在父bean是一个抽象bean,专门用来给子bean做继承的
2.校验当前的bean定义,若当前的bean定义是abstract的话直接抛出异常
3.解析依赖的bean,并且注册依赖,比如InstancA创建该对象 必须要依赖InstanceB的话,那么就会首先创建B对象(使用@DependsOn)
3.7 根据不同scope创建Bean
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
destroySingleton(beanName);
throw ex;
}
});
1.根据 Bean 定义的模式,采取的不同创建 Bean 实例对象的策略,具体的 Bean实例 对象的创 建过程 由实现了 ObjectFactory 接口 的匿名内 部类的 createBean()方法完成,ObjectFactory 使 用 委 派 模 式 , 具 体 的 Bean 实 例 创 建 过 程 交 由 其 实 现 类AbstractAutowireCapableBeanFactory 完成
2.最后再处理一下创建的对象可能不是我们想要的也有可能是FactoryBean,在这里处理下工厂bean
3.8 开始创建createBean
AbstractAutowireCapableBeanFactory 类实现了 ObjectFactory 接口,创建容器指定的 Bean 实例对象,同时还对创建的 Bean 实例对象进行初始化处理。
主要两个
1.resolveBeforeInstantiation,就是在这里给后置处理器一个机会来返回代理对象,但是在这里并没有创建代理对象,仅仅是把切面信息解析出来缓存好
2.doCreateBean,真正的逻辑,
*封装了要被创建的 Bean 对象BeanWrapper
*核心,createBeanInstance这里开始创建真实例对象
*调用MergedBeanDefinitionPostProcessor后置处理器 合并Bean定义的
*向容器中缓存单例模式的 早期(还没populateBean)Bean 对象引用, 以防循环引用
*核心,populateBean ,给Bean示例对象属性赋值
*initializeBean 初始化 Bean 对象
3.9 实例化createBeanInstance
1.对使用工厂方法和自动装配特性的 Bean 的实例化相当比较清楚,调用相应的工厂方法或者参数匹配的构造方法完成实例化对象的工作
2.默认无参构造方法就需要使用相应的初始化策略(JDK 的反射机制或者 CGLib)来进行初始化了 。
如果 Bean 没有方法被覆盖了,则使用 JDK 的反射机制进行实例化,否则需要去覆盖方法的话,使用 CGLib技术 进行实例化。
3.10 存储早期对象
解决循环依赖
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
3.11 依赖属性注入
populateBean 方法
1 概括
populateBean解析BeanDefinition中设置的property值,获取到Bean的依赖信息并设置Bean的依赖关系。通过使用BeanDefinitionResolver对BeanDefinition进行解析,然后注入到property中。
2.解析类型
populateBean=》applyPropertyValues=》resolveValueIfNecessary方法
包含了对所有的注入类型的处理,里面包含各种instanceof的判断,比如RuntimeBeanReference(就是普通类型意思),Array,List,Map等数据类型,RuntimeBeanReference是在对BeanDefinition进行解析时生成的数据对象,是在载入BeanDefinition时根据配置生成的,如果RuntimeBeanReference是在双亲容器中则从双亲容器中去获取Bean(通过getBean方法形成递归)。
3.设置进实例
完成解析过程后就已经是为依赖注入做好了准备条件,依赖注入的发生是在BeanWrapper的setPropertyValues中
setPropertyValues:
1)、对于集合类型的属性,将其属性值解析为目标类型的集合后直接赋值给属性。
2)、对于非集合类型的属性,大量使用了 JDK 的反射机制,通过属性的 getter()方法获取指定属性注入以前的值,同时调用属性的 setter()方法为属性设置注入后的值。