Spring源码更新完毕,10W+字的Spring源码系列文章送给你

哈喽大家好呀,我是黎杜,近期一直在更新Spring源码的部分文章,到现在为止源码系列已经更新完了,下面为了完整性,更新一篇关于Spring源码入门的基础篇,大家可以要阅读源码的,可以先从这一篇文章入手,然后按照本文的思路走,不说掌握的100%,起码百分之七八十是有的,但是要求是使用过Spring进行开发的同学,有Spring基础的,如果你有SSM的开发经验,那其实更好了,因为现在SpringBoot虽然作为一站式开发框架,屏蔽了很多底层的东西,但是对于想要走底层的程序员是很不友好的,就是傻瓜式一般的只会使用。下面就开始源码之路吧。

Spring Ioc源码分析系列--Ioc的基础知识准备

Ioc的概念

在Spring里,Ioc的定义为The IoC Container,翻译过来也就是Ioc容器。为什么会被叫做容器呢?我们来比对一下日常生活中的容器,也就是那些瓶瓶罐罐。假设我们有个大米缸,里面提前放好了米,等我们需要米的时候,我们就可以到大米缸里面取。那么Ioc也是一样的道理,里面有一个容器singletonObjects(提前透露这里容器的类型是ConcurrentHashMap),里面放好了各种初始化好的bean,当我们代码需要使用的时候,就到里面去取。

借助一张图来看一下Spring Ioc的工作流程。整个过程就如同上面描述类似,把业务类pojo和一些元数据配置信息Configuration Metadata提供到Ioc,Ioc会根据你给的信息生成可以使用的Bean,这里生成的bean是可以直接使用的,Ioc是不是替我们省去了一大堆new的工作。当然这里面涉及非常多的细节,例如怎么获取元数据,怎么根据元数据生成想要的bean,这些都会在后续解答。

那么问题来了,为什么需要一个容器,我随手new个对象不香吗?要讨论这个问题,可以对比有容器和没有容器的区别,我个人认为有以下比较显著的优点

  • 方便管理。容器提供了一个集中化的管理,方便进行其他的操作,例如Aop相关的功能实现。无容器的无法集中管理bean,所有bean散落到项目的各个角落,如果要进行一些额外的调整需要改动的点非常多。

  • 性能节省。容器只需初始化一次bean,后续使用只需要直接获取。而无容器需要每次new对象,开销相比较而言肯定会更大。

  • 代码美观。容器屏蔽了复杂对象的构造过程,对于使用而言只需要直接去获取,无容器需要每次构造复杂对象,代码重复率非常高,想想你的项目里充满了各种new对象的代码,是不是就已经让你很头疼。

那么一个东西不可能只有优点而没有缺点,任何事物都需要辩证地去看待,那么提供容器后的缺点是什么?个人认为有如下比较显著的缺点

  • 并发安全。提供了一个集中式的容器管理,不可避免得在多线程情况下出现并发访问的情况,那么在保证线程安全的时候需要付出额外的性能开销。

  • 启动缓慢。同理,提供了一个集中式的容器管理,那么就需要在启动之初就把需要的各种bean初始化好,放入容器中,尽管这些bean不一定会被用到。如果没有指定初始化时机,那么这部分没有使用的bean也会在启动之初就进行初始化,这相比使用时再创建当然会消耗了额外的性能。

  • 内存隐患。由于对象都放在容器里,那么在有许多大对象或者对象的生命周期都非常的长的时候,需要考虑对象太多造成的内存开销。

这里简单分析了一下优缺点,当然这只是一家之言,有错漏补充欢迎指出。目前来看,Spring的优点远远大于其缺点,这也是Spring经久不衰的原因。

经过上面的介绍,我相信你已经对Ioc有个初步的整体认识。即这是一个容器,里面放好了可以使用的bean请牢记这个结论。那么接下来会介绍Ioc的一些知识体系,留下个整体轮廓就行,不涉及太多了源码分析。

BeanFactory 还是 ApplicationContext

本节说明 BeanFactoryApplicationContext 容器级别之间的差异以及对使用Ioc的影响。相信尝试看过Ioc源码的人都会被这两个迷惑过,BeanFactoryApplicationContext提供的功能看起来似乎是类似的,那么这两个玩意有啥联系和区别呢?

我们通常推荐使用ApplicationContext,除非有充分的理由不这样做,否则应该使用 ApplicationContext,通常将 GenericApplicationContext 及其子类 AnnotationConfigApplicationContext作为自定义引导的常见实现。这些是 Spring 核心容器的主要入口点,用于所有常见目的:加载配置文件、触发类路径扫描、以编程方式注册 bean 定义和带注释的类,以及(从 5.0 开始)注册功能 bean 定义。

因为 ApplicationContext包含 BeanFactory的所有功能,所以通常建议使用 ApplicationContext,除非需要完全控制 bean 处理的场景。在 ApplicationContext(例如 GenericApplicationContext实现)中,按照约定(即按 bean 名称或按 bean 类型 —特别是后处理器)检测几种 bean,而普通的 DefaultListableBeanFactory 不知道任何特殊的 bean

对于许多扩展容器特性,例如注解处理和 AOP 代理,BeanPostProcessor扩展点是必不可少的。如果你仅使用普通的 DefaultListableBeanFactory,则默认情况下不会检测和激活此类后处理器。这种情况可能会令人困惑,因为您的 bean 配置实际上没有任何问题。相反,在这种情况下,需要通过额外的设置来完全引导容器。

下表列出了 BeanFactoryApplicationContext 接口和实现提供的功能。

特性BeanFactoryApplicationContext
Bean实例化/注入YesYes
集成的生命周期管理NoYes
自动 BeanPostProcessor 注册NoYes
自动 BeanFactoryPostProcessor 注册NoYes
方便的 MessageSource 访问(用于国际化)NoYes
内置ApplicationEvent发布机制NoYes

要使用 DefaultListableBeanFactory 显式注册 bean 后处理器,您需要以编程方式调用 addBeanPostProcessor(),如以下示例所示:

DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
// 用 bean 定义填充工厂

// 现在注册任何需要的 BeanPostProcessor 实例
factory.addBeanPostProcessor(new AutowiredAnnotationBeanPostProcessor());
factory.addBeanPostProcessor(new MyBeanPostProcessor());

// 现在开始使用工厂

要将 BeanFactoryPostProcessor 应用于普通的 DefaultListableBeanFactory,您需要调用其 postProcessBeanFactory() 方法,如以下示例所示:

DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(new FileSystemResource("beans.xml"));

// 从属性文件中引入一些属性值
PropertySourcesPlaceholderConfigurer cfg = new PropertySourcesPlaceholderConfigurer();
cfg.setLocation(new FileSystemResource("jdbc.properties"));

// 现在实际进行替换
cfg.postProcessBeanFactory(factory);

在这两种情况下,显式注册步骤都不方便,这就是为什么在 Spring 支持的应用程序中各种 ApplicationContext 变体优于普通 DefaultListableBeanFactory 的原因,尤其是在典型企业设置中依赖 BeanFactoryPostProcessorBeanPostProcessor 实例来扩展容器功能时。

AnnotationConfigApplicationContext 注册了所有常见的注释后处理器,并且可以通过配置注释(例如@EnableTransactionManagement)在幕后引入额外的处理器。在 Spring 的基于注解的配置模型的抽象级别上,bean 后置处理器的概念变成了纯粹的内部容器细节。

Spring的统一资源加载策略

资源抽象Resource

在Spring里, org.springframework.core.io.Resource 为 Spring 框架所有资源的抽象和访问接口,它继承 org.springframework.core.io.InputStreamSource接口。作为所有资源的统一抽象,Resource 定义了一些通用的方法,由子类 AbstractResource 提供统一的默认实现。定义如下:

public interface Resource extends InputStreamSource {

 /**
  * 资源是否存在
  */
 boolean exists();

 /**
  * 资源是否可读
  */
 default boolean isReadable() {
  return true;
 }

 /**
  * 资源所代表的句柄是否被一个 stream 打开了
  */
 default boolean isOpen() {
  return false;
 }

 /**
  * 是否为 File
  */
 default boolean isFile() {
  return false;
 }

 /**
  * 返回资源的 URL 的句柄
  */
 URL getURL() throws IOException;

 /**
  * 返回资源的 URI 的句柄
  */
 URI getURI() throws IOException;

 /**
  * 返回资源的 File 的句柄
  */
 File getFile() throws IOException;

 /**
  * 返回 ReadableByteChannel
  */
 default ReadableByteChannel readableChannel() throws IOException {
  return java.nio.channels.Channels.newChannel(getInputStream());
 }

 /**
  * 资源内容的长度
  */
 long contentLength() throws IOException;

 /**
  * 资源最后的修改时间
  */
 long lastModified() throws IOException;

 /**
  * 根据资源的相对路径创建新资源
  */
 Resource createRelative(String relativePath) throws IOException;

 /**
  * 资源的文件名
  */
 @Nullable
 String getFilename();

 /**
  * 资源的描述
  */
 String getDescription();

}

子类结构如下:

从上图可以看到,Resource 根据资源的不同类型提供不同的具体实现,如下:

  • FileSystemResource :对 java.io.File 类型资源的封装,只要是跟 File 打交道的,基本上与 FileSystemResource 也可以打交道。支持文件和 URL 的形式,实现 WritableResource 接口,且从 Spring Framework 5.0 开始,FileSystemResource 使用 NIO2 API进行读/写交互。

  • ByteArrayResource :对字节数组提供的数据的封装。如果通过 InputStream 形式访问该类型的资源,该实现会根据字节数组的数据构造一个相应的 ByteArrayInputStream。

  • UrlResource :对 java.net.URL类型资源的封装。内部委派 URL 进行具体的资源操作。

  • ClassPathResource :class path 类型资源的实现。使用给定的 ClassLoader 或者给定的 Class 来加载资源。

  • InputStreamResource :将给定的 InputStream 作为一种资源的 Resource 的实现类。

org.springframework.core.io.AbstractResource ,为 Resource 接口的默认抽象实现。它实现了 Resource 接口的大部分的公共实现

资源定位ResourceLoader

Spring 将资源的定义和资源的加载区分开了,Resource 定义了统一的资源,那资源的加载则由 ResourceLoader 来统一定义

org.springframework.core.io.ResourceLoader 为 Spring 资源加载的统一抽象,具体的资源加载则由相应的实现类来完成,所以我们可以将 ResourceLoader 称作为统一资源定位器。其定义如下:

