前言
在陈旧系统中都是使用 SSM 架构进行搭建,Spring 源码是后续深入理解学习 SpringBoot、SpringCloud 组件所必须依赖的,只是不再采用 XML 方式来进行配置使用,在这里,主要是回顾下最初在使用 Spring 时的核心类「ClassPathXmlApplicationContext」实例过程以及配置文件加载的详细过程,因为后续的核心方法 refresh 内容讲解也得从此处开始讲起.
文章内容有点长,可以分目录节点进行阅读,比较想深入理解的方法可以点击文章目录入口.
Spring 启动流程解析
新建 ApplicationContext 上下文对象整体流程如下
- PathMatchingResourcePatternResolver:创建一个资源模式解析器(其实就是用来解析 xml 配置文件),内部创建了 Ant 方式表达式语言匹配器
// 创建 Ant【表达式语言】 方式的路径匹配器 private PathMatcher pathMatcher = new AntPathMatcher();
- setConfigLocations:设置应用程序上下文的配置路径
- customizePropertySources:在创建标准化环境对象(StandardEnvironment)时 createEnvironment() 会执行父类【AbstractEnvironment】构造方法加载这两项值【systemProperties:系统属性值,systemEnvironment:系统环境值】
protected void customizePropertySources(MutablePropertySources propertySources) { propertySources.addLast(new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME,getSystemProperties())); propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,getSystemEnvironment())); }
- resolveRequiredPlaceholders:解析占位符信息
创建 PropertyPlaceholderHelper 处理类
doResolvePlaceholders 执行解析操作
在其内部递归调用 PropertyPlaceholderHelper#parseStringValue 方法从系统属性和系统环境中解析占用符拿到具体值,递归的原因是因为可能出现这种情况【spring-${username${nickname}}.xml】
Refresh 内部方法全解析
prepareRefresh
容器刷新前的准备工作
protected void prepareRefresh() { // 设置容器启动的时间 this.startupDate = System.currentTimeMillis(); // 容器的关闭标志位 this.closed.set(false); // 容器的激活标志位 this.active.set(true); // 记录日志 if (logger.isDebugEnabled()) { if (logger.isTraceEnabled()) { logger.trace("Refreshing " + this); } else { logger.debug("Refreshing " + getDisplayName()); } } // 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... // 判断刷新前的应用程序监听器集合是否为空,如果为空,则将监听器添加到此集合中 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<>(); }
设置容器的启动时间、设置活跃状态为 true、设置关闭状态为 false、获取 Environment 对象,并加载当前的系统属性值到 Environment 对象中、验证必需的属性是否在环境中存在,如果没有则抛出异常、准备监听器和监听事件的集合对象,默认为空的集合.
该方法流程里面有一个扩展点「初始化属性源交由子类去扩展,可以在其下自定义一些属性,要求其拥有其必须依赖的属性才能正常的运行」
public class MyClassPathXmlApplicationContext extends ClassPathXmlApplicationContext { public MyClassPathXmlApplicationContext(String... configLocations){ super(configLocations); } @Override protected void initPropertySources() { System.out.println("扩展initPropertySource"); // 若系统属性中不存在 username,会抛出异常. getEnvironment().setRequiredProperties("username"); } }
obtainFreshBeanFactory
创建容器对象【DefaultListableBeanFactory】加载 xml 配置文件的属性值到当前工厂中,最重要的就是 BeanDefinition
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() { // 初始化 BeanFactory,并进行 XML 文件读取,并将得到的 BeanFactory 记录在当前实体的属性中 refreshBeanFactory(); // 返回当前实体 beanFactory 属性 return getBeanFactory(); }
AbstractApplicationContext 类以及实现子类
AbstractApplicationContext 子类 AbstractRefreshableApplicationContext 继承重写以下方法,XML 启动容器时是该入口来实现
因为 AbstractRefreshableApplicationContext 是 ClassPathXmlApplicationContext 的父类
@Override protected final void refreshBeanFactory() throws BeansException { // 如果存在beanFactory,则销毁beanFactory if (hasBeanFactory()) { destroyBeans(); closeBeanFactory(); } try { // 创建DefaultListableBeanFactory对象 DefaultListableBeanFactory beanFactory = createBeanFactory(); // 为了序列化指定id,可以从id反序列化到beanFactory对象 beanFactory.setSerializationId(getId()); // 定制beanFactory,设置相关属性,包括是否允许覆盖同名称的不同定义的对象以及循环依赖 customizeBeanFactory(beanFactory); // 初始化documentReader,并进行XML文件读取及解析,默认命名空间的解析,自定义标签的解析 loadBeanDefinitions(beanFactory); this.beanFactory = beanFactory; } catch (IOException ex) { throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex); } }
AbstractApplicationContext 子类 GenericApplicationContext 继承重写以下方法,SpringBoot 是该入口来实现:
protected final void refreshBeanFactory() throws IllegalStateException { if (!this.refreshed.compareAndSet(false, true)) { throw new IllegalStateException( "GenericApplicationContext does not support multiple refresh attempts: just call 'refresh' once"); } this.beanFactory.setSerializationId(getId()); }
涉及到 Web 容器启动流程中在 createApplicationContext 方法中会根据类型创建一个上下文对象:AnnotationConfigServletWebServerApplicationContext
protected ConfigurableApplicationContext createApplicationContext() { Class<?> contextClass = this.applicationContextClass; if (contextClass == null) { try { switch(this.webApplicationType) { case SERVLET: contextClass = Class.forName("org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext"); break; case REACTIVE: contextClass = Class.forName("org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext"); break; default: contextClass = Class.forName("org.springframework.context.annotation.AnnotationConfigApplicationContext"); } } catch (ClassNotFoundException var3) { throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3); } } return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass); }
再查看它的类关系图就清楚了
AbstractRefreshableApplicationContext#obtainFreshBeanFactory 方法
接着继续分析 AbstractRefreshableApplicationContext#obtainFreshBeanFactory
方法的处理流程:
- 创建实例化 DefaultLisableBeanFactory 时,会构建其父类的构造方法,其逻辑会忽略要依赖的接口
public AbstractAutowireCapableBeanFactory() { super(); // 忽略要依赖的接口 ignoreDependencyInterface(BeanNameAware.class); ignoreDependencyInterface(BeanFactoryAware.class); ignoreDependencyInterface(BeanClassLoaderAware.class); }
- 指定上下文工厂对象序列化 ID
- customizeBeanFactory:定制 beanFactory,设置相关属性,包括是否允许覆盖同名称的不同定义的对象以及循环依赖
// 此方法可以交由子类去自由扩展实现 @Override protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) { super.setAllowBeanDefinitionOverriding(false); super.setAllowCircularReferences(false); super.customizeBeanFactory(beanFactory); }
- loadBeanDefinitions:初始化 documentReader,并进行 XML 文件读取及解析,默认命名空间的解析,自定义标签的解析【创建 BeanDefinition 核心步骤】
XmlWebApplicationContext#loadBeanDefinitions 方法
创建配置文件读取器:XmlBeanDefinitionReader
beanDefinitionReader#setEntityResolver(new ResourceEntityResolver(this))
:设置实体解析器,有 DTD(document type Definition)、XSD(XML Schema Definition) 这两种解析方式,目前大部分使用的是后者
this.dtdResolver = new BeansDtdResolver(); // 当完成这行代码的调用之后,大家神奇的发现一件事情,schemaResolver 对象的 schemaMappings 属性被完成了赋值操作,但是你遍历完成所有代码后依然没有看到显式调用 // 其实此时的原理是非常简单的,我们在进行 debug 的时候,因为在程序运行期间需要显示当前类的所有信息,所以 idea 会帮助我们调用 toString 方法,只不过此过程我们识别不到而已,idea 中可以设置关闭 Debug 模式下 toString 方法调用 // toString(){return "EntityResolver using schema mappings " + getSchemaMappings();} // 会去加载 META-INF/spring.schemas 文件下所有键值对 this.schemaResolver = new PluggableSchemaResolver(classLoader);
initBeanDefinitionReader(beanDefinitionReader):初始化 beanDefinitionReader 对象,此处设置配置文件是否要进行验证
loadBeanDefinitions(beanDefinitionReader):开始完成对 BeanDefinition 的加载,详细流程图如下:
解析 spring 配置文件整体流程: 这个解析过程是由 documentLoader 完成的,从 String[]—>string—>Resource[]—>resource,最终开始将 resource 转换为 InputStream 流对象后读取成一个 document 文档,新建 BeanDefinitionDocumentReader 对象后对 XML 中 BeanDefinition 进行解析,根据文档的节点信息封装成一个个的 BeanDefinition 对象,将其包装为 BeanDefinitionHolder 对象,并把 BeanDefinition、BeanName、alias 相关信息存入【beanDefinitionMap、beanDefinitionNames】集合中.
BeanDefinitionHolder 介绍:BeanDefinition 对象持有者,封装了 BeanDefinition,bean 名字和别名,用它来完成向 IOC 容器注册,得到这个 BeanDefinitionHolder 意味着 beanDefinition 是通过 BeanDefinitionParserDelegate 对 XML 元素的信息按照 spring bean 规则进行解析得到的
通过子节点来划分整个加载 XML 配置文件的流程:
- createReaderContext(resource):创建 XML 上下文读取解析器,同时会加载命名空间下的 handlers,存入在【META-INF/spring.handlers】文件中,以 key(命名空间路径)—>value(具体的 handler 类)方式存储.
- delegate.isDefaultNamespace(root):判定命名空间是否为默认的命名空间【http://www.springframework.org/schema/beans】如果是进行默认
parseDefaultElement(ele, delegate)
处理,无须去加载文件中的命名空间 handler 类;非默认命名空间,则需要去获取对应的 handler 进行处理 - 默认节点标签:import、alias、bean、beans
import 标签:会获取
resource
属性值,判定其是绝对还是相对的 URI【绝对 URI 直接将 resource 对象直接重新解析的一个流程,相对 URI 则匹配上前缀路径后再走这样的一个流程】alias:给指定的 BeanName 注册 1~N 个别名,会对所有的
alias->name
关系进行校验,如果不满足就会抛出异常结束「beanName 与 alias 相同不记录 alias、alias 已存在集合中,判定是否允许覆盖:不允许抛出异常、检查别名关系中是否出现嵌套异常,如 A->B 可以但 B->A 就不可以了、将 alias->name 键值对存入集合中」bean 标签:获取 id、name 属性,id 作为 BeanName,name 作为 alias,如果没有指定 BeanName 名称时,那么 Spring 会根据命名规则为当前 Bean 生成 BeanName 值
checkNameUniqueness(beanName, aliases, ele):会去校验 BeanName 的唯一性,如果存在了会抛出异常
parseBeanDefinitionElement(ele, beanName, containingBean):对 bean 元素进行详细解析
1、根据 class 属性获取全路径类名,反射获取其类实例
2、解析 bean 标签内的属性:depends-on、init-method、destroy-method
3、解析 bean 标签下子标签及其属性:lookup-method【执行新的逻辑需要新加 bean】、replaced-method【将之前方法执行的逻辑替换为新的】、property、构造函数参数
4、property 标签有以下约束:property 标签上不能同时有 value 和 ref 属性、 在有 value 或 ref 属性的同时不能再有子节点、property 标签上 value 和 ref 两个属性都没有也会报错
以上流程处理完调用 BeanDefinitionReaderUtils.registerBeanDefinition(definition, registry)
注册 Bean 信息放入到 【beanDefinitionMap、beanDefinitionNames】 集合中