Spring源码学习(一):Spring初始化入口

写在前面

​   作为一个刚步入职场的小白,对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,还继承了其他接口,因此相较于BeanFactoryApplicationContext具有更多的功能。

​   我们接着进去看看ClassPathXmlApplicationContext的构造方法:

super(parent)

​   super(parent)方法的目的主要有两个:

tex 复制代码
1.设置资源模式解析器(resourcePatternResolver)和资源加载器(resourceLoader),为后面的资源(配置文件)解析做准备。
2.设置父上下文容器,将父级容器运行时环境合并到当前(子)容器运行时环境,默认没有父上下文容器,因此不需要关心。

​   我们先来看看调用链:

​   通过层层调用最后调用到AbstractApplicationContext的构造方法:

​   我们继续进到getResourcePatternResolver方法看看:

​   所以这里可以看作是有一个循环引用,PathMatchingResourcePatternResolverAbstractApplicationContext互相作为属性设置到对方中。

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方法

​   这里实际上调用的是AbstractEnvironmentresolveRequiredPlaceholders方法:

​   可以看到,AbstractEnvironment也是交给PropertySourcesPropertyResolverresolveRequiredPlaceholders方法:

​   createPlaceholderHelper方法的实现如下:

java 复制代码
 //调用PropertyPlaceholderHelper的构造器
 //传递默认的占位符解析格式  前缀"${"   后缀"}"   占位符变量和默认值的分隔符":"
 //ignoreUnresolvablePlaceholders=true,即在无法解析占位符的时候忽略

private PropertyPlaceholderHelper createPlaceholderHelper(boolean ignoreUnresolvablePlaceholders) {
		return new PropertyPlaceholderHelper(this.placeholderPrefix, this.placeholderSuffix,
				this.valueSeparator, ignoreUnresolvablePlaceholders);
}

​   我们继续往下看,在创建了helper之后,会继续调用PropertySourcesPropertyResolverdoResolvePlaceholders方法:

​   因为用到了方法引用这里的代码可能有点难理解,我们一步一步来看这段代码,我们先来看看PropertyPlaceholderHelperreplacePlaceholders方法:

​   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);
    }
});

​   到了这一步后,我们来看看replacePlaceholdersPropertyPlaceholderHelper)里面的parseStringValue,这就是最后的关键方法,我们简单来看看这个方法:

​   中间还有很多方法没有讲,感兴趣的小伙伴可以自己去看看源码,这里只梳理一下大致的解析占位符逻辑。

​   简单来说,Spring处理占位符考虑到了两个事情:一是占位符可能存在嵌套的情况;二是占位符变量对应的值也可能存在占位符,因此需要进一步解析。

小结

​   setConfigLocations干的事情其实很简单,完成占位符的解析获取实际xml配置文件的地址:

tex 复制代码
1.创建一个Environment类型的对象,创建的时候会读取系统变量信息,并将这些信息保存到内部的PropertySourcesPropertyResolver类型属性中
2.Environment委托内部PropertySourcesPropertyResolver类型的属性,去解析配置文件路径。
2.PropertySourcesPropertyResolver内部会创建PropertyPlaceholderHelper来负责解析配置文件路径。
3.PropertyPlaceholderHelper会递归处理占位符,解析得到真正的配置文件路径。

​   因为现在大多都使用注解开发,这部分的代码可能实际用的比较少,大家了解即可,需要注意的是这个过程中,我们会创建好Environment类型的对象,并保存在ApplicationContext中,Environment在后面出场的概率还是比较高的。

refresh方法

​   讲完前两个方法,我们来到构造器中的第三个方法:refreshrefresh 方法其实在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()

​   DefaultListableBeanFactorydestroySingletons 方法干的第一件事就是完成销毁单例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实例化时,会反复用到这些属性。

​   我们接着来看看DefaultSingletonBeanRegistrydestroySingletons方法:

​   可以看到核心逻辑在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();
}

​   DefaultListableBeanFactorydestroySingleton 方法进来的第一件事就是调用父类DefaultSingletonBeanRegistrydestroySingleton 方法(绕来绕去还是回到了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,关闭的方法也很简单就是将ApplicationContextbeanFactory属性置为空:

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对象作为参数传入AbstractXmlApplicationContextloadBeanDefinitions方法:

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);
    }
}

​   继续进到XmlBeanDefinitionReaderloadBeanDefinitions 方法,其实是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的核心方法会在下一篇文章中讲到。

参考博客:

Spring IoC容器初始化源码(2)---refresh方法入口、prepareRefresh准备刷新、obtainFreshBeanFactory加载XML资源、解析<beans/>标签【两万字】_spring种的refresh方法入口-CSDN博客

相关推荐
用户37215742613522 分钟前
Java 实现 Excel 与 TXT 文本高效互转
java
浮游本尊1 小时前
Java学习第22天 - 云原生与容器化
java
渣哥3 小时前
原来 Java 里线程安全集合有这么多种
java
间彧3 小时前
Spring Boot集成Spring Security完整指南
java
间彧4 小时前
Spring Secutiy基本原理及工作流程
java
Java水解5 小时前
JAVA经典面试题附答案(持续更新版)
java·后端·面试
洛小豆7 小时前
在Java中,Integer.parseInt和Integer.valueOf有什么区别
java·后端·面试
前端小张同学7 小时前
服务器上如何搭建jenkins 服务CI/CD😎😎
java·后端
ytadpole7 小时前
Spring Cloud Gateway:一次不规范 URL 引发的路由转发404问题排查
java·后端
华仔啊7 小时前
基于 RuoYi-Vue 轻松实现单用户登录功能,亲测有效
java·vue.js·后端