/**
 * 用于加载资源(例如类路径或文件系统资源)的策略接口。
 * 需要 {@link org.springframework.context.ApplicationContext} 来提供此功能,
 * 以及扩展的 {@link org.springframework.core.io.support.ResourcePatternResolver} 支持。 
 * <p>{@link DefaultResourceLoader} 是一个独立的实现,可以在 ApplicationContext 之外使用,也被 {@link ResourceEditor} 使用。 
 * <p>在 ApplicationContext 中运行时,可以使用特定上下文的资源加载策略从字符串中填充类型为 Resource 和 Resource 数组的 Bean 属性。
 *
 */
public interface ResourceLoader {

 /** Pseudo URL prefix for loading from the class path: "classpath:". */
 String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;


 /**
  * Return a Resource handle for the specified resource location.
  * <p>The handle should always be a reusable resource descriptor,
  * allowing for multiple {@link Resource#getInputStream()} calls.
  * <p><ul>
  * <li>Must support fully qualified URLs, e.g. "file:C:/test.dat".
  * <li>Must support classpath pseudo-URLs, e.g. "classpath:test.dat".
  * <li>Should support relative file paths, e.g. "WEB-INF/test.dat".
  * (This will be implementation-specific, typically provided by an
  * ApplicationContext implementation.)
  * </ul>
  * <p>Note that a Resource handle does not imply an existing resource;
  * you need to invoke {@link Resource#exists} to check for existence.
  * @param location the resource location
  * @return a corresponding Resource handle (never {@code null})
  * @see #CLASSPATH_URL_PREFIX
  * @see Resource#exists()
  * @see Resource#getInputStream()
  */
 Resource getResource(String location);

 /**
  * Expose the ClassLoader used by this ResourceLoader.
  * <p>Clients which need to access the ClassLoader directly can do so
  * in a uniform manner with the ResourceLoader, rather than relying
  * on the thread context ClassLoader.
  * @return the ClassLoader
  * (only {@code null} if even the system ClassLoader isn't accessible)
  * @see org.springframework.util.ClassUtils#getDefaultClassLoader()
  * @see org.springframework.util.ClassUtils#forName(String, ClassLoader)
  */
 @Nullable
 ClassLoader getClassLoader();

}
  • #getResource(String location) 方法,根据所提供资源的路径 location 返回 Resource 实例,但是它不确保该 Resource 一定存在,需要调用 Resource#exist() 方法来判断。

  • 该方法支持以下模式的资源加载:

    • URL位置资源,如 "file:C:/test.dat"

    • ClassPath位置资源,如 "classpath:test.dat

    • 相对路径资源,如 "WEB-INF/test.dat" ,此时返回的Resource 实例,根据实现不同而不同。

  • 该方法的主要实现是在其子类 DefaultResourceLoader中实现,具体过程我们在分析 DefaultResourceLoader时做详细说明。

  • #getClassLoader() 方法,返回 ClassLoader实例,对于想要获取 ResourceLoader 使用的 ClassLoader 用户来说,可以直接调用该方法来获取。在分析 Resource 时,提到了一个类 ClassPathResource ,这个类是可以根据指定的 ClassLoader 来加载资源的。

子类结构如下:

  • DefaultResourceLoaderAbstractResource 相似,org.springframework.core.io.DefaultResourceLoaderResourceLoader 的默认实现。

  • FileSystemResourceLoader继承 DefaultResourceLoader ,且覆写了 #getResourceByPath(String) 方法,使之从文件系统加载资源并以 FileSystemResource 类型返回,这样我们就可以得到想要的资源类型。

  • ClassRelativeResourceLoaderDefaultResourceLoader 的另一个子类的实现。和 FileSystemResourceLoader 类似,在实现代码的结构上类似,也是覆写 #getResourceByPath(String path) 方法,并返回其对应的 ClassRelativeContextResource 的资源类型。

  • PathMatchingResourcePatternResolverResourcePatternResolver 最常用的子类,它除了支持 ResourceLoaderResourcePatternResolver 新增的 classpath*: 前缀外,还支持 Ant 风格的路径匹配模式(类似于 "**/*.xml")。

至此 Spring 整个资源记载过程已经分析完毕。下面简要总结下:

  • Spring 提供了 ResourceResourceLoader 来统一抽象整个资源及其定位。使得资源与资源的定位有了一个更加清晰的界限,并且提供了合适的 Default 类,使得自定义实现更加方便和清晰。

  • AbstractResource 为 Resource 的默认抽象实现,它对 Resource 接口做了一个统一的实现,子类继承该类后只需要覆盖相应的方法即可,同时对于自定义的 Resource 我们也是继承该类。

  • DefaultResourceLoader 同样也是 ResourceLoader 的默认实现,在自定 ResourceLoader 的时候我们除了可以继承该类外还可以实现 ProtocolResolver 接口来实现自定资源加载协议。

  • DefaultResourceLoader每次只能返回单一的资源,所以 Spring 针对这个提供了另外一个接口 ResourcePatternResolver ,该接口提供了根据指定的 locationPattern 返回多个资源的策略。其子类 PathMatchingResourcePatternResolver 是一个集大成者的 ResourceLoader ,因为它即实现了 Resource getResource(String location) 方法,也实现了 Resource[] getResources(String locationPattern) 方法。

BeanFactory与ApplicationContext体系

BeanFactory体系

下面来介绍一下Ioc的核心实现有哪些重要的类,先看BeanFactory的体系,类结构如下,这里把spring-context部分的实现去掉了。

可以看到里面的类还是比较多的,但是各司其职,每个类都有自己对应的职责,下面来介绍几个比较重点的类。

  • AutowireCapableBeanFactory接口提供了对现有bean进行自动装配的能力,设计目的不是为了用于一般的应用代码中,对于一般的应用代码应该使用BeanFactoryListableBeanFactory。其他框架的代码集成可以利用这个接口去装配和填充现有的bean的实例,但是Spring不会控制这些现有bean的生命周期。

  • ConfigurableBeanFactory提供了bean工厂的配置机制(除了BeanFactory接口中的bean的工厂的客户端方法)。该BeanFactory接口不适应一般的应用代码中,应该使用BeanFactoryListableBeanFactory。该扩展接口仅仅用于内部框架的使用,并且是对bean工厂配置方法的特殊访问。

  • ConfigurableListableBeanFactory接口继承自ListableBeanFactory, AutowireCapableBeanFactory, ConfigurableBeanFactory。大多数具有列出能力的bean工厂都应该实现此接口。此了这些接口的能力之外,该接口还提供了分析、修改bean的定义和单例的预先实例化的机制。这个接口不应该用于一般的客户端代码中,应该仅仅提供给内部框架使用。

  • AbstractBeanFactory 继承自FactoryBeanRegistrySupport,实现了ConfigurableBeanFactory接口。AbstractBeanFactoryBeanFactory的抽象基础类实现,提供了完整的ConfigurableBeanFactory的能力。

    • 单例缓存

    • 别名的管理

    • FactoryBean的处理

    • 用于子bean定义的bean的合并

    • bean的摧毁接口

    • 自定义的摧毁方法

    • BeanFactory的继承管理

  • AbstractAutowireCapableBeanFactory继承自AbstractBeanFactory,实现了AutowireCapableBeanFactory接口。该抽象了实现了默认的bean的创建。

    • 提供了bean的创建、属性填充、装配和初始化

    • 处理运行时bean的引用,解析管理的集合、调用初始化方法等

    • 支持构造器自动装配,根据类型来对属性进行装配,根据名字来对属性进行装配

  • DefaultListableBeanFactory 继承自AbstractAutowireCapableBeanFactory,实现了ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable接口。这个类是一个非常完全的BeanFactory,基于bean的定义元数据,通过后置处理器来提供可扩展性。

  • XmlBeanFactory 继承自DefaultListableBeanFactory,用来从xml文档中读取bean的定义的一个非常方便的类。最底层是委派给XmlBeanDefinitionReader,实际上等价于带有XmlBeanDefinitionReaderDefaultListableBeanFactory。该类已经废弃,推荐使用的是DefaultListableBeanFactory

ApplicationContext体系

