自学内容网 自学内容网

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

写在前面

​   作为一个刚步入职场的小白,对Spring(SpringBoot)的了解只停留在会用,并未对其内部的原理有过学习。在公司导师的指导下,开始进一步学习Spring的源码,毕竟Spring源码是Spring全家桶的基础,学习了源码对Spring其他框架也能更好上手。

​   由于本人的基础并不太好,因此文章中有错误的地方欢迎指出。

一个小Demo

​   Spring容器的特点是:控制反转和依赖注入。

​   背过八股的同学肯定对这两个概念都不陌生,简单来说,在java里面我们定义的类在Spring框架下称为bean,控制反转的意思是,不需要我们主动去管理类(也就是bean,下文都称为bean)的生命周期,Spring来负责bean的实例化、属性填充、初始化和销毁等操作。而依赖注入是指bean之间可能相互依赖,是实现控制反转的一种具体技术。通过依赖注入,对象的依赖关系由外部容器在运行时动态注入,而不是由对象自己创建或查找。

​   我们从一个简单的Demo来看看Spring应用程序是怎么搭建的,先看看目录结构:

在这里插入图片描述

​   先引入核心依赖:

<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定义如下:

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 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定义如下:

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

​   我们看看程序运行的结果:

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)方法的目的主要有两个:

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

​   我们先来看看调用链:

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

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

setConfigLocations方法

​   setConfigLocations主要是为了解析资源路径中的占位符,我们传入的xml路径可以通过占位符来进行动态设置。我们也可以手动设置系统变量,然后通过占位符来引用系统变量,这里是一个简单的例子:

在这里插入图片描述

​   我们先看看setConfigLocations方法:

在这里插入图片描述

​   继续进到resolvePath方法看看:

在这里插入图片描述

​ 这里的setConfigLocations方法和resolvePath方法实际调用的是AbstractRefreshableConfigApplicationContext里面的方法。

getEnvironment方法

​   getEnvironment方法其实属于AbstractApplicationContext:

在这里插入图片描述

​   我们再来看看StandardEnviroment

在这里插入图片描述

​   可以看到,StandardEnviroment并没有构造方法,因此创建StandardEnviroment对象时会调用父类的构造方法,也就是AbstractEnvironment的构造方法,我们来看看部分AbstractEnvironment的代码:

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方法的实现如下:

 //调用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里面):

在这里插入图片描述

​   因此,我们可以将上述代码改造为匿名内部类的方式:

//改造前
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配置文件的地址:

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

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
    //关闭以前的 beanFactory(如果存在),并初始化一个新的 beanFactory
refreshBeanFactory();
    // 返回新的 beanFactory
return getBeanFactory();
}

​   obtainFreshBeanFactory方法的逻辑其实很好理解,让我们进到refreshBeanFactory方法看看是如何实现的,refreshBeanFactory方法其实位于AbstractRefreshableApplicationContext

在这里插入图片描述

在这里插入图片描述

destoryBeans方法

​   这里简单提一下destoryBeans方法,该方法主要完成销毁回调,我们来看看一个简单的例子:

在这里插入图片描述

​   测试类如下:

@Test
public void destroy(){
    ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("spring-config.xml");
    //通过手动调用refresh方法触发销毁回调
    ac.refresh();
}

​   控制台输出结果:

在这里插入图片描述

​   可以看到,Spring会帮我们自动回调销毁方法。

​   在Spring中,定义销毁方法的方式有三个,这三者的优先级逐级降低,并且同一方法只会回调一次:

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

@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设置了一系列重要属性,我们简单来看一些后面会用到的属性:

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中的方法(子类有就优先调用子类的,子类没有再去父类中找):

/**
 * 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):

/**
 * 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中,销毁逻辑分为三部分,分别对应三个我们之前提到过的需要销毁回调的场景:

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方法
/**
 * 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属性置为空:

protected final void closeBeanFactory() {
DefaultListableBeanFactory beanFactory = this.beanFactory;
if (beanFactory != null) {
beanFactory.setSerializationId(null);
this.beanFactory = null;
}
}

createBeanFactory

​   在关闭原有的beanFactory后,Spring会通过createBeanFactory继续创建一个新的beanFactory:

//会创建一个DefaultListableBeanFactory类型的beanFactory
protected DefaultListableBeanFactory createBeanFactory() {
//根据父工厂创建一个beanFactory
//非web环境下,工厂的父工厂默认为null,web环境下,Spring的beanFactory就是Spring MVC的beanFactory的父工厂
return new DefaultListableBeanFactory(getInternalParentBeanFactory());
}

​   创建beanFactory的逻辑比较简单,我们继续深入到DefaultListableBeanFactory的构造方法看看:

//DefaultListableBeanFactory会继续调用父类的构造方法,这里的parentBeanFactory为null
public DefaultListableBeanFactory(@Nullable BeanFactory parentBeanFactory) {
super(parentBeanFactory);
}

​   DefaultListableBeanFactory的父类是AbstractAutowireCapableBeanFactory,我们看看它的构造方法:

// 先进到这个构造方法,会继续调用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方法主要干了两件事:

1.设置allowBeanDefinitionOverriding属性:在registerBeanDefinition注册bean定义的时候判断是否允许同名的BeanDefinition 覆盖,默认允许true,Springboot则对其进一步封装,默认不允许false。
2.allowCircularReferences:是否允许循环依赖引用,默认允许true。

​   customizeBeanFactory方法实现如下:

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

/**
 * 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:

/**
 * 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方法:

/**
 * AbstractBeanDefinitionReader的方法
 */
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
return loadBeanDefinitions(location, null);
}

​   还是继续调用AbstractBeanDefinitionReader中重载的loadBeanDefinitions方法:

在这里插入图片描述

​   可以看到这里还是调用了AbstractBeanDefinitionReader中重载的loadBeanDefinitions方法,这里的loadBeanDefinitions方法通过几次中转最终会调用到XmlBeanDefinitionReader的方法:

/**
 * 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方法:

/**
 * 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方法:

/**
 * 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上下文:

/**
 * 创建一个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方法:

/**
 * 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博客


原文地址:https://blog.csdn.net/qq_44218195/article/details/144008473

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!