背景
最近在使用Spring MVC过程中遇到了一些问题,网上搜索不少帖子后虽然找到了答案和解决方法,但这些答案大部分都只是给了结论,并没有说明具体原因,感觉总是有点不太满意。
更重要的是这些所谓的结论大多是抄来抄去,基本源自一家,真实性也有待考证。
要成为一名优秀的码农,不仅能熟练的复制粘贴,更要有打破砂锅问到底的精神,达到知其然也知其所以然的境界。
那作为程序员怎么能知其所以然呢?
答案就是阅读源代码!
此处请大家内心默读三遍。
用过Spring 的人都知道其核心就是IOC和AOP,因此要想了解Spring机制就得先从这两点入手,本文主要通过对IOC部分的机制进行介绍。
2. 实验环境
在开始阅读之前,先准备好以下实验材料。
- Spring 5.0源码(https://github.com/spring-projects/spring-framework.git)
- IDE:Intellij IDEA
IDEA 是一个优秀的开发工具,如果还在用Eclipse的建议切换到此工具进行。
IDEA有很多的快捷键,在分析过程中建议大家多用Ctrl+Alt+B快捷键,可以快速定位到实现函数。
3. Spring Bean 解析注册
Spring bean的加载主要分为以下6步:
- (1)读取XML配置文件
- (2)XML文件解析为document文档
- (3)解析bean
- (4)注册bean
- (5)实例化bean
- (6)获取bean
3.1 读取XML配置文件
查看源码第一步是找到程序入口,再以入口为突破口,一步步进行源码跟踪。
Java Web应用中的入口就是web.xml。
在web.xml找到ContextLoaderListener ,此Listener负责初始化Spring IOC。
contextConfigLocation参数设置了bean定义文件地址。
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:spring.xml</param-value>
</context-param>
下面是ContextLoaderListener的官方定义:
public class ContextLoaderListener extends ContextLoader implements ServletContextListener
Bootstrap listener to start up and shut down Spring's root WebApplicationContext. Simply delegates to ContextLoader as well as to ContextCleanupListener.
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/context/ContextLoaderListener.html
翻译过来ContextLoaderListener作用就是负责启动和关闭Spring root WebApplicationContext。
具体WebApplicationContext是什么?开始看源码。
package org.springframework.web.context;
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
public ContextLoaderListener() {
}
public ContextLoaderListener(WebApplicationContext context) {
super(context);
}
//servletContext初始化时候调用
public void contextInitialized(ServletContextEvent event) {
this.initWebApplicationContext(event.getServletContext();
}
//servletContext销毁时候调用
public void contextDestroyed(ServletContextEvent event) {
this.closeWebApplicationContext(event.getServletContext());
}
}
从源码看出此Listener主要有两个函数,一个负责初始化WebApplicationContext,一个负责销毁。
继续看initWebApplicationContext函数。
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
//初始化Spring容器时如果发现servlet 容器中已存在根Spring容根器则抛出异常,证明rootWebApplicationContext只能有一个。
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
throw new IllegalStateException(
"Cannot initialize context because there is already a root application context present - " +
"check whether you have multiple ContextLoader* definitions in your web.xml!");
}
if (this.context == null) {
//1.创建webApplicationContext实例
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
//2.配置WebApplicationContext
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
//把生成的webApplicationContext 设置为root webApplicationContext。
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
return this.context;
}
在上面的代码中主要有两个功能:
- (1)创建WebApplicationContext实例。
- (2)配置生成WebApplicationContext实例。
3.1.1 创建WebApplicationContext实例
进入
CreateWebAPPlicationContext函数
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
//得到ContextClass类,默认实例化的是XmlWebApplicationContext类
Class<?> contextClass = determineContextClass(sc);
//实例化Context类
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}
进入determineContextClass函数。
protected Class<?> determineContextClass(ServletContext servletContext) {
// 此处CONTEXT_CLASS_PARAM = "contextClass"String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
if (contextClassName != null) {
//若设置了contextClass则使用定义好的ContextClass。
return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
}
else {
//此处获取的是在Spring源码中ContextLoader.properties中配置的org.springframework.web.context.support.XmlWebApplicationContext类。
contextClassName = defaultStrategies.get