接下来看看更高层次的容器实现ApplicationContext的体系。类结构图如下,这里只展示了常用的实现,并且去掉了大部分spring-web模块的实现类:

  • ConfigurableApplicationContext 从上面的类的继承层次图能看到,ConfigurableApplicationContext是比较上层的一个接口,该接口也是比较重要的一个接口,几乎所有的应用上下文都实现了该接口。该接口在ApplicationContext的基础上提供了配置应用上下文的能力,此外提供了生命周期的控制能力。

  • AbstractApplicationContextApplicationContext接口的抽象实现,这个抽象类仅仅是实现了公共的上下文特性。这个抽象类使用了模板方法设计模式,需要具体的实现类去实现这些抽象的方法。

  • GenericApplicationContext继承自AbstractApplicationContext,是为通用目的设计的,它能加载各种配置文件,例如xml,properties等等。它的内部持有一个DefaultListableBeanFactory的实例,实现了BeanDefinitionRegistry接口,以便允许向其应用任何bean的定义的读取器。为了能够注册bean的定义,refresh()只允许调用一次。

  • AnnotationConfigApplicationContext继承自GenericApplicationContext,提供了注解配置(例如:Configuration、Component、inject等)和类路径扫描(scan方法)的支持,可以使用register(Class... annotatedClasses)来注册一个一个的进行注册。实现了AnnotationConfigRegistry接口,来完成对注册配置的支持,只有两个方法:register()scan()。内部使用AnnotatedBeanDefinitionReader来完成注解配置的解析,使用ClassPathBeanDefinitionScanner来完成类路径下的bean定义的扫描。

  • AbstractXmlApplicationContext继承自AbstractRefreshableConfigApplicationContext,用于描绘包含能被XmlBeanDefinitionReader所理解的bean定义的XML文档。子类只需要实现getConfigResourcesgetConfigLocations来提供配置文件资源。

  • ClassPathXmlApplicationContext继承自AbstractXmlApplicationContext,和FileSystemXmlApplicationContext类似,只不过ClassPathXmlApplicationContext是用于处理类路径下的xml配置文件。文件的路径可以是具体的文件路径,例如:xxx/application.xml,也可以是ant风格的配置,例如:xxx/*-context.xml

  • AnnotationConfigWebApplicationContext继承自AbstractRefreshableWebApplicationContext,接受注解的类作为输入(特殊的@Configuration注解类,一般的@Component注解类,与JSR-330兼容的javax.inject注解)。允许一个一个的注入,同样也能使用类路径扫描。对于web环境,基本上是和AnnotationConfigApplicationContext等价的。使用AnnotatedBeanDefinitionReader来对注解的bean进行处理,使用ClassPathBeanDefinitionScanner来对类路径下的bean进行扫描。

小结

这篇主要做了一些基础知识的准备,简单介绍了一些Ioc的概念,这里并没有举代码例子,只是通过生活中的容器去类比了一下Spring的容器。接下来对比分析了BeanFactoryApplicationContext区别与联系,然后介绍了Spring的资源加载,Spring的许多元数据加载通过统一资源加载的方式去获取的,特别是classpath路径下文件的获取。最后我们简单看了一下BeanFactoryApplicationContext的体系结构,展示常见的类图,并且有简单的描述,但是没有涉及太多的代码分析,主要也是混个眼熟。

那么有了这些准备,下一篇,我们就会通过一个xml配置文件去加载配置,通过Spring容器获取我们需要的bean,那么这就会用到这篇文章介绍过的资源加载,BeanFactory以及ApplicationContext体系里的类等等。

那么下面的文章就会进行真正的源码分析了,庖丁解牛。

如果有人看到这里,那在这里老话重提。与君共勉,路漫漫其修远兮,吾将上下而求索

前言

上一篇文章Spring Ioc源码分析系列--Ioc的基础知识准备介绍了Ioc的基础概念以及Spring Ioc体系的部分基础知识。那么这一篇就会真正通过一个例子,启动Ioc容器,获取容器里的bean

首先说明,本文的例子是基于xml配置文件去完成的。

为什么是xml?因为xml是Spring的灵魂,可能我们初学Spring都会有畏难情绪,看到繁杂的xml就会打退堂鼓。但是实际上不然,xml的格式是相当清晰的,一个配置文件可以说没有一行配置是多余的。现在大部分的配置是用注解去完成的,相比xml而言是简洁许多,但是对于我们初学而言,xml其实是更好的方式xml相对于注解而言是相对繁杂,但是它的信息也更多更明确,注解只是添加了一个注解就完成配置,细节上是更为隐蔽的。再加上xml配置文件和注解配置的原理是相通的,核心思想是一样的,掌握核心就万变不离其宗。所以这系列文章的例子大部分都会采取xml的方式去配置,当然后续可能也会补充一下注解方式的例子和分析文章。

万事开头难,如果实在觉得看不懂但又想学的,可以硬着头皮看下去,等以后回过头来再看的时候,会有豁然开朗的感觉。

源码分析

启动容器示例

废话少说,下面开始搞个例子分析一下。所有源码都在我的仓库ioc-sourcecode-analysis-code-demo里找到。

首先弄个实体类User

/**
 * @author Codegitz
 * @date 2022/4/26 10:58
 **/
public class User {
 private String id;

 private String name;

 private String age;
}

创建业务类UserService以及业务实现类UserServiceImpl,这里的逻辑很简单,就是根据传入的nameage返回一个新的user对象。

/**
 * @author Codegitz
 * @date 2022/4/26 10:59
 **/
public interface UserService {
 User getUser(String name,String age);
}

public class UserServiceImpl implements UserService {
 @Override
 public User getUser(String name, String age) {
  User user = new User();
  user.setId("1");
  user.setName(name);
  user.setAge(age);
  return user;
 }
}

业务类的准备工作已经完成了,接下来就是要写个xml配置文件,告诉Spring Ioc我需要一个UserServicebean

<?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="userService" class="io.codegitz.service.impl.UserServiceImpl"/>
</beans>

这个xml配置文件比较简单,我们来解释一下每一行是什么意思。

<?xml version="1.0" encoding="UTF-8"?>为xml文件的规定头,没啥好说的。

xmlns="http://www.springframework.org/schema/beans"表明了xml的命名空间,xmlns全称为xml namespace,一个xml里面命名空间可以有多个。

xmlns:xsixsi:schemaLocation是指明了xml文件的验证模型和验证模型文件的位置。可以看到验证模型能通过http方式获取,但是为了防止网络抖动的影响,一般会在本地存放验证文件。spring-beans.xsd就可以在图示的目录下找到。



接下来就到了<bean id="userService" class="io.codegitz.service.impl.UserServiceImpl"/>这一行,这是我们获取一个Bean的关键配置,这里告诉Ioc我需要一个iduserServiceclassio.codegitz.service.impl.UserServiceImpl的Bean。

到这里配置已经完成了,接下来创建一个引导类启动Ioc就能获取到这个bean了。

新建引导类Application,通过ClassPathXmlApplicationContext类引导启动Ioc容器,这是加载xml配置的常用引导类。

/**
 * @author Codegitz
 * @date 2022/4/26 10:57
 **/
public class Application {
 public static void main(String[] args) {
  ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
  UserService userService = (UserService) applicationContext.getBean("userService");
  User codegitz = userService.getUser("codegitz", "25");
  System.out.println(codegitz);
 }
}

到这里其实已经完成了所有样例的准备,可以启动容器了。可以看到,只是简单的ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");就可以启动一个容器,随后就能通过getBean()方法获取容器里的bean了,接下来我们看看new ClassPathXmlApplicationContext("beans.xml")发生了什么。

容器启动分析

ClassPathXmlApplicationContext构造方法

这里我们使用的是ClassPathXmlApplicationContext,那么就从这个类的构造方法开始分析。

摘取的代码片段我会保留原文注释,看官可以细细品味。

/**
  * Create a new ClassPathXmlApplicationContext, loading the definitions
  * from the given XML file and automatically refreshing the context.
  * @param configLocation resource location
  * @throws BeansException if context creation failed
  */
 public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
  this(new String[] {configLocation}, true, null);
 }

继续跟进构造函数,根据方法上的注释和代码可以看到。这里就做了两件事,根据给定的xml配置文件加载BeanDefinition,然后调用refresh()方法刷新容器。

/**
  * Create a new ClassPathXmlApplicationContext with the given parent,
  * loading the definitions from the given XML files.
  * @param configLocations array of resource locations
  * @param refresh whether to automatically refresh the context,
  * loading all bean definitions and creating all singletons.
  * Alternatively, call refresh manually after further configuring the context.
  * @param parent the parent context
  * @throws BeansException if context creation failed
  * @see #refresh()
  */
 public ClassPathXmlApplicationContext(
   String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
   throws BeansException {

  // 初始化父类
  super(parent);
  // 设置配置文件信息,这里会进行占位符的替换
  // 例如/${env}-beans.xml类型的路径会被占位符替换,如果env=sit,那么路径就会被替换为/sit-beans.xml
  setConfigLocations(configLocations);
  // 如果没有刷新,就会调用refresh()方法进行刷新,这个方法是整个Ioc的关键入口,由父类AbstractApplicationContext实现
  if (refresh) {
   refresh();
  }
 }

前面两个方法比较简单,可以跟着注释看一下。重点在refresh()方法。这个方法会完成Ioc所需要的所有配置加载,完成所有bean的注册以及Spring的一系列实现。

refresh()方法

到这里,才是真正摸到了Ioc的源码入口。看一下refresh()方法的代码。

public void refresh() throws BeansException, IllegalStateException {
  //给容器refresh加锁,避免容器在refresh阶段时,容器进行了初始化或者销毁操作
  synchronized (this.startupShutdownMonitor) {
   // Prepare this context for refreshing.
   //调用容器准备刷新的方法,获取容器当前时间,同时给容器设置同步标识、具体方法
   prepareRefresh();

   // Tell the subclass to refresh the internal bean factory.
   /**
    * 告诉子类启动refreshBeanFactory方法,refreshBeanFactory方法会载入bean的定义文件,该方法的实现会针对xml配置,最终创建内部容器
    * 该容器负责bean的创建与管理,会进行BeanDefinition的注册
    */
   ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

   // Prepare the bean factory for use in this context.
   //注册一些容器中需要使用的系统bean,例如classloader,BeanFactoryPostProcessor等
   prepareBeanFactory(beanFactory);

   try {
    // Allows post-processing of the bean factory in context subclasses
    // 允许容器的子类去注册PostProcessor,钩子方法.
    postProcessBeanFactory(beanFactory);

    // Invoke factory processors registered as beans in the context.
    //激活容器中注册为bean的BeanFactoryPostProcessor
    //对于注解容器,org.springframework.context.annotation.ConfigurationClassPostProcessor
    invokeBeanFactoryPostProcessors(beanFactory);

    // Register bean processors that intercept bean creation.
    registerBeanPostProcessors(beanFactory);

    // Initialize message source for this context.
    initMessageSource();

    // Initialize event multicaster for this context.
    initApplicationEventMulticaster();

    // Initialize other special beans in specific context subclasses.
    onRefresh();

    // Check for listener beans and register them.
    registerListeners();

    // Instantiate all remaining (non-lazy-init) singletons.
    // 创建对象
    finishBeanFactoryInitialization(beanFactory);

    // Last step: publish corresponding event.
    finishRefresh();
   }

   catch (BeansException ex) {
    if (logger.isWarnEnabled()) {
     logger.warn("Exception encountered during context initialization - " +
       "cancelling refresh attempt: " + ex);
    }

    // Destroy already created singletons to avoid dangling resources.
    destroyBeans();

    // Reset 'active' flag.
    cancelRefresh(ex);

    // Propagate exception to caller.
    throw ex;
   }

   finally {
    // Reset common introspection caches in Spring's core, since we
    // might not ever need metadata for singleton beans anymore...
    resetCommonCaches();
   }
  }
 }

咋一看代码量非常大,但是逻辑是比较清晰的,结合注释来看,再调试一遍,没啥大问题。

下面对方法进行逐个分析,鉴于一次性写完会比较长,本文先分析前三个方法,即prepareRefresh()obtainFreshBeanFactory()prepareBeanFactory(beanFactory)三个方法。

prepareRefresh()方法

跟进prepareRefresh()方法,这个方法主要是做了一些初始化属性设置和校验。

/**
  * Prepare this context for refreshing, setting its startup date and
  * active flag as well as performing any initialization of property sources.
  */
 protected void prepareRefresh() {
  // Switch to active.
  this.startupDate = System.currentTimeMillis();
  this.closed.set(false);
  this.active.set(true);
        
        // 省略部分日志...

  // Initialize any placeholder property sources in the context environment.
  // 初始化一些属性,交由子类实现
  initPropertySources();

  // Validate that all properties marked as required are resolvable:
  // see ConfigurablePropertyResolver#setRequiredProperties
  // 校验是否有必须的属性,如果有必须的属性但是环境没配置,则抛出异常
  getEnvironment().validateRequiredProperties();

  // Store pre-refresh ApplicationListeners...
  // 存储在refresh之前注册的ApplicationListener,避免这部分的ApplicationListener在后续的调用中被丢失
  // 这是一个2019年修复的bug
  if (this.earlyApplicationListeners == null) {
   this.earlyApplicationListeners = new LinkedHashSet<>(this.applicationListeners);
  }
  else {
   // Reset local application listeners to pre-refresh state.
   this.applicationListeners.clear();
   this.applicationListeners.addAll(this.earlyApplicationListeners);
  }

  // Allow for the collection of early ApplicationEvents,
  // to be published once the multicaster is available...
  this.earlyApplicationEvents = new LinkedHashSet<>();
 }

这个里面比较简单,没啥好看。稍微来看下getEnvironment().validateRequiredProperties()的代码。

/**
  * Return the {@code Environment} for this application context in configurable
  * form, allowing for further customization.
  * <p>If none specified, a default environment will be initialized via
  * {@link #createEnvironment()}.
  */
 @Override
 public ConfigurableEnvironment getEnvironment() {
  if (this.environment == null) {
   this.environment = createEnvironment();
  }
  return this.environment;
 }

 /**
  * Create and return a new {@link StandardEnvironment}.
  * <p>Subclasses may override this method in order to supply
  * a custom {@link ConfigurableEnvironment} implementation.
  */
 protected ConfigurableEnvironment createEnvironment() {
  return new StandardEnvironment();
 }

可以看到这里返回的是StandardEnvironment类型Environment,这里的逻辑就是有则返回,无则创建。接下来看validateRequiredProperties()方法。最终实现是在AbstractPropertyResolver#validateRequiredProperties()方法。

@Override
 public void validateRequiredProperties() {
  MissingRequiredPropertiesException ex = new MissingRequiredPropertiesException();
  // 遍历requiredProperties,逐个去当前环境获取是否存在
  for (String key : this.requiredProperties) {
   if (this.getProperty(key) == null) {
    ex.addMissingRequiredProperty(key);
   }
  }
  // 如果存在缺失的必须属性,则抛出异常
  if (!ex.getMissingRequiredProperties().isEmpty()) {
   throw ex;
  }
 }

prepareRefresh()方法比较简单,到此已经基本看完,接下来看下一个obtainFreshBeanFactory()方法。

obtainFreshBeanFactory()方法

obtainFreshBeanFactory()方法从名字上就知道是获取并且同时刷新一个beanfactory

在没有分析代码之前,先来猜测一下它的实现。我们知道它最终会返回一个可以使用的beanfactory,说明此时在beanfactory里已经初始化完成了我们定义的BeanDefinition,那么这里应该会有一些基础信息的配置,然后解析xml文件,获取xml配置信息,然后初始化相应的BeanDefinition,准备就绪后返回beanfactory。当然这只是猜测,具体实现如何,马上分析。

那么接下来就跟进这个方法看一下代码实现。

/**
  * Tell the subclass to refresh the internal bean factory.
  * @return the fresh BeanFactory instance
  * @see #refreshBeanFactory()
  * @see #getBeanFactory()
  */
 protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
  // 调用子类的实现,刷新beanFactory
  refreshBeanFactory();
  // 返回刷新完成(即启动完成)的beanFactory
  return getBeanFactory();
 }

