写在前面
作为一个刚步入职场的小白,对Spring(SpringBoot)的了解只停留在会用,并未对其内部的原理有过学习。在公司导师的指导下,开始进一步学习Spring的源码,毕竟Spring源码是Spring全家桶的基础,学习了源码对Spring其他框架也能更好上手。
由于本人的基础并不太好,因此文章中有错误的地方欢迎指出。
一个小Demo
Spring容器的特点是:控制反转和依赖注入。
背过八股的同学肯定对这两个概念都不陌生,简单来说,在java里面我们定义的类在Spring框架下称为bean,控制反转的意思是,不需要我们主动去管理类(也就是bean,下文都称为bean)的生命周期,Spring来负责bean的实例化、属性填充、初始化和销毁等操作。而依赖注入是指bean之间可能相互依赖,是实现控制反转的一种具体技术。通过依赖注入,对象的依赖关系由外部容器在运行时动态注入,而不是由对象自己创建或查找。
我们从一个简单的Demo来看看Spring应用程序是怎么搭建的,先看看目录结构:
先引入核心依赖:
xml
<properties>
<spring-framework.version>5.2.8.RELEASE</spring-framework.version>
</properties>
<dependencies>
<!--spring 核心组件所需依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring-framework.version}</version>
</dependency>
</dependencies>
FirstSpringSource定义如下:
java
public class FirstSpringSource {
public FirstSpringSource(){
System.out.println("FirstSpringSource init.");
}
public void methodCall(){
System.out.println("methodCall() called.");
}
public String str;
public void setStr(String str) {
this.str = str;
}
@Override
public String toString() {
return "FirstSpringSource{" +
"str='" + str + '\'' +
'}';
}
}
spring-config.xml定义如下:
xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
https://www.springframework.org/schema/context/spring-context.xsd">
<bean class="com.source.FirstSpringSource" name="firstSpringSource">
<property name="str" value="AyanokoujiMonki"/>
</bean>
</beans>
启动类SpringSource定义如下:
java
public class SpringSource {
@Test
public void firstSpringSource(){
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("spring-config.xml");
FirstSpringSource firstSpringSource1 = ac.getBean("firstSpringSource", FirstSpringSource.class);
FirstSpringSource firstSpringSource2 = ac.getBean("firstSpringSource", FirstSpringSource.class);
firstSpringSource1.methodCall();
System.out.println(firstSpringSource1);
System.out.println(firstSpringSource2);
System.out.println(firstSpringSource1 == firstSpringSource2);
}
我们看看程序运行的结果:
java
FirstSpringSource init.
methodCall() called.
FirstSpringSource{str='AyanokoujiMonki'}
FirstSpringSource{str='AyanokoujiMonki'}
true
从结果来看,我们可以根据类型或者bean的名称(beanName,这是一个重要概念)从容器中获取对应的bean,然后调用bean中的方法。
另外,我们在xml中给bean配置的属性,也成功注入到bean中。我们重复从bean中获取相同名称的bean,其实这些bean是同一个bean,这也证明了Spring容器默认是单例模式。
程序入口
从Demo中也可以看到,程序的入口是ClassPathXmlApplicationContext
的构造器,ClassPathXmlApplicationContext
我们一般称之为上下文容器,Spring中有很多ApplicationContext
类型的类,我们称这些类为上下文容器,我们可以看看ClassPathXmlApplicationContext
的类图:
ApplicationContext
接口实现了BeanFactory
接口,而BeanFactory
就是存储bean的bean工厂。其实早期Spring并没有上下文容器的概念,也就是没有ApplicationContext
接口,只有BeanFacotry
接口,在后来的版本才引入ApplicationContext
。
ApplicationContext
不仅继承了BeanFactory
,还继承了其他接口,因此相较于BeanFactory
,ApplicationContext
具有更多的功能。
我们接着进去看看ClassPathXmlApplicationContext
的构造方法:
super(parent)
super(parent)方法的目的主要有两个:
tex
1.设置资源模式解析器(resourcePatternResolver)和资源加载器(resourceLoader),为后面的资源(配置文件)解析做准备。
2.设置父上下文容器,将父级容器运行时环境合并到当前(子)容器运行时环境,默认没有父上下文容器,因此不需要关心。
我们先来看看调用链:
通过层层调用最后调用到AbstractApplicationContext
的构造方法:
我们继续进到getResourcePatternResolver方法看看:
所以这里可以看作是有一个循环引用,PathMatchingResourcePatternResolver
和AbstractApplicationContext
互相作为属性设置到对方中。
setConfigLocations方法
setConfigLocations主要是为了解析资源路径中的占位符,我们传入的xml路径可以通过占位符来进行动态设置。我们也可以手动设置系统变量,然后通过占位符来引用系统变量,这里是一个简单的例子:
我们先看看setConfigLocations方法:
继续进到resolvePath方法看看:
这里的setConfigLocations 方法和resolvePath 方法实际调用的是AbstractRefreshableConfigApplicationContext
里面的方法。
getEnvironment方法
getEnvironment 方法其实属于AbstractApplicationContext
:
我们再来看看StandardEnviroment
:
可以看到,StandardEnviroment
并没有构造方法,因此创建StandardEnviroment
对象时会调用父类的构造方法,也就是AbstractEnvironment
的构造方法,我们来看看部分AbstractEnvironment
的代码:
java
public abstract class AbstractEnvironment implements ConfigurableEnvironment {
public static final String IGNORE_GETENV_PROPERTY_NAME = "spring.getenv.ignore";
public static final String ACTIVE_PROFILES_PROPERTY_NAME = "spring.profiles.active";
public static final String DEFAULT_PROFILES_PROPERTY_NAME = "spring.profiles.default";
protected static final String RESERVED_DEFAULT_PROFILE_NAME = "default";
protected final Log logger = LogFactory.getLog(getClass());
private final Set<String> activeProfiles = new LinkedHashSet<>();
private final Set<String> defaultProfiles = new LinkedHashSet<>(getReservedDefaultProfiles());
private final MutablePropertySources propertySources = new MutablePropertySources();
private final ConfigurablePropertyResolver propertyResolver =
new PropertySourcesPropertyResolver(this.propertySources);
public AbstractEnvironment() {
customizePropertySources(this.propertySources);
}
}
可以看到,在创建StandardEnviroment
对象的时候,就已经完成系统变量的加载了。
resolveRequiredPlaceholders方法
这里实际上调用的是AbstractEnvironment
的resolveRequiredPlaceholders方法:
可以看到,AbstractEnvironment
也是交给PropertySourcesPropertyResolver
的resolveRequiredPlaceholders方法:
createPlaceholderHelper方法的实现如下:
java
//调用PropertyPlaceholderHelper的构造器
//传递默认的占位符解析格式 前缀"${" 后缀"}" 占位符变量和默认值的分隔符":"
//ignoreUnresolvablePlaceholders=true,即在无法解析占位符的时候忽略
private PropertyPlaceholderHelper createPlaceholderHelper(boolean ignoreUnresolvablePlaceholders) {
return new PropertyPlaceholderHelper(this.placeholderPrefix, this.placeholderSuffix,
this.valueSeparator, ignoreUnresolvablePlaceholders);
}
我们继续往下看,在创建了helper之后,会继续调用PropertySourcesPropertyResolver
的doResolvePlaceholders方法:
因为用到了方法引用这里的代码可能有点难理解,我们一步一步来看这段代码,我们先来看看PropertyPlaceholderHelper
的replacePlaceholders方法:
replacePlaceholders方法的第二个参数要求的是PlaceholderResolver实例,我们继续看看PlaceholderResolver:
我们再来看看getPropertyAsRawString方法(位于PropertySourcesPropertyResolver里面):
因此,我们可以将上述代码改造为匿名内部类的方式:
java
//改造前
helper.replacePlaceholders(text, this::getPropertyAsRawString);
//改造后
return helper.replacePlaceholders(text, new
PropertyPlaceholderHelper.PlaceholderResolver() {
@Override
public String resolvePlaceholder(String key) {
return getPropertyAsRawString(key);
}
});
到了这一步后,我们来看看replacePlaceholders (PropertyPlaceholderHelper
)里面的parseStringValue,这就是最后的关键方法,我们简单来看看这个方法:
中间还有很多方法没有讲,感兴趣的小伙伴可以自己去看看源码,这里只梳理一下大致的解析占位符逻辑。
简单来说,Spring处理占位符考虑到了两个事情:一是占位符可能存在嵌套的情况;二是占位符变量对应的值也可能存在占位符,因此需要进一步解析。
小结
setConfigLocations干的事情其实很简单,完成占位符的解析获取实际xml配置文件的地址:
tex
1.创建一个Environment类型的对象,创建的时候会读取系统变量信息,并将这些信息保存到内部的PropertySourcesPropertyResolver类型属性中
2.Environment委托内部PropertySourcesPropertyResolver类型的属性,去解析配置文件路径。
2.PropertySourcesPropertyResolver内部会创建PropertyPlaceholderHelper来负责解析配置文件路径。
3.PropertyPlaceholderHelper会递归处理占位符,解析得到真正的配置文件路径。
因为现在大多都使用注解开发,这部分的代码可能实际用的比较少,大家了解即可,需要注意的是这个过程中,我们会创建好Environment类型的对象,并保存在ApplicationContext中,Environment在后面出场的概率还是比较高的。
refresh方法
讲完前两个方法,我们来到构造器中的第三个方法:refresh 。refresh 方法其实在AbstractApplicationContext
中,是Spring启动的核心逻辑,当你想要研究SpringBoot和SpringCloud的源码时,你就找它对应的refresh方法即可。
我们先来看看refresh方法长啥样:
关于Spring如何创建bean的流程,省流版是:获取每个bean的class对象,并根据class对象创建对应的bean定义(即beanDefinition),然后根据beanDefinition中的beanClass通过反射实例化,并完成属性填充。
我们先来复习一下java类加载过程,当我们的程序启动时java虚拟机会将java文件转换为字节码文件,然后根据字节码文件为每个类创建一个class对象保存在堆内存中,一个类有且仅有一个class对象。当我们手动创建某个类的对象时(比如new),也是通过该类的class对象得到。
因此,Spring能为我们管理对象的一个重要原因就是java程序启动完成后,jvm的堆内存中就会有class每个bean的class对象,Spring通过反射机制就可以依据class对象创建对应的实例bean。
obtainFreshBeanFactory方法加载beanDefinition
我们先来看看obtainFreshBeanFactory方法:
java
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
//关闭以前的 beanFactory(如果存在),并初始化一个新的 beanFactory
refreshBeanFactory();
// 返回新的 beanFactory
return getBeanFactory();
}
obtainFreshBeanFactory 方法的逻辑其实很好理解,让我们进到refreshBeanFactory 方法看看是如何实现的,refreshBeanFactory 方法其实位于AbstractRefreshableApplicationContext
:
destoryBeans方法
这里简单提一下destoryBeans方法,该方法主要完成销毁回调,我们来看看一个简单的例子:
测试类如下:
java
@Test
public void destroy(){
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("spring-config.xml");
//通过手动调用refresh方法触发销毁回调
ac.refresh();
}
控制台输出结果:
可以看到,Spring会帮我们自动回调销毁方法。
在Spring中,定义销毁方法的方式有三个,这三者的优先级逐级降低,并且同一方法只会回调一次:
tex
1.@PreDestroy注解标注的方法回调;
2.DisposableBean接口的destroy方法回调;
3.XML的destroy-method属性指定或者Spring自动推断的方法回调;
其中,DisposableBean
接口是Spring框架提供的接口,实现这个接口需要重写destroy 方法,Spring会负责回调重写后的destroy方法。
在完成销毁回调后,Spring会销毁这些bean并关闭原有的beanFactory。因此,简单来说destoryBeans方法干了两件事,销毁回调和注销beanFactory。
我们来看看destoryBeans的具体实现:
getBeanFactory方法
这里的getBeanFactory 是在AbstractRefreshableApplicationContext
中:
destroySingletons方法
根据getBeanFactory 方法我们知道,这里调用的destroySingletons 方法来自DefaultListableBeanFactory
:
java
@Override
public void destroySingletons() {
//调用父类的方法销毁单例bean,并且进行销毁回调(如果设置了)
super.destroySingletons();
//清空DefaultListableBeanFactory类自己的manualSingletonNames属性集合
//即清空所有手动注册的bean,所谓手动注册,就是调用registerSingleton(String beanName, Object singletonObject)方法注册的bean
updateManualSingletonNames(Set::clear, set -> !set.isEmpty());
//删除有关按类型映射的任何缓存,即清空allBeanNamesByType和singletonBeanNamesByType集合
clearByTypeCache();
}
super.destroySingletons()
DefaultListableBeanFactory
的destroySingletons 方法干的第一件事就是完成销毁单例bean,并进行销毁回调,但是这部分操作是交给了父类DefaultSingletonBeanRegistry
的同名方法来完成的,我们可以通过类图查看这两个类之间的关系:
我们进到DefaultSingletonBeanRegistry
类里再看看,可以发现DefaultSingletonBeanRegistry
设置了一系列重要属性,我们简单来看一些后面会用到的属性:
java
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
//单例bean缓存,beanName到bean实例的map
//当Spring完全创建好bean后会将bean放到这里,我们可以通过beanName取出对应的bean实例,因此beanName不允许重复
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
//单例factory缓存,beanName到ObjectFactory的map
//其实这就是我们常说的Spring三级缓存,后面讲Spring创建bean时会提到
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
//单例早期实例bean缓存,beanName到ObjectFactory的map
//这个和singletonObjects区别就是,singletonObjects完成了属性填充,earlySingletonObjects并没有
//singletonObjects、singletonFactories和earlySingletonObjects构成了我们常说的Spring三级缓存
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
//已注册的单例beanName集合,按注册顺序存入单例beanName
private final Set<String> registeredSingletons = new LinkedHashSet<>(256);
//beanName到支持销毁回调的实例映射的map
//实际上value是一个DisposableBeanAdapter对象
private final Map<String, Object> disposableBeans = new LinkedHashMap<>();
//beanName到该依赖该bran的beanName的集合映射(如果A依赖B,则B是key,A是value)
private final Map<String, Set<String>> dependentBeanMap = new ConcurrentHashMap<>(64);
... ...
}
等到后面bean实例化时,会反复用到这些属性。
我们接着来看看DefaultSingletonBeanRegistry
的destroySingletons方法:
可以看到核心逻辑在destroySingleton 中,这里的destroySingleton 是调用的DefaultListableBeanFactory
中的方法(子类有就优先调用子类的,子类没有再去父类中找):
java
/**
* DefaultListableBeanFactory的方法
* <p>
* 销毁指定beanName对应的Bean
*/
@Override
public void destroySingleton(String beanName) {
//调用父类的destroySingleton方法
super.destroySingleton(beanName);
//当前的beanName从手动注册bean名称集合manualSingletonNames缓存中移除
removeManualSingletonName(beanName);
//删除有关按类型映射的任何缓存,即清空allBeanNamesByType和singletonBeanNamesByType集合
clearByTypeCache();
}
DefaultListableBeanFactory
的destroySingleton 方法进来的第一件事就是调用父类DefaultSingletonBeanRegistry
的destroySingleton 方法(绕来绕去还是回到了DefaultSingletonBeanRegistry
):
java
/**
* DefaultSingletonBeanRegistry的方法
* <p>
* 销毁指定beanName对应的Bean
*/
public void destroySingleton(String beanName) {
// 从单例bean缓存中删除给定名称的已注册单例bean.
removeSingleton(beanName);
// 从disposableBeans缓存中移除对应beanName的bean,获取移除的对象disposableBean.
// 注意需要进行销毁回调的bean,Spring都会包装成DisposableBean类型
DisposableBean disposableBean;
synchronized (this.disposableBeans) {
disposableBean = (DisposableBean) this.disposableBeans.remove(beanName);
}
//销毁bean,并且进行销毁回调
destroyBean(beanName, disposableBean);
}
removeSingleton方法实现比较简单,本质就是从map中删除指定的键值对:
destroyBean方法实现如下:
我们再来看看destroy 方法,该方法位于DiposalBeanAdapter
中,销毁逻辑分为三部分,分别对应三个我们之前提到过的需要销毁回调的场景:
tex
1.@PreDestroy注解标注的方法回调;
2.DisposableBean接口的destroy方法回调;
3.XML的destroy-method属性指定或者Spring自动推断的方法回调;
updateManualSingletonNames方法
DefaultListableBeanFactory
中的manualSingletonNames缓存,用于按注册顺序手动注册的单例的beanName列表,后面我们还会遇到这个缓存。
所谓手动注册,就是调用registerSingleton(String beanName, Object singletonObject)方法注册的bean在注册bean定义完成之后,Spring会手动注册一些bean,比如前面说的环境变量:"environment"、"systemProperties"。
clearByTypeCache方法
java
/**
* DefaultSingletonBeanRegistry的方法
* <p>
* 清空注册的单例bean相关缓存容器
*/
protected void clearSingletonCache() {
//synchronized同步
synchronized (this.singletonObjects) {
//清空四个单例缓存容器
this.singletonObjects.clear();
this.singletonFactories.clear();
this.earlySingletonObjects.clear();
this.registeredSingletons.clear();
//标志位改为false,表示销毁bean的操作结束
this.singletonsCurrentlyInDestruction = false;
}
}
小结
虽然销毁回调的逻辑稍微有点绕,但是这部分逻辑其实是Spring帮我们完成的,我们无需操心。我们还是要知道销毁回调大致的逻辑和回调时机,当我们自定义的销毁方法出现问题时,能快速定位问题的位置。
closeBeanFactory方法
当Spring完成了销毁回调后,需要关闭当前beanFactory,关闭的方法也很简单就是将ApplicationContext
的beanFactory属性置为空:
java
protected final void closeBeanFactory() {
DefaultListableBeanFactory beanFactory = this.beanFactory;
if (beanFactory != null) {
beanFactory.setSerializationId(null);
this.beanFactory = null;
}
}
createBeanFactory
在关闭原有的beanFactory后,Spring会通过createBeanFactory继续创建一个新的beanFactory:
java
//会创建一个DefaultListableBeanFactory类型的beanFactory
protected DefaultListableBeanFactory createBeanFactory() {
//根据父工厂创建一个beanFactory
//非web环境下,工厂的父工厂默认为null,web环境下,Spring的beanFactory就是Spring MVC的beanFactory的父工厂
return new DefaultListableBeanFactory(getInternalParentBeanFactory());
}
创建beanFactory的逻辑比较简单,我们继续深入到DefaultListableBeanFactory
的构造方法看看:
java
//DefaultListableBeanFactory会继续调用父类的构造方法,这里的parentBeanFactory为null
public DefaultListableBeanFactory(@Nullable BeanFactory parentBeanFactory) {
super(parentBeanFactory);
}
DefaultListableBeanFactory
的父类是AbstractAutowireCapableBeanFactory
,我们看看它的构造方法:
java
// 先进到这个构造方法,会继续调用AbstractAutowireCapableBeanFactory的无参构造
public AbstractAutowireCapableBeanFactory(@Nullable BeanFactory parentBeanFactory) {
this();
setParentBeanFactory(parentBeanFactory);
}
// 调用AbstractAutowireCapableBeanFactory父类的空参构造,这里的父类是AbstractBeanFactory,它的空参构造是个空实现
// 忽略给定依赖接口的setter自动装配,主要是Aware接口
public AbstractAutowireCapableBeanFactory() {
super();
ignoreDependencyInterface(BeanNameAware.class);
ignoreDependencyInterface(BeanFactoryAware.class);
ignoreDependencyInterface(BeanClassLoaderAware.class);
}
customizeBeanFactory方法
customizeBeanFactory方法主要干了两件事:
tex
1.设置allowBeanDefinitionOverriding属性:在registerBeanDefinition注册bean定义的时候判断是否允许同名的BeanDefinition 覆盖,默认允许true,Springboot则对其进一步封装,默认不允许false。
2.allowCircularReferences:是否允许循环依赖引用,默认允许true。
customizeBeanFactory方法实现如下:
java
protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {
// 是否允许 BeanDefinition覆盖,默认为null
if (this.allowBeanDefinitionOverriding != null) {
//设置AbstractAutowireCapableBeanFactory的属性
beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
//是否允许循环引用,默认为null
if (this.allowCircularReferences != null) {
//设置AbstractAutowireCapableBeanFactory的属性
beanFactory.setAllowCircularReferences(this.allowCircularReferences);
}
}
//AbstractAutowireCapableBeanFactory的属性
/**
* 是否自动尝试解决 bean 之间的循环依赖,默认为true
*/
private boolean allowCircularReferences = true;
/**
* 是否允许重新注册具有相同名称的不同定义,即覆盖,默认为true
*/
private boolean allowBeanDefinitionOverriding = true;
loadBeanDefinitions方法
loadBeanDefinitions 是最核心的方法位于AbstractRefreshableApplicationContext
,用来加载beanDefinition:
可以看到方法内部会创建一个XmlBeanDefinitionReader
类型的配置文件解析器,并设置相关属性。注意这里会将当前的ApplicationContext
作为resourceLodader 属性设置到XmlBeanDefinitionReader
中。
最后会将创建的XmlBeanDefinitionReader
对象作为参数传入AbstractXmlApplicationContext
的loadBeanDefinitions方法:
java
/**
* AbstractXmlApplicationContext类的方法
* <p>
* 使用给定的XmlBeanDefinitionReader加载bean的定义
*/
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
//获取配置文件的Resource数组,该属性在ClassPathXmlApplicationContext中,默认为null
//在前面的setConfigLocations方法中解析的是configLocations配置文件路径字符串数组,注意区分
Resource[] configResources = getConfigResources();
if (configResources != null) {
//调用reader自己的loadBeanDefinitions方法,加载bean 的定义
reader.loadBeanDefinitions(configResources);
}
//获取配置文件路径数组,在此前最外层的setConfigLocations方法中已经初始化了
//非web容器第一次进来默认就是走的这个逻辑
String[] configLocations = getConfigLocations();
if (configLocations != null) {
//调用reader自己的loadBeanDefinitions方法,加载bean 的定义
//内部会从资源路径字符串处加载资源成为Resource,从还是会调用上面的loadBeanDefinitions方法
reader.loadBeanDefinitions(configLocations);
}
}
继续进到XmlBeanDefinitionReader
的loadBeanDefinitions 方法,其实是XmlBeanDefinitionReader
父类AbstractBeanDefinitionReader
中的方法,该方法会遍历得到的配置文件路径,统计并加载beanDefinition:
java
/**
* AbstractBeanDefinitionReader的方法
* <p>
* 从指定的资源位置加载 bean 定义
*/
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;
}
这里会继续调用AbstractBeanDefinitionReader
中重载的loadBeanDefinitions方法:
java
/**
* AbstractBeanDefinitionReader的方法
*/
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
return loadBeanDefinitions(location, null);
}
还是继续调用AbstractBeanDefinitionReader
中重载的loadBeanDefinitions方法:
可以看到这里还是调用了AbstractBeanDefinitionReader
中重载的loadBeanDefinitions 方法,这里的loadBeanDefinitions 方法通过几次中转最终会调用到XmlBeanDefinitionReader
的方法:
java
/**
* XmlBeanDefinitionReader的方法
* 对单个resource处理
*/
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (logger.isTraceEnabled()) {
logger.trace("Loading XML bean definitions from " + encodedResource);
}
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
//获取输入流
try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
// 核心方法
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"IOException parsing XML document from " + encodedResource.getResource(), ex);
}
finally {
//移除已被加载的resource
currentResources.remove(encodedResource);
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
}
我们直接进到doLoadBeanDefinitions方法:
java
/**
* XmlBeanDefinitionReader的方法
*/
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
// 将输入流解析为文档树
Document doc = doLoadDocument(inputSource, resource);
// 根据文档树注册beanDefinition,并统计数量
int count = registerBeanDefinitions(doc, resource);
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + count + " bean definitions from " + resource);
}
return count;
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (SAXParseException ex) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(),
"Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
}
catch (SAXException ex) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(),
"XML document from " + resource + " is invalid", ex);
}
catch (ParserConfigurationException ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"Parser configuration exception parsing XML from " + resource, ex);
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"IOException parsing XML document from " + resource, ex);
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"Unexpected exception parsing XML document from " + resource, ex);
}
}
doLoadBeanDefinitions 方法会将得到的输入流转换为文档树,解析文档树来注册beanDefinition 。继续进入到registerBeanDefinitions方法:
java
/**
* XmlBeanDefinitionReader的方法
使用BeanDefinitionDocumentReader分析 DOM 文档对象中包含的 beanDefinition,并且对beanDefinition进行注册
*/
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
// 创建一个文档树分析器,用来解析DOM文档中的beanDefiniton
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
// 统计解析前当前容器中的beanDefinition中的数量
// getRegistry()其实得到的是我们先前创建好的beanFactory(当前ApplicationContext中的beanFactory)
int countBefore = getRegistry().getBeanDefinitionCount();
// 解析并注册文档书中的beanDefinition
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
// 返回当前文档树解析到的beanDefinition的数量
return getRegistry().getBeanDefinitionCount() - countBefore;
}
在执行registerBeanDefinitions 方法前,会createReaderContext 方法创建一个XmlReaderContext
上下文:
java
/**
* 创建一个XmlReaderContex、
* <p>
*
* @param resource the XML bean definition resource
* @param problemReporter the problem reporter in use
* @param eventListener the event listener in use
* @param sourceExtractor the source extractor in use
* @param reader the XML bean definition reader in use
* @param namespaceHandlerResolver the XML namespace resolver
*/
public XmlReaderContext createReaderContext(Resource resource) {
return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
this.sourceExtractor, this, getNamespaceHandlerResolver());
}
/**
* 如果之前未设置,则创建一个默认的命名空间处理器解析器
*/
public NamespaceHandlerResolver getNamespaceHandlerResolver() {
if (this.namespaceHandlerResolver == null) {
this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver();
}
return this.namespaceHandlerResolver;
}
/**
* DefaultNamespaceHandlerResolver的构造器
* <p>
* 使用提供的映射文件位置创建一个新的DefaultNamespaceHandlerResolver
*
* @param classLoader 用于加载映射资源的ClassLoader,如果为null,则使用线程上下文类加载器
* @param handlerMappingsLocation 映射文件位置
*/
public DefaultNamespaceHandlerResolver(@Nullable ClassLoader classLoader, String handlerMappingsLocation) {
Assert.notNull(handlerMappingsLocation, "Handler mappings location must not be null");
this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
this.handlerMappingsLocation = handlerMappingsLocation;
}
XmlBeanDefinitionReader
中有一个比较重要的属性namespaceHandlerResolver,它被用来加载handlerMappings下的自定义命名空间以及该命名空间的NamespaceHandler处理器,存放到内部的handlerMappings映射集合中,而handlerMappings文件的默认地址和名字为"META-INF/spring.handlers"。
这里的namespaceHandlerResolver 属性一般是DefaultNamespaceHandlerResolver
类型的,我们可以看看这个类:
在后面parseCustomElement方法的解析外部引入以及自定义的命名空间下的标签时,就是使用到对应命名空间的NamespaceHandler来解析的。
在创建完XmlReaderContext
上下文后,继续到registerBeanDefinitions方法:
java
/**
* DefaultBeanDefinitionDocumentReader的方法
*/
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
// 设置当前文档分析树BeanDefinitionDocumentReader的readerContext属性
this.readerContext = readerContext;
// 调用doRegisterBeanDefinitions方法
doRegisterBeanDefinitions(doc.getDocumentElement());
}
doRegisterBeanDefinitions方法实现如下:
继续进到parseBeanDefinitions方法
小结
这篇文章主要讲了Spring的初始化启动入口,简单介绍了一下super(parent)、setConfiglocations和refresh,简单介绍了一下前两个方法。其中setConfiglocations方法主要干了两件事,解析XML文件路径,并创建Environment对象。
refresh方法是Spring启动的核心方法,该部分内容比较多,本文先简单介绍了obtainFreshBeanFactory方法,该方法主要干的事情是关闭原有的beanFactory(一般是没有),创建新的beanFactory并加载beanDefinition。
关于关闭原有的beanFactory虽然我们在开发中用到的不多,但是有关销毁回调的知识还是有必要了解一下,大致知道销毁回调的原理和时机,这样有助于我们自定义的销毁函数出问题时能快速定位到问题。
关于创建新的beanFactory并加载beanDefinition是我们研究的重点,本文只涉及到加载beanDefinition之前的预处理,有关加载beanDefinition的核心方法会在下一篇文章中讲到。