上篇写了 探索SpringBoot-结合idea搭建Maven工程 续(五)
,好了,基本上关于 Idea
这块暂时先告一个段落了。下面正式来探索下 Spring Boot
相关的内容。
在 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
中,我们使用 import
和Java的 System Property
来引入不同环境所需要的上下文。
<import resource="classpath:/META-INF/${env}-context.xml"/> 复制代码
在 dev-context.xml
和 prod-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
上下文,并输出 id
为 name
的 Bean
的属性值。当然会根据 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
在这个过程中,做了什么事情呢?
xml
文件,初始化 Spring上下文
Spring上下文
中的对象
我们进入到 ClassPathXmlApplicationContext(String)
构造函数中,可以发现调用了另外一个构造函数 ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
而且设置了 refresh
参数为 true
, parent
参数为 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文件加载定义的对象
bean Spring
构造函数首先解析了资源文件并设置为上下文的一个属性,之后进入到了关键的 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()
函数。
/** * 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。