跟进refreshBeanFactory()方法,关键地方都已经加上注释,也比较简单易懂,跟着看一下就行。

/**
  * This implementation performs an actual refresh of this context's underlying
  * bean factory, shutting down the previous bean factory (if any) and
  * initializing a fresh bean factory for the next phase of the context's lifecycle.
  * 此实现执行此上下文的底层 bean 工厂的实际刷新,
  * 关闭先前的 bean 工厂(如果有)并为上下文生命周期的下一阶段初始化一个新的 bean 工厂。
  */
 @Override
 protected final void refreshBeanFactory() throws BeansException {
  // 如果有前一个,先关闭
  if (hasBeanFactory()) {
   // 先销毁所有已经注册的bean
   destroyBeans();
   // 关闭工厂,将this.beanFactory设为null
   closeBeanFactory();
  }
  try {
   //创建DefaultListableBeanFactory
   DefaultListableBeanFactory beanFactory = createBeanFactory();
   //为了序列化指定id,如果需要的话,让这个beanFactory从id反序列化到BeanFactory对象
   beanFactory.setSerializationId(getId());
   //定制beanFactory,设置相关属性,包括是否允许覆盖同名称的不同定义的对象已经循环依赖
   //以及设置@Autowire和@Qualifier注册解析器QualifierAnnotationAutowire-CandidateResolver
   customizeBeanFactory(beanFactory);
   //初始化DocumentReader,并进行xml文件读取和解析
   loadBeanDefinitions(beanFactory);
   synchronized (this.beanFactoryMonitor) {
    this.beanFactory = beanFactory;
   }
  }
  catch (IOException ex) {
   throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
  }
 }

前面的准备工作是比较简单的,不再细说。重点在loadBeanDefinitions(beanFactory)方法,这里会加载xml获取我们配置的BeanDefinition

记住这个位置AbstractRefreshableApplicationContext#refreshBeanFactory(),这是经过前面繁杂的摸索后真正进入BeanDefinition加载的分水岭。跟进代码查看。

/**
  * Loads the bean definitions via an XmlBeanDefinitionReader.
  * 通过 XmlBeanDefinitionReader 加载 bean 定义。
  * @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader
  * @see #initBeanDefinitionReader
  * @see #loadBeanDefinitions
  */
 @Override
 protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
  // Create a new XmlBeanDefinitionReader for the given BeanFactory.
  //为指定 beanFactory创建XmlBeanDefinitionReader
  XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

  // Configure the bean definition reader with this context's
  // resource loading environment.
  //对 beanDefinitionReader 进行环境变量的设置
  beanDefinitionReader.setEnvironment(this.getEnvironment());
  beanDefinitionReader.setResourceLoader(this);
  beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

  // Allow a subclass to provide custom initialization of the reader,
  // then proceed with actually loading the bean definitions.
  // 允许子类提供阅读器的自定义初始化,然后继续实际加载 bean 定义。
  // 对 beanDefinitionReader 进行属性设置
  initBeanDefinitionReader(beanDefinitionReader);
  // 加载 bean 定义
  loadBeanDefinitions(beanDefinitionReader);
 }

可以看到这里也是进行了一波准备工作,首先是给beanFactory创建一个XmlBeanDefinitionReader,后续xml解析都是由它来完成,接下来对这个XmlBeanDefinitionReader进行一些属性设置,接下来调用loadBeanDefinitions(beanDefinitionReader)加载BeanDefinition,跟进代码查看。

/**
  *
  * 使用给定的 XmlBeanDefinitionReader 加载 bean 定义。
  * <p>bean 工厂的生命周期由 {@link refreshBeanFactory} 方法处理;因此这个方法只是应该加载和或注册 bean 定义。
  *
  * 在初始化了DefaultListableBeanFactory和XmlBeanDefinitionReader后就可以进行配置文件的读取了
  *
  */
 protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
  // 返回一个引用构建此上下文的 XML bean 定义文件的 Resource 对象数组, 默认实现返回 {@code null}。
  // 子类可以覆盖它以提供预构建的资源对象而不是占位符字符串。
  Resource[] configResources = getConfigResources();
  if (configResources != null) {
   reader.loadBeanDefinitions(configResources);
  }
  // 返回引用构建此上下文的 XML bean 定义文件资源位置数组,还可以包括占位符模式,这将通过 ResourcePatternResolver 进行解析获取。
  // <p>默认实现返回 {@code null}。子类可以覆盖它以提供一组资源位置以从中加载 bean 定义。
  String[] configLocations = getConfigLocations();
  if (configLocations != null) {
   reader.loadBeanDefinitions(configLocations);
  }
 }

可以看到,由于没有预构建的资源,所以configResources会为nullconfigLocations则获取到了我们的beans.xml,那么接下来就会解析beans.xml获取BeanDefinition。跟进reader.loadBeanDefinitions(configLocations)方法,具体的实现是在AbstractBeanDefinitionReader#loadBeanDefinitions(String... locations)方法。

@Override
 public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
  Assert.notNull(locations, "Location array must not be null");
  int count = 0;
  for (String location : locations) {
   count += loadBeanDefinitions(location);
  }
  return count;
 }

继续套娃,进入loadBeanDefinitions(location)方法。

public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
  //获取资源加载器,主要功能是根据路径和类加载器获取Resource对象
  ResourceLoader resourceLoader = getResourceLoader();
  if (resourceLoader == null) {
   throw new BeanDefinitionStoreException(
     "Cannot load bean definitions from location [" + location + "]: no ResourceLoader available");
  }

  //ResourcePatternResolver用于加载多个文件或者加载Ant风格路径的资源文件
  if (resourceLoader instanceof ResourcePatternResolver) {
   // Resource pattern matching available.
   try {
    // 以Resource形式返回所有配置文件
    Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
    // 通过resources加载
    int count = loadBeanDefinitions(resources);
    if (actualResources != null) {
     Collections.addAll(actualResources, resources);
    }
    if (logger.isTraceEnabled()) {
     logger.trace("Loaded " + count + " bean definitions from location pattern [" + location + "]");
    }
    return count;
   }
   catch (IOException ex) {
    throw new BeanDefinitionStoreException(
      "Could not resolve bean definition resource pattern [" + location + "]", ex);
   }
  }
  else {
   /**
    * 加载单个资源,直接使用ResourceLoader加载
    */
   // Can only load single resources by absolute URL.
   Resource resource = resourceLoader.getResource(location);
   int count = loadBeanDefinitions(resource);
   if (actualResources != null) {
    actualResources.add(resource);
   }
   if (logger.isTraceEnabled()) {
    logger.trace("Loaded " + count + " bean definitions from location [" + location + "]");
   }
   return count;
  }
 }

