探索SpringBoot-一起来看看Spring容器加载核心源码(六)

957 阅读5分钟

前文回顾

上篇写了探索SpringBoot-结合idea搭建Maven工程 续(五),好了,基本上关于Idea这块暂时先告一个段落了。下面正式来探索下Spring Boot相关的内容。

探索Spring 对象工厂能力

Spring2.x时代

learn-spring-framework-2.x中的pom文件中,加上最新的spring-context依赖。表示在这个模块中,我们使用的依赖是spring2.x

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>2.0.8</version>
        </dependency>

因为spring2.x时,必须需要编写xml文件来加载spring上下文。所以,我们除了启动类之后,还打算编写三个xml文件。

  • configurable-context.xml:表示需要加载的上下文
  • dev-context.xml:表示dev环境下需要加载的Bean
  • prod-context.xml:表示prod环境下需要加载的Bean

configurable-context.xml中,我们使用import和Java的System Property来引入不同环境所需要的上下文。

    <import resource="classpath:/META-INF/${env}-context.xml"/>

dev-context.xmlprod-context.xml中分别引入相同名称的Bean,但是这个Bean的存在不同的属性值。

dev-context.xml

    <!-- dev 环境 value Bean 定义-->
    <bean id="name" class="java.lang.String">
        <constructor-arg>
            <value>shane</value>
        </constructor-arg>
    </bean>

prod-context.xml

    <!-- prod 环境 name Bean 定义-->
    <bean id="name" class="java.lang.String">
        <constructor-arg>
            <value>微秒</value>
        </constructor-arg>
    </bean>

最后定义启动类,启动类显示加载Spring上下文,并输出idnameBean的属性值。当然会根据System Property的内容来动态加载不同环境下的Bean,并且输出不同的值。

这么做,也是为了演示Spring最最基础的功能,作为一个对象工厂的能力。

ConfigurableApplicationContextBootstrap.java

public class ConfigurableApplicationContextBootstrap {

    static {
        // 调整系统属性 "env",实现 "name" bean 的定义切换
        // envValue 可能来自于 "-D" 命令行启动参数
        // 参数当不存在时,使用 "prod" 作为默认值
        String envValue = System.getProperty("env", "dev");
        System.setProperty("env", envValue);
    }

    public static void main(String[] args) {
        // 定义 XML ApplicationContext
        // 先留意下这个location的方式,不需要写classpath的前缀
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("META-INF/configurable-context.xml");
        // "name" bean 对象
        String value = (String) context.getBean("name");
        // "name" bean 内容输出
        System.out.println("Bean 'name' 的内容为:" + value);
        // 关闭上下文
        context.close();
    }

控制台输出

思考下Spring在这个过程中,做了什么事情呢?

  1. 加载xml文件,初始化Spring上下文
  2. 获取Spring上下文中的对象

ClassPathXmlAppliationContext源码初步分析

我们进入到ClassPathXmlApplicationContext(String)构造函数中,可以发现调用了另外一个构造函数ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)而且设置了refresh参数为trueparent参数为null

	/**
	 * 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);
	}
	
	
	/**
	 * 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, ApplicationContext parent)
			throws BeansException {

		super(parent);
		setConfigLocations(configLocations);
		if (refresh) {
			refresh();
		}
	}

通过注释可以知道构造函数的作用是用给定的parent创建一个ClassPathXmlApplicationContext,并且根据XML文件加载定义的对象

  • configLocations: 资源文件的地址
  • refresh:是否自动刷新上下文,加载所有的bean定义并且创建所有的单例对象
  • parent:父类上下文(暂时不理解也没有关系,Spring是存在父子上下文的,之后有机会讲到)

refresh函数

构造函数首先解析了资源文件并设置为上下文的一个属性,之后进入到了关键的refresh函数中。

	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			// Prepare this context for refreshing.  [1.为refresh准备上下文]
			prepareRefresh();

			// Tell the subclass to refresh the internal bean factory.[2.告知子类refresh内部bean工厂]
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			// Prepare the bean factory for use in this context.[3.准备需要在本次上文中使用的bean工厂]
			prepareBeanFactory(beanFactory);

			try {
				// Allows post-processing of the bean factory in context subclasses.[4.允许上下文子类的bean工厂调用初始化函数]
				postProcessBeanFactory(beanFactory);

				// Invoke factory processors registered as beans in the context.[5.调用在上下文的注册的工厂处理器]
				invokeBeanFactoryPostProcessors(beanFactory);

				// Register bean processors that intercept bean creation.[6.注册在bean创建的过程中的拦截处理器]
				registerBeanPostProcessors(beanFactory);

				// Initialize message source for this context.[7.初始化消息源]
				initMessageSource();

				// Initialize event multicaster for this context.[8.初始化事件多播机制]
				initApplicationEventMulticaster();

				// Initialize other special beans in specific context subclasses.[9.在这个特定的上下文子类中初始化其他特殊的beans]
				onRefresh();

				// Check for listener beans and register them.[10.检查监听器的beans并且注册他们]
				registerListeners();

				// Instantiate all remaining (non-lazy-init) singletons.[11.初始化存在的单例,不包括懒加载的对象]
				finishBeanFactoryInitialization(beanFactory);

				// Last step: publish corresponding event.[12.最后一步:发布相关的事件]
				finishRefresh();
			}

			catch (BeansException ex) {
				// Destroy already created singletons to avoid dangling resources.
				beanFactory.destroySingletons();

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

				// Propagate exception to caller.
				throw ex;
			}
		}
	}

我的中文注释,总共有12步核心步骤来初始化ClassPathXmlApplicationContext。下面我们在一步步来分析。

prepareRefresh函数

首先看prepareRefresh()函数。

	/**
	 * Prepare this context for refreshing, setting its startup date and
	 * active flag.
	 */
	protected void prepareRefresh() {
		this.startupDate = System.currentTimeMillis();

		synchronized (this.activeMonitor) {
			this.active = true;
		}

		if (logger.isInfoEnabled()) {
			logger.info("Refreshing " + this);
		}
	}

可以看到就只有两步核心操作。首先记录了当前的时间,然后尝试获取activeMonitor的锁。可以activeMonitor的作用后面联系起来再分析。

下一篇分析obtainFreshBeanFactory()函数,一步步来,毕竟是探索系列嘛,不知道的内容,不断地探索,我们才能将其转换为我们知道的东西,这就是学习

当然这是Spring部分的核心的源代码,不过因为SpringBoot其实是构建在Spring基础之上的,所以Spring的部分源代码也会有讲解。

关于写作

以后这里每天都会写一篇文章,题材不限,内容不限,字数不限。尽量把自己每天的思考都放入其中。

如果这篇文章给你带来了帮助,能请你写下是哪个部分吗?有效的反馈是对我最大的帮助。

我是shane。今天是2019年8月11日。百天写作计划的第十八天,18/100。