这里会经过一系列的重载方法,最终会来到doLoadBeanDefinitions()方法里,这才是真正解析的地方,好家伙,山路十八弯。跟进doLoadBeanDefinitions()方法。

/**
  * Actually load bean definitions from the specified XML file.
  * @param inputSource the SAX InputSource to read from
  * @param resource the resource descriptor for the XML file
  * @return the number of bean definitions found
  * @throws BeanDefinitionStoreException in case of loading or parsing errors
  * @see #doLoadDocument
  * @see #registerBeanDefinitions
  */
 protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
   throws BeanDefinitionStoreException {

  try {
   /**
    * 创建Document对象,XML文档对象,即dom树
    * 使用这个Document对象可以获取XML文件中的节点并且创建节点
    * SAX XML
    * 解析dom树,即解析出一个个属性,将其属性保存到BeanDefinition当中
    * 并向容器注册BeanDefinition
    */
   Document doc = doLoadDocument(inputSource, resource);
   int count = registerBeanDefinitions(doc, resource);
   if (logger.isDebugEnabled()) {
    logger.debug("Loaded " + count + " bean definitions from " + resource);
   }
   return count;
  }
  catch (BeanDefinitionStoreException ex) {
   throw ex;
  }
  // 省略部分异常信息
 }

获取Document对象就不说了,可以单独出一篇文章Spring Ioc源码分析系列--获取xml文件Document对象,这里再跟进去就离主题太远了。

跟进registerBeanDefinitions(doc, resource)方法。

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
  //创建BeanDefinitionDocumentReader,这个是实际从XML的dom树中服务BeanDefinition
  BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
  //获取注册表BeanDefinitionMap在本次加载前的BeanDefinition数量
  int countBefore = getRegistry().getBeanDefinitionCount();
  //加载并注册
  documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
  //用本次加载后的数据减去以前有的数量,即为本次加载的BeanDefinition数量
  return getRegistry().getBeanDefinitionCount() - countBefore;
 }

重点在方法documentReader.registerBeanDefinitions(doc, createReaderContext(resource))里,跟进代码。

protected void doRegisterBeanDefinitions(Element root) {
  // Any nested <beans> elements will cause recursion in this method. In
  // order to propagate and preserve <beans> default-* attributes correctly,
  // keep track of the current (parent) delegate, which may be null. Create
  // the new (child) delegate with a reference to the parent for fallback purposes,
  // then ultimately reset this.delegate back to its original (parent) reference.
  // this behavior emulates a stack of delegates without actually necessitating one.
  //BeanDefinition的解析委托类
  BeanDefinitionParserDelegate parent = this.delegate;
  //判断这个根节点是否是默认的命名空间
  //底层就是判断这个根节点的namespace="http://www.springframework.org/schema/beans"
  this.delegate = createDelegate(getReaderContext(), root, parent);

  if (this.delegate.isDefaultNamespace(root)) {
   //获取这个profile的值,表示剖面,用于设置环境
   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;
    }
   }
  }

  //解析XML之前做的准备工作,其实什么也没做
  preProcessXml(root);
  //调用这个方法解析
  parseBeanDefinitions(root, this.delegate);
  //后续处理
  postProcessXml(root);

  this.delegate = parent;
 }

跟进解析BeanDefinition的方法parseBeanDefinitions(root, this.delegate)里面。

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
  //如果是默认命名空间
  if (delegate.isDefaultNamespace(root)) {
   //获取根节点下的所有子节点
   NodeList nl = root.getChildNodes();
   //遍历所有子节点
   for (int i = 0; i < nl.getLength(); i++) {
    //取出节点
    Node node = nl.item(i);
    if (node instanceof Element) {
     Element ele = (Element) node;
     //Bean定义的document对象使用了spring的默认命名空间,如http://www.springframework.org/schema/beans
     if (delegate.isDefaultNamespace(ele)) {
      //若是则按照spring原有的逻辑进行解析
      parseDefaultElement(ele, delegate);
     }
     else {
      delegate.parseCustomElement(ele);
     }
    }
   }
  }
  else {
   //使用扩展的自定义代理类去解析
   delegate.parseCustomElement(root);
  }
 }

这个例子没有自定义标签,进入到默认标签的解析。

//根据不同的标签进行解析
 private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
  //如果是import标签,进入导入解析
  if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
   importBeanDefinitionResource(ele);
  }
  //若果是别名元素,则进行别名解析
  else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
   processAliasRegistration(ele);
  }
  //bean元素解析
  else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
   processBeanDefinition(ele, delegate);
  }
  else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
   // recurse
   doRegisterBeanDefinitions(ele);
  }
 }

显然我们的例子只有一个Bean标签,所以会进入到processBeanDefinition()方法里。

/**
  * Process the given bean element, parsing the bean definition
  * and registering it with the registry.
  */
 protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
  //BeanDefinitionHolder是对BeanDefinition的封装,即bean定义的封装类
  //对document对象中的bean标签解析由BeanDefinitionParserDelegate实现
  BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
  if (bdHolder != null) {
   bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
   try {
    // Register the final decorated instance.
    //从springIOC容器注册解析得到的BeanDefinition,这是BeanDefinition向IOC容器注册的入口
    BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
   }
   catch (BeanDefinitionStoreException ex) {
    getReaderContext().error("Failed to register bean definition with name '" +
      bdHolder.getBeanName() + "'", ele, ex);
   }
   // Send registration event.
   getReaderContext().fireComponentRegistered(new BeanComponentDefinition
                                                       ·(bdHolder));
  }
 }

调用BeanDefinitionReaderUtils.registerBeanDefinition()方法真正将BeanDefinition注册进容器里。咋注册呢?其实就是加到BeanFactorybeanDefinitionMap属性里。beanDefinitionMap可以说就是BeanDefinition的容器了。

public static void registerBeanDefinition(
   BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
   throws BeanDefinitionStoreException {

  // Register bean definition under primary name.
  String beanName = definitionHolder.getBeanName();
  // 这里真正把BeanDefinition注册到了BeanFactory里
  registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

  // Register aliases for bean name, if any.
  // 注册别名
  String[] aliases = definitionHolder.getAliases();
  if (aliases != null) {
   for (String alias : aliases) {
    registry.registerAlias(beanName, alias);
   }
  }
 }

到这里已经完成了BeanDefinition的注册,是不是有点曲折?其实很简单,就是细节比较多

到这里obtainFreshBeanFactory()方法已经基本结束,已经完成了配置文件的解析并且注册BeanDefinition的过程了。剩下的操作就是把这个BeanFactory返回给上一步。

prepareBeanFactory()方法

接下来分析一下prepareBeanFactory()方法,这方法也简单,主要就是对BeanFactory进行一些属性的设置,跟着注释看一下就行。

/**
  * Configure the factory's standard context characteristics,
  * such as the context's ClassLoader and post-processors.
  * @param beanFactory the BeanFactory to configure
  */
 protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
  // Tell the internal bean factory to use the context's class loader etc.
  // 设置 beanFactory的classLoader 为当前 context的classLoader
  beanFactory.setBeanClassLoader(getClassLoader());
  /**
   * 设置 beanFactory的表达式语言处理器,Spring3 增加了表达式语言的支持,
   * 默认可以使用#{bean.xxx}的形式来调用相关属性值
   * @Qusetion 在注册了这个解析器之后,spring是在什么时候调用这个解析器的呢?
   * Spring在bean进行初始化的时候会有属性填充的步骤,而在这一步中
   * Spring会调用AbstractAutowireCapableBeanFactory类的applyPropertyValues函数来完成功能。
   * 就这个函数中,会通过构造 BeanDefinitionValueResolver类型实例valueResolver来进行属性值的解析。
   * 同时,也是在这个步骤中一般通过 AbstractBeanFactory 中的 evaluateBeanDefinitionString
   * 方法去完成SpEL解析
   */
  beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()));
  //为beanFactory增加一个PropertyEditor,这个主要是对bean的属性等设置管理的一个工具
  beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment()));

  // Configure the bean factory with context callbacks.
  //添加beanPostProcessor
  beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
  //设置几个忽略自动装配的接口
  // aware都是由invokeAware方法注入,忽略自动Autowire
  beanFactory.ignoreDependencyInterface(EnvironmentAware.class);
  beanFactory.ignoreDependencyInterface(EmbeddedValueResolverAware.class);
  beanFactory.ignoreDependencyInterface(ResourceLoaderAware.class);
  beanFactory.ignoreDependencyInterface(ApplicationEventPublisherAware.class);
  beanFactory.ignoreDependencyInterface(MessageSourceAware.class);
  beanFactory.ignoreDependencyInterface(ApplicationContextAware.class);

  // BeanFactory interface not registered as resolvable type in a plain factory.
  // MessageSource registered (and found for autowiring) as a bean.
  // 设置了几个自动装配的规则,后面三个都是把当前对象注册了进去,只有BeanFactory老老实实得注册了一个BeanFactory
  beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);
  beanFactory.registerResolvableDependency(ResourceLoader.class, this);
  beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);
  beanFactory.registerResolvableDependency(ApplicationContext.class, this);

  // Register early post-processor for detecting inner beans as ApplicationListeners.
  beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(this));

  // Detect a LoadTimeWeaver and prepare for weaving, if found.
  // 添加对AspectJ的支持
  if (beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
   beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
   // Set a temporary ClassLoader for type matching.
   beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
  }

  // Register default environment beans.添加系统环境默认的bean
  if (!beanFactory.containsLocalBean(ENVIRONMENT_BEAN_NAME)) {
   beanFactory.registerSingleton(ENVIRONMENT_BEAN_NAME, getEnvironment());
  }
  if (!beanFactory.containsLocalBean(SYSTEM_PROPERTIES_BEAN_NAME)) {
   beanFactory.registerSingleton(SYSTEM_PROPERTIES_BEAN_NAME, getEnvironment().getSystemProperties());
  }
  if (!beanFactory.containsLocalBean(SYSTEM_ENVIRONMENT_BEAN_NAME)) {
   beanFactory.registerSingleton(SYSTEM_ENVIRONMENT_BEAN_NAME, getEnvironment().getSystemEnvironment());
  }
 }

到这里refresh()的前面三个方法已经简单过完了,除了解析配置文件复杂点,其他的都是些属性配置居多。

Spring Ioc源码分析系列--Ioc源码入口分析

本系列文章代码基于Spring Framework 5.2.x

前言

上一篇文章Spring Ioc源码分析系列--Ioc的基础知识准备介绍了Ioc的基础概念以及Spring Ioc体系的部分基础知识。那么这一篇就会真正通过一个例子,启动Ioc容器,获取容器里的bean

首先说明,本文的例子是基于xml配置文件去完成的。

为什么是xml?因为xml是Spring的灵魂,可能我们初学Spring都会有畏难情绪,看到繁杂的xml就会打退堂鼓。但是实际上不然,xml的格式是相当清晰的,一个配置文件可以说没有一行配置是多余的。现在大部分的配置是用注解去完成的,相比xml而言是简洁许多,但是对于我们初学而言,xml其实是更好的方式xml相对于注解而言是相对繁杂,但是它的信息也更多更明确,注解只是添加了一个注解就完成配置,细节上是更为隐蔽的。再加上xml配置文件和注解配置的原理是相通的,核心思想是一样的,掌握核心就万变不离其宗。所以这系列文章的例子大部分都会采取xml的方式去配置,当然后续可能也会补充一下注解方式的例子和分析文章。

万事开头难,如果实在觉得看不懂但又想学的,可以硬着头皮看下去,等以后回过头来再看的时候,会有豁然开朗的感觉。

源码分析

启动容器示例

废话少说,下面开始搞个例子分析一下。所有源码都在我的仓库ioc-sourcecode-analysis-code-demo里找到。

首先弄个实体类User

/**
 * @author Codegitz
 * @date 2022/4/26 10:58
 **/
public class User {
 private String id;

 private String name;

 private String age;
}

创建业务类UserService以及业务实现类UserServiceImpl,这里的逻辑很简单,就是根据传入的nameage返回一个新的user对象。

/**
 * @author Codegitz
 * @date 2022/4/26 10:59
 **/
public interface UserService {
 User getUser(String name,String age);
}

public class UserServiceImpl implements UserService {
 @Override
 public User getUser(String name, String age) {
  User user = new User();
  user.setId("1");
  user.setName(name);
  user.setAge(age);
  return user;
 }
}

业务类的准备工作已经完成了,接下来就是要写个xml配置文件,告诉Spring Ioc我需要一个UserServicebean

<?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="userService" class="io.codegitz.service.impl.UserServiceImpl"/>
</beans>

这个xml配置文件比较简单,我们来解释一下每一行是什么意思。

<?xml version="1.0" encoding="UTF-8"?>为xml文件的规定头,没啥好说的。

xmlns="http://www.springframework.org/schema/beans"表明了xml的命名空间,xmlns全称为xml namespace,一个xml里面命名空间可以有多个。

xmlns:xsixsi:schemaLocation是指明了xml文件的验证模型和验证模型文件的位置。可以看到验证模型能通过http方式获取,但是为了防止网络抖动的影响,一般会在本地存放验证文件。spring-beans.xsd就可以在图示的目录下找到。接下来就到了<bean id="userService" class="io.codegitz.service.impl.UserServiceImpl"/>这一行,这是我们获取一个Bean的关键配置,这里告诉Ioc我需要一个iduserServiceclassio.codegitz.service.impl.UserServiceImpl的Bean。

到这里配置已经完成了,接下来创建一个引导类启动Ioc就能获取到这个bean了。

新建引导类Application,通过ClassPathXmlApplicationContext类引导启动Ioc容器,这是加载xml配置的常用引导类。

/**
 * @author Codegitz
 * @date 2022/4/26 10:57
 **/
public class Application {
 public static void main(String[] args) {
  ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
  UserService userService = (UserService) applicationContext.getBean("userService");
  User codegitz = userService.getUser("codegitz", "25");
  System.out.println(codegitz);
 }
}

到这里其实已经完成了所有样例的准备,可以启动容器了。可以看到,只是简单的ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");就可以启动一个容器,随后就能通过getBean()方法获取容器里的bean了,接下来我们看看new ClassPathXmlApplicationContext("beans.xml")发生了什么。

容器启动分析

ClassPathXmlApplicationContext构造方法

这里我们使用的是ClassPathXmlApplicationContext,那么就从这个类的构造方法开始分析。

摘取的代码片段我会保留原文注释,看官可以细细品味。

/**
  * Create a new ClassPathXmlApplicationContext, loading the definitions
  * from the given XML file and automatically refreshing the context.
  * @param configLocation resource location
  * @throws BeansException if context creation failed
  */
 public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
  this(new String[] {configLocation}, true, null);
 }

继续跟进构造函数,根据方法上的注释和代码可以看到。这里就做了两件事,根据给定的xml配置文件加载BeanDefinition,然后调用refresh()方法刷新容器。

/**
  * Create a new ClassPathXmlApplicationContext with the given parent,
  * loading the definitions from the given XML files.
  * @param configLocations array of resource locations
  * @param refresh whether to automatically refresh the context,
  * loading all bean definitions and creating all singletons.
  * Alternatively, call refresh manually after further configuring the context.
  * @param parent the parent context
  * @throws BeansException if context creation failed
  * @see #refresh()
  */
 public ClassPathXmlApplicationContext(
   String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
   throws BeansException {

  // 初始化父类
  super(parent);
  // 设置配置文件信息,这里会进行占位符的替换
  // 例如/${env}-beans.xml类型的路径会被占位符替换,如果env=sit,那么路径就会被替换为/sit-beans.xml
  setConfigLocations(configLocations);
  // 如果没有刷新,就会调用refresh()方法进行刷新,这个方法是整个Ioc的关键入口,由父类AbstractApplicationContext实现
  if (refresh) {
   refresh();
  }
 }

前面两个方法比较简单,可以跟着注释看一下。重点在refresh()方法。这个方法会完成Ioc所需要的所有配置加载,完成所有bean的注册以及Spring的一系列实现。

refresh()方法

到这里,才是真正摸到了Ioc的源码入口。看一下refresh()方法的代码。

public void refresh() throws BeansException, IllegalStateException {
  //给容器refresh加锁,避免容器在refresh阶段时,容器进行了初始化或者销毁操作
  synchronized (this.startupShutdownMonitor) {
   // Prepare this context for refreshing.
   //调用容器准备刷新的方法,获取容器当前时间,同时给容器设置同步标识、具体方法
   prepareRefresh();

   // Tell the subclass to refresh the internal bean factory.
   /**
    * 告诉子类启动refreshBeanFactory方法,refreshBeanFactory方法会载入bean的定义文件,该方法的实现会针对xml配置,最终创建内部容器
    * 该容器负责bean的创建与管理,会进行BeanDefinition的注册
    */
   ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

   // Prepare the bean factory for use in this context.
   //注册一些容器中需要使用的系统bean,例如classloader,BeanFactoryPostProcessor等
   prepareBeanFactory(beanFactory);

   try {
    // Allows post-processing of the bean factory in context subclasses
    // 允许容器的子类去注册PostProcessor,钩子方法.
    postProcessBeanFactory(beanFactory);

    // Invoke factory processors registered as beans in the context.
    //激活容器中注册为bean的BeanFactoryPostProcessor
    //对于注解容器,org.springframework.context.annotation.ConfigurationClassPostProcessor
    invokeBeanFactoryPostProcessors(beanFactory);

    // Register bean processors that intercept bean creation.
    registerBeanPostProcessors(beanFactory);

    // Initialize message source for this context.
    initMessageSource();

    // Initialize event multicaster for this context.
    initApplicationEventMulticaster();

    // Initialize other special beans in specific context subclasses.
    onRefresh();

    // Check for listener beans and register them.
    registerListeners();

    // Instantiate all remaining (non-lazy-init) singletons.
    // 创建对象
    finishBeanFactoryInitialization(beanFactory);

    // Last step: publish corresponding event.
    finishRefresh();
   }

   catch (BeansException ex) {
    if (logger.isWarnEnabled()) {
     logger.warn("Exception encountered during context initialization - " +
       "cancelling refresh attempt: " + ex);
    }

    // Destroy already created singletons to avoid dangling resources.
    destroyBeans();

    // Reset 'active' flag.
    cancelRefresh(ex);

    // Propagate exception to caller.
    throw ex;
   }

   finally {
    // Reset common introspection caches in Spring's core, since we
    // might not ever need metadata for singleton beans anymore...
    resetCommonCaches();
   }
  }
 }

咋一看代码量非常大,但是逻辑是比较清晰的,结合注释来看,再调试一遍,没啥大问题。

下面对方法进行逐个分析,鉴于一次性写完会比较长,本文先分析前三个方法,即prepareRefresh()obtainFreshBeanFactory()prepareBeanFactory(beanFactory)三个方法。

prepareRefresh()方法

跟进prepareRefresh()方法,这个方法主要是做了一些初始化属性设置和校验。

/**
  * Prepare this context for refreshing, setting its startup date and
  * active flag as well as performing any initialization of property sources.
  */
 protected void prepareRefresh() {
  // Switch to active.
  this.startupDate = System.currentTimeMillis();
  this.closed.set(false);
  this.active.set(true);
        
        // 省略部分日志...

  // Initialize any placeholder property sources in the context environment.
  // 初始化一些属性,交由子类实现
  initPropertySources();

  // Validate that all properties marked as required are resolvable:
  // see ConfigurablePropertyResolver#setRequiredProperties
  // 校验是否有必须的属性,如果有必须的属性但是环境没配置,则抛出异常
  getEnvironment().validateRequiredProperties();

  // Store pre-refresh ApplicationListeners...
  // 存储在refresh之前注册的ApplicationListener,避免这部分的ApplicationListener在后续的调用中被丢失
  // 这是一个2019年修复的bug
  if (this.earlyApplicationListeners == null) {
   this.earlyApplicationListeners = new LinkedHashSet<>(this.applicationListeners);
  }
  else {
   // Reset local application listeners to pre-refresh state.
   this.applicationListeners.clear();
   this.applicationListeners.addAll(this.earlyApplicationListeners);
  }

  // Allow for the collection of early ApplicationEvents,
  // to be published once the multicaster is available...
  this.earlyApplicationEvents = new LinkedHashSet<>();
 }

这个里面比较简单,没啥好看。稍微来看下getEnvironment().validateRequiredProperties()的代码。

/**
  * Return the {@code Environment} for this application context in configurable
  * form, allowing for further customization.
  * <p>If none specified, a default environment will be initialized via
  * {@link #createEnvironment()}.
  */
 @Override
 public ConfigurableEnvironment getEnvironment() {
  if (this.environment == null) {
   this.environment = createEnvironment();
  }
  return this.environment;
 }

 /**
  * Create and return a new {@link StandardEnvironment}.
  * <p>Subclasses may override this method in order to supply
  * a custom {@link ConfigurableEnvironment} implementation.
  */
 protected ConfigurableEnvironment createEnvironment() {
  return new StandardEnvironment();
 }

可以看到这里返回的是StandardEnvironment类型Environment,这里的逻辑就是有则返回,无则创建。接下来看validateRequiredProperties()方法。最终实现是在AbstractPropertyResolver#validateRequiredProperties()方法。

@Override
 public void validateRequiredProperties() {
  MissingRequiredPropertiesException ex = new MissingRequiredPropertiesException();
  // 遍历requiredProperties,逐个去当前环境获取是否存在
  for (String key : this.requiredProperties) {
   if (this.getProperty(key) == null) {
    ex.addMissingRequiredProperty(key);
   }
  }
  // 如果存在缺失的必须属性,则抛出异常
  if (!ex.getMissingRequiredProperties().isEmpty()) {
   throw ex;
  }
 }

prepareRefresh()方法比较简单,到此已经基本看完,接下来看下一个obtainFreshBeanFactory()方法。

obtainFreshBeanFactory()方法

obtainFreshBeanFactory()方法从名字上就知道是获取并且同时刷新一个beanfactory

在没有分析代码之前,先来猜测一下它的实现。我们知道它最终会返回一个可以使用的beanfactory,说明此时在beanfactory里已经初始化完成了我们定义的BeanDefinition,那么这里应该会有一些基础信息的配置,然后解析xml文件,获取xml配置信息,然后初始化相应的BeanDefinition,准备就绪后返回beanfactory。当然这只是猜测,具体实现如何,马上分析。

那么接下来就跟进这个方法看一下代码实现。

/**
  * Tell the subclass to refresh the internal bean factory.
  * @return the fresh BeanFactory instance
  * @see #refreshBeanFactory()
  * @see #getBeanFactory()
  */
 protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
  // 调用子类的实现,刷新beanFactory
  refreshBeanFactory();
  // 返回刷新完成(即启动完成)的beanFactory
  return getBeanFactory();
 }

跟进refreshBeanFactory()方法,关键地方都已经加上注释,也比较简单易懂,跟着看一下就行。

/**
  * This implementation performs an actual refresh of this context's underlying
  * bean factory, shutting down the previous bean factory (if any) and
  * initializing a fresh bean factory for the next phase of the context's lifecycle.
  * 此实现执行此上下文的底层 bean 工厂的实际刷新,
  * 关闭先前的 bean 工厂(如果有)并为上下文生命周期的下一阶段初始化一个新的 bean 工厂。
  */
 @Override
 protected final void refreshBeanFactory() throws BeansException {
  // 如果有前一个,先关闭
  if (hasBeanFactory()) {
   // 先销毁所有已经注册的bean
   destroyBeans();
   // 关闭工厂,将this.beanFactory设为null
   closeBeanFactory();
  }
  try {
   //创建DefaultListableBeanFactory
   DefaultListableBeanFactory beanFactory = createBeanFactory();
   //为了序列化指定id,如果需要的话,让这个beanFactory从id反序列化到BeanFactory对象
   beanFactory.setSerializationId(getId());
   //定制beanFactory,设置相关属性,包括是否允许覆盖同名称的不同定义的对象已经循环依赖
   //以及设置@Autowire和@Qualifier注册解析器QualifierAnnotationAutowire-CandidateResolver
   customizeBeanFactory(beanFactory);
   //初始化DocumentReader,并进行xml文件读取和解析
   loadBeanDefinitions(beanFactory);
   synchronized (this.beanFactoryMonitor) {
    this.beanFactory = beanFactory;
   }
  }
  catch (IOException ex) {
   throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
  }
 }

前面的准备工作是比较简单的,不再细说。重点在loadBeanDefinitions(beanFactory)方法,这里会加载xml获取我们配置的BeanDefinition

记住这个位置AbstractRefreshableApplicationContext#refreshBeanFactory(),这是经过前面繁杂的摸索后真正进入BeanDefinition加载的分水岭。跟进代码查看。

/**
  * Loads the bean definitions via an XmlBeanDefinitionReader.
  * 通过 XmlBeanDefinitionReader 加载 bean 定义。
  * @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader
  * @see #initBeanDefinitionReader
  * @see #loadBeanDefinitions
  */
 @Override
 protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
  // Create a new XmlBeanDefinitionReader for the given BeanFactory.
  //为指定 beanFactory创建XmlBeanDefinitionReader
  XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

  // Configure the bean definition reader with this context's
  // resource loading environment.
  //对 beanDefinitionReader 进行环境变量的设置
  beanDefinitionReader.setEnvironment(this.getEnvironment());
  beanDefinitionReader.setResourceLoader(this);
  beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

  // Allow a subclass to provide custom initialization of the reader,
  // then proceed with actually loading the bean definitions.
  // 允许子类提供阅读器的自定义初始化,然后继续实际加载 bean 定义。
  // 对 beanDefinitionReader 进行属性设置
  initBeanDefinitionReader(beanDefinitionReader);
  // 加载 bean 定义
  loadBeanDefinitions(beanDefinitionReader);
 }

可以看到这里也是进行了一波准备工作,首先是给beanFactory创建一个XmlBeanDefinitionReader,后续xml解析都是由它来完成,接下来对这个XmlBeanDefinitionReader进行一些属性设置,接下来调用loadBeanDefinitions(beanDefinitionReader)加载BeanDefinition,跟进代码查看。

/**
  *
  * 使用给定的 XmlBeanDefinitionReader 加载 bean 定义。
  * <p>bean 工厂的生命周期由 {@link refreshBeanFactory} 方法处理;因此这个方法只是应该加载和或注册 bean 定义。
  *
  * 在初始化了DefaultListableBeanFactory和XmlBeanDefinitionReader后就可以进行配置文件的读取了
  *
  */
 protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
  // 返回一个引用构建此上下文的 XML bean 定义文件的 Resource 对象数组, 默认实现返回 {@code null}。
  // 子类可以覆盖它以提供预构建的资源对象而不是占位符字符串。
  Resource[] configResources = getConfigResources();
  if (configResources != null) {
   reader.loadBeanDefinitions(configResources);
  }
  // 返回引用构建此上下文的 XML bean 定义文件资源位置数组,还可以包括占位符模式,这将通过 ResourcePatternResolver 进行解析获取。
  // <p>默认实现返回 {@code null}。子类可以覆盖它以提供一组资源位置以从中加载 bean 定义。
  String[] configLocations = getConfigLocations();
  if (configLocations != null) {
   reader.loadBeanDefinitions(configLocations);
  }
 }

可以看到,由于没有预构建的资源,所以configResources会为nullconfigLocations则获取到了我们的beans.xml,那么接下来就会解析beans.xml获取BeanDefinition。跟进reader.loadBeanDefinitions(configLocations)方法,具体的实现是在AbstractBeanDefinitionReader#loadBeanDefinitions(String... locations)方法。

@Override
 public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
  Assert.notNull(locations, "Location array must not be null");
  int count = 0;
  for (String location : locations) {
   count += loadBeanDefinitions(location);
  }
  return count;
 }

继续套娃,进入loadBeanDefinitions(location)方法。

public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
  //获取资源加载器,主要功能是根据路径和类加载器获取Resource对象
  ResourceLoader resourceLoader = getResourceLoader();
  if (resourceLoader == null) {
   throw new BeanDefinitionStoreException(
     "Cannot load bean definitions from location [" + location + "]: no ResourceLoader available");
  }

  //ResourcePatternResolver用于加载多个文件或者加载Ant风格路径的资源文件
  if (resourceLoader instanceof ResourcePatternResolver) {
   // Resource pattern matching available.
   try {
    // 以Resource形式返回所有配置文件
    Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
    // 通过resources加载
    int count = loadBeanDefinitions(resources);
    if (actualResources != null) {
     Collections.addAll(actualResources, resources);
    }
    if (logger.isTraceEnabled()) {
     logger.trace("Loaded " + count + " bean definitions from location pattern [" + location + "]");
    }
    return count;
   }
   catch (IOException ex) {
    throw new BeanDefinitionStoreException(
      "Could not resolve bean definition resource pattern [" + location + "]", ex);
   }
  }
  else {
   /**
    * 加载单个资源,直接使用ResourceLoader加载
    */
   // Can only load single resources by absolute URL.
   Resource resource = resourceLoader.getResource(location);
   int count = loadBeanDefinitions(resource);
   if (actualResources != null) {
    actualResources.add(resource);
   }
   if (logger.isTraceEnabled()) {
    logger.trace("Loaded " + count + " bean definitions from location [" + location + "]");
   }
   return count;
  }
 }

这里会经过一系列的重载方法,最终会来到doLoadBeanDefinitions()方法里,这才是真正解析的地方,好家伙,山路十八弯。跟进doLoadBeanDefinitions()方法。

/**
  * Actually load bean definitions from the specified XML file.
  * @param inputSource the SAX InputSource to read from
  * @param resource the resource descriptor for the XML file
  * @return the number of bean definitions found
  * @throws BeanDefinitionStoreException in case of loading or parsing errors
  * @see #doLoadDocument
  * @see #registerBeanDefinitions
  */
 protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
   throws BeanDefinitionStoreException {

  try {
   /**
    * 创建Document对象,XML文档对象,即dom树
    * 使用这个Document对象可以获取XML文件中的节点并且创建节点
    * SAX XML
    * 解析dom树,即解析出一个个属性,将其属性保存到BeanDefinition当中
    * 并向容器注册BeanDefinition
    */
   Document doc = doLoadDocument(inputSource, resource);
   int count = registerBeanDefinitions(doc, resource);
   if (logger.isDebugEnabled()) {
    logger.debug("Loaded " + count + " bean definitions from " + resource);
   }
   return count;
  }
  catch (BeanDefinitionStoreException ex) {
   throw ex;
  }
  // 省略部分异常信息
 }

获取Document对象就不说了,可以单独出一篇文章Spring Ioc源码分析系列--获取xml文件Document对象,这里再跟进去就离主题太远了。

跟进registerBeanDefinitions(doc, resource)方法。

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
  //创建BeanDefinitionDocumentReader,这个是实际从XML的dom树中服务BeanDefinition
  BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
  //获取注册表BeanDefinitionMap在本次加载前的BeanDefinition数量
  int countBefore = getRegistry().getBeanDefinitionCount();
  //加载并注册
  documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
  //用本次加载后的数据减去以前有的数量,即为本次加载的BeanDefinition数量
  return getRegistry().getBeanDefinitionCount() - countBefore;
 }

重点在方法documentReader.registerBeanDefinitions(doc, createReaderContext(resource))里,跟进代码。

protected void doRegisterBeanDefinitions(Element root) {
  // Any nested <beans> elements will cause recursion in this method. In
  // order to propagate and preserve <beans> default-* attributes correctly,
  // keep track of the current (parent) delegate, which may be null. Create
  // the new (child) delegate with a reference to the parent for fallback purposes,
  // then ultimately reset this.delegate back to its original (parent) reference.
  // this behavior emulates a stack of delegates without actually necessitating one.
  //BeanDefinition的解析委托类
  BeanDefinitionParserDelegate parent = this.delegate;
  //判断这个根节点是否是默认的命名空间
  //底层就是判断这个根节点的namespace="http://www.springframework.org/schema/beans"
  this.delegate = createDelegate(getReaderContext(), root, parent);

  if (this.delegate.isDefaultNamespace(root)) {
   //获取这个profile的值,表示剖面,用于设置环境
   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;
    }
   }
  }

  //解析XML之前做的准备工作,其实什么也没做
  preProcessXml(root);
  //调用这个方法解析
  parseBeanDefinitions(root, this.delegate);
  //后续处理
  postProcessXml(root);

  this.delegate = parent;
 }

跟进解析BeanDefinition的方法parseBeanDefinitions(root, this.delegate)里面。

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
  //如果是默认命名空间
  if (delegate.isDefaultNamespace(root)) {
   //获取根节点下的所有子节点
   NodeList nl = root.getChildNodes();
   //遍历所有子节点
   for (int i = 0; i < nl.getLength(); i++) {
    //取出节点
    Node node = nl.item(i);
    if (node instanceof Element) {
     Element ele = (Element) node;
     //Bean定义的document对象使用了spring的默认命名空间,如http://www.springframework.org/schema/beans
     if (delegate.isDefaultNamespace(ele)) {
      //若是则按照spring原有的逻辑进行解析
      parseDefaultElement(ele, delegate);
     }
     else {
      delegate.parseCustomElement(ele);
     }
    }
   }
  }
  else {
   //使用扩展的自定义代理类去解析
   delegate.parseCustomElement(root);
  }
 }

这个例子没有自定义标签,进入到默认标签的解析。

//根据不同的标签进行解析
 private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
  //如果是import标签,进入导入解析
  if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
   importBeanDefinitionResource(ele);
  }
  //若果是别名元素,则进行别名解析
  else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
   processAliasRegistration(ele);
  }
  //bean元素解析
  else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
   processBeanDefinition(ele, delegate);
  }
  else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
   // recurse
   doRegisterBeanDefinitions(ele);
  }
 }

显然我们的例子只有一个Bean标签,所以会进入到processBeanDefinition()方法里。

/**
  * Process the given bean element, parsing the bean definition
  * and registering it with the registry.
  */
 protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
  //BeanDefinitionHolder是对BeanDefinition的封装,即bean定义的封装类
  //对document对象中的bean标签解析由BeanDefinitionParserDelegate实现
  BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
  if (bdHolder != null) {
   bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
   try {
    // Register the final decorated instance.
    //从springIOC容器注册解析得到的BeanDefinition,这是BeanDefinition向IOC容器注册的入口
    BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
   }
   catch (BeanDefinitionStoreException ex) {
    getReaderContext().error("Failed to register bean definition with name '" +
      bdHolder.getBeanName() + "'", ele, ex);
   }
   // Send registration event.
   getReaderContext().fireComponentRegistered(new BeanComponentDefinition
                                                       ·(bdHolder));
  }
 }

调用BeanDefinitionReaderUtils.registerBeanDefinition()方法真正将BeanDefinition注册进容器里。咋注册呢?其实就是加到BeanFactorybeanDefinitionMap属性里。beanDefinitionMap可以说就是BeanDefinition的容器了。

public static void registerBeanDefinition(
   BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
   throws BeanDefinitionStoreException {

  // Register bean definition under primary name.
  String beanName = definitionHolder.getBeanName();
  // 这里真正把BeanDefinition注册到了BeanFactory里
  registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

  // Register aliases for bean name, if any.
  // 注册别名
  String[] aliases = definitionHolder.getAliases();
  if (aliases != null) {
   for (String alias : aliases) {
    registry.registerAlias(beanName, alias);
   }
  }
 }

到这里已经完成了BeanDefinition的注册,是不是有点曲折?其实很简单,就是细节比较多

到这里obtainFreshBeanFactory()方法已经基本结束,已经完成了配置文件的解析并且注册BeanDefinition的过程了。剩下的操作就是把这个BeanFactory返回给上一步。

prepareBeanFactory()方法

接下来分析一下prepareBeanFactory()方法,这方法也简单,主要就是对BeanFactory进行一些属性的设置,跟着注释看一下就行。

/**
  * Configure the factory's standard context characteristics,
  * such as the context's ClassLoader and post-processors.
  * @param beanFactory the BeanFactory to configure
  */
 protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
  // Tell the internal bean factory to use the context's class loader etc.
  // 设置 beanFactory的classLoader 为当前 context的classLoader
  beanFactory.setBeanClassLoader(getClassLoader());
  /**
   * 设置 beanFactory的表达式语言处理器,Spring3 增加了表达式语言的支持,
   * 默认可以使用#{bean.xxx}的形式来调用相关属性值
   * @Qusetion 在注册了这个解析器之后,spring是在什么时候调用这个解析器的呢?
   * Spring在bean进行初始化的时候会有属性填充的步骤,而在这一步中
   * Spring会调用AbstractAutowireCapableBeanFactory类的applyPropertyValues函数来完成功能。
   * 就这个函数中,会通过构造 BeanDefinitionValueResolver类型实例valueResolver来进行属性值的解析。
   * 同时,也是在这个步骤中一般通过 AbstractBeanFactory 中的 evaluateBeanDefinitionString
   * 方法去完成SpEL解析
   */
  beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()));
  //为beanFactory增加一个PropertyEditor,这个主要是对bean的属性等设置管理的一个工具
  beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment()));

  // Configure the bean factory with context callbacks.
  //添加beanPostProcessor
  beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
  //设置几个忽略自动装配的接口
  // aware都是由invokeAware方法注入,忽略自动Autowire
  beanFactory.ignoreDependencyInterface(EnvironmentAware.class);
  beanFactory.ignoreDependencyInterface(EmbeddedValueResolverAware.class);
  beanFactory.ignoreDependencyInterface(ResourceLoaderAware.class);
  beanFactory.ignoreDependencyInterface(ApplicationEventPublisherAware.class);
  beanFactory.ignoreDependencyInterface(MessageSourceAware.class);
  beanFactory.ignoreDependencyInterface(ApplicationContextAware.class);

  // BeanFactory interface not registered as resolvable type in a plain factory.
  // MessageSource registered (and found for autowiring) as a bean.
  // 设置了几个自动装配的规则,后面三个都是把当前对象注册了进去,只有BeanFactory老老实实得注册了一个BeanFactory
  beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);
  beanFactory.registerResolvableDependency(ResourceLoader.class, this);
  beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);
  beanFactory.registerResolvableDependency(ApplicationContext.class, this);

  // Register early post-processor for detecting inner beans as ApplicationListeners.
  beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(this));

  // Detect a LoadTimeWeaver and prepare for weaving, if found.
  // 添加对AspectJ的支持
  if (beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
   beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
   // Set a temporary ClassLoader for type matching.
   beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
  }

  // Register default environment beans.添加系统环境默认的bean
  if (!beanFactory.containsLocalBean(ENVIRONMENT_BEAN_NAME)) {
   beanFactory.registerSingleton(ENVIRONMENT_BEAN_NAME, getEnvironment());
  }
  if (!beanFactory.containsLocalBean(SYSTEM_PROPERTIES_BEAN_NAME)) {
   beanFactory.registerSingleton(SYSTEM_PROPERTIES_BEAN_NAME, getEnvironment().getSystemProperties());
  }
  if (!beanFactory.containsLocalBean(SYSTEM_ENVIRONMENT_BEAN_NAME)) {
   beanFactory.registerSingleton(SYSTEM_ENVIRONMENT_BEAN_NAME, getEnvironment().getSystemEnvironment());
  }
 }

到这里refresh()的前面三个方法已经简单过完了,除了解析配置文件复杂点,其他的都是些属性配置居多。

上面说的都是Spring的基础部分,看源码首先要抓住源码的主干部分,切勿抠细节,要是抠细节就会被绕在里面出不来,而且Spring源码庞大,很难去抠细节。

所以先抓住主干部分,抓住Spring的两个核心点就是IOC和AOP,首先IOC的源码主干,可以看这篇:Spring IOC 绝对劲爆的源码分析,AOP源码可以看这篇:又一次非常劲爆的Spring AOP 源码解析

这个两篇文章要是掌握的百分之七八十,你也就大概知道Spring源码主干部分了,后面就是抠细节,一些技术细节的实现。

比如说Bean是怎么实例化的,实例化过程是怎么样的,这个也是面试的八股文之一:面试阿里被问到Spring的Bean实例化的过程,轻松拿捏

还可以深入了解我们的Bean是怎么进行注解注入的,怎么通过一个注解就是实现自动注入:Spring Ioc源码分析系列--@Autowired源码解析

还有有关八股文BeanPostProcessor与BeanFactoryPostProcessor的区别,可以通过源码部分来进行深入学习:Spring源码系列-BeanPostProcessor与BeanFactoryPostProcessor

到目前为止,Spring的源码部分就更新完了,以上就是Spring的源码的文章,大家可以参考学习,自己下载源码,然后对照文章进行深入学习。

纸上得来终觉浅,绝知此事要躬行。只有自己实践了,才会记忆深刻。

296b766c42adf778e13eff115fa33005.gif

1.Mysql完结汇总篇(18W字送给大家),完结撒花

2.如何啃下JVM这座大山,完结撒花(完结篇)

3.最全的八股文线程池总结(臭不要脸)

4.手把手教新人调优

5.上班摸鱼学习法

6.阻塞队列yyds

7.线程与

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值