# 浅析Spring源码
Spring 源码解读
# 1. 下载导入
现在 Spring 新版本都是使用 Gradle 构建,了解下 Gradle 先
# 1.1. 下载代码
下载 Spring 源码,我直接下载的 Releases 包,版本 5.2.22.Releases,当然也可以 Git 下载到提交记录
# 1.2. 导入验证
打开项目,按默认的等待加载即可,下载依赖等,需要点时间
# 1.3. 新增模块
创建一个自己的模块 spring-study,类型为 Gradle
# 1.4. 验证模块
运行模块下的 Main 方法输出正常,但是最后报错
13:45:18: Executing ':spring-study:Main.main()'...
> Task :buildSrc:compileJava UP-TO-DATE
> Task :buildSrc:compileGroovy NO-SOURCE
> Task :buildSrc:pluginDescriptors UP-TO-DATE
> Task :buildSrc:processResources UP-TO-DATE
> Task :buildSrc:classes UP-TO-DATE
> Task :buildSrc:jar UP-TO-DATE
> Task :buildSrc:assemble UP-TO-DATE
> Task :buildSrc:pluginUnderTestMetadata UP-TO-DATE
> Task :buildSrc:compileTestJava NO-SOURCE
> Task :buildSrc:compileTestGroovy NO-SOURCE
> Task :buildSrc:processTestResources NO-SOURCE
> Task :buildSrc:testClasses UP-TO-DATE
> Task :buildSrc:test NO-SOURCE
> Task :buildSrc:validateTaskProperties UP-TO-DATE
> Task :buildSrc:check UP-TO-DATE
> Task :buildSrc:build UP-TO-DATE
fatal: not a git repository (or any of the parent directories): .git
> Task :spring-aop:processResources UP-TO-DATE
> Task :spring-core:cglibRepackJar UP-TO-DATE
> Task :spring-beans:processResources UP-TO-DATE
> Task :spring-expression:processResources UP-TO-DATE
> Task :spring-study:processResources NO-SOURCE
> Task :spring-core:objenesisRepackJar UP-TO-DATE
> Task :spring-core:processResources UP-TO-DATE
> Task :spring-context:processResources UP-TO-DATE
> Task :spring-instrument:compileJava UP-TO-DATE
> Task :spring-instrument:processResources NO-SOURCE
> Task :spring-instrument:classes UP-TO-DATE
> Task :spring-instrument:jar UP-TO-DATE
> Task :spring-jcl:compileJava UP-TO-DATE
> Task :spring-jcl:processResources UP-TO-DATE
> Task :spring-jcl:classes UP-TO-DATE
> Task :spring-jcl:jar UP-TO-DATE
> Task :kotlin-coroutines:compileKotlin
> Task :kotlin-coroutines:compileJava NO-SOURCE
> Task :kotlin-coroutines:processResources NO-SOURCE
> Task :kotlin-coroutines:classes UP-TO-DATE
> Task :kotlin-coroutines:inspectClassesForKotlinIC UP-TO-DATE
> Task :kotlin-coroutines:jar UP-TO-DATE
> Task :spring-core:compileKotlin UP-TO-DATE
> Task :spring-core:compileJava UP-TO-DATE
> Task :spring-core:classes UP-TO-DATE
> Task :spring-core:inspectClassesForKotlinIC UP-TO-DATE
> Task :spring-core:jar UP-TO-DATE
> Task :spring-beans:compileGroovy UP-TO-DATE
> Task :spring-beans:compileKotlin UP-TO-DATE
> Task :spring-beans:compileJava NO-SOURCE
> Task :spring-beans:classes UP-TO-DATE
> Task :spring-beans:inspectClassesForKotlinIC UP-TO-DATE
> Task :spring-beans:jar UP-TO-DATE
> Task :spring-expression:compileKotlin
> Task :spring-expression:compileJava UP-TO-DATE
> Task :spring-expression:classes UP-TO-DATE
> Task :spring-expression:inspectClassesForKotlinIC UP-TO-DATE
> Task :spring-expression:jar UP-TO-DATE
> Task :spring-aop:compileJava UP-TO-DATE
> Task :spring-aop:classes UP-TO-DATE
> Task :spring-aop:jar UP-TO-DATE
> Task :spring-context:compileKotlin
> Task :spring-context:compileJava FROM-CACHE
> Task :spring-context:compileGroovy NO-SOURCE
> Task :spring-context:classes UP-TO-DATE
> Task :spring-context:inspectClassesForKotlinIC
> Task :spring-context:jar
> Task :spring-study:compileJava
> Task :spring-study:classes
Connected to the target VM, address: 'localhost:46903', transport: 'socket'
Disconnected from the target VM, address: 'localhost:46903', transport: 'socket'
> Task :spring-study:Main.main()
Hello world!
BUILD SUCCESSFUL in 28s
35 actionable tasks: 7 executed, 1 from cache, 27 up-to-date
Build scan background action failed.
org.gradle.process.internal.ExecException: Process 'command 'git'' finished with non-zero exit value 128
at org.gradle.process.internal.DefaultExecHandle$ExecResultImpl.assertNormalExitValue(DefaultExecHandle.java:409)
at org.gradle.process.internal.DefaultExecAction.execute(DefaultExecAction.java:38)
at org.gradle.process.internal.DefaultExecActionFactory.exec(DefaultExecActionFactory.java:145)
at io.spring.ge.conventions.gradle.WorkingDirectoryProcessOperations.exec(WorkingDirectoryProcessOperations.java:45)
at io.spring.ge.conventions.gradle.ProcessOperationsProcessRunner.run(ProcessOperationsProcessRunner.java:41)
at io.spring.ge.conventions.core.BuildScanConventions.run(BuildScanConventions.java:166)
at io.spring.ge.conventions.core.BuildScanConventions.addGitMetadata(BuildScanConventions.java:113)
at io.spring.ge.conventions.gradle.GradleConfigurableBuildScan.lambda$background$0(GradleConfigurableBuildScan.java:104)
at com.gradle.scan.plugin.internal.api.j.a(SourceFile:22)
at com.gradle.scan.plugin.internal.api.k$a.a(SourceFile:112)
at com.gradle.scan.plugin.internal.api.h.a(SourceFile:62)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:829)
13:45:48: Execution finished ':spring-study:Main.main()'.
参考文章注释掉根目录下 build.gradle 下的 id 'io.spring.ge.conventions' version '0.0.7'
解决
plugins {
id 'io.spring.dependency-management' version '1.0.9.RELEASE' apply false
// id 'io.spring.ge.conventions' version '0.0.7'
id 'io.spring.nohttp' version '0.0.5.RELEASE'
id "io.freefair.aspectj" version '4.1.6' apply false
id 'org.jetbrains.dokka' version '0.10.1' apply false
id 'org.jetbrains.kotlin.jvm' version '1.3.72' apply false
id 'org.asciidoctor.jvm.convert' version '2.4.0'
id 'org.asciidoctor.jvm.pdf' version '2.4.0'
id "com.github.ben-manes.versions" version '0.28.0'
id 'com.gradle.build-scan' version '3.2'
id 'de.undercouch.download' version '4.1.1'
}
再次运行无报错
14:10:33: Executing ':spring-study:Main.main()'...
> Task :buildSrc:compileJava UP-TO-DATE
> Task :buildSrc:compileGroovy NO-SOURCE
> Task :buildSrc:pluginDescriptors UP-TO-DATE
> Task :buildSrc:processResources UP-TO-DATE
> Task :buildSrc:classes UP-TO-DATE
> Task :buildSrc:jar UP-TO-DATE
> Task :buildSrc:assemble UP-TO-DATE
> Task :buildSrc:pluginUnderTestMetadata UP-TO-DATE
> Task :buildSrc:compileTestJava NO-SOURCE
> Task :buildSrc:compileTestGroovy NO-SOURCE
> Task :buildSrc:processTestResources NO-SOURCE
> Task :buildSrc:testClasses UP-TO-DATE
> Task :buildSrc:test NO-SOURCE
> Task :buildSrc:validateTaskProperties UP-TO-DATE
> Task :buildSrc:check UP-TO-DATE
> Task :buildSrc:build UP-TO-DATE
> Task :spring-core:cglibRepackJar UP-TO-DATE
> Task :spring-core:objenesisRepackJar UP-TO-DATE
> Task :spring-expression:processResources UP-TO-DATE
> Task :spring-beans:processResources UP-TO-DATE
> Task :spring-study:processResources NO-SOURCE
> Task :spring-core:processResources UP-TO-DATE
> Task :spring-aop:processResources UP-TO-DATE
> Task :spring-context:processResources UP-TO-DATE
> Task :kotlin-coroutines:compileKotlin UP-TO-DATE
> Task :kotlin-coroutines:compileJava NO-SOURCE
> Task :kotlin-coroutines:processResources NO-SOURCE
> Task :kotlin-coroutines:classes UP-TO-DATE
> Task :kotlin-coroutines:inspectClassesForKotlinIC UP-TO-DATE
> Task :kotlin-coroutines:jar UP-TO-DATE
> Task :spring-instrument:compileJava UP-TO-DATE
> Task :spring-instrument:processResources NO-SOURCE
> Task :spring-instrument:classes UP-TO-DATE
> Task :spring-instrument:jar UP-TO-DATE
> Task :spring-jcl:compileJava UP-TO-DATE
> Task :spring-jcl:processResources UP-TO-DATE
> Task :spring-jcl:classes UP-TO-DATE
> Task :spring-jcl:jar UP-TO-DATE
> Task :spring-core:compileKotlin UP-TO-DATE
> Task :spring-core:compileJava UP-TO-DATE
> Task :spring-core:classes UP-TO-DATE
> Task :spring-core:inspectClassesForKotlinIC UP-TO-DATE
> Task :spring-core:jar UP-TO-DATE
> Task :spring-expression:compileKotlin UP-TO-DATE
> Task :spring-expression:compileJava UP-TO-DATE
> Task :spring-expression:classes UP-TO-DATE
> Task :spring-expression:inspectClassesForKotlinIC UP-TO-DATE
> Task :spring-beans:compileGroovy UP-TO-DATE
> Task :spring-expression:jar UP-TO-DATE
> Task :spring-beans:compileKotlin UP-TO-DATE
> Task :spring-beans:compileJava NO-SOURCE
> Task :spring-beans:classes UP-TO-DATE
> Task :spring-beans:inspectClassesForKotlinIC UP-TO-DATE
> Task :spring-beans:jar UP-TO-DATE
> Task :spring-aop:compileJava UP-TO-DATE
> Task :spring-aop:classes UP-TO-DATE
> Task :spring-aop:jar UP-TO-DATE
> Task :spring-context:compileKotlin UP-TO-DATE
> Task :spring-context:compileJava UP-TO-DATE
> Task :spring-context:compileGroovy NO-SOURCE
> Task :spring-context:classes UP-TO-DATE
> Task :spring-context:inspectClassesForKotlinIC UP-TO-DATE
> Task :spring-context:jar UP-TO-DATE
> Task :spring-study:compileJava UP-TO-DATE
> Task :spring-study:classes UP-TO-DATE
Connected to the target VM, address: 'localhost:51073', transport: 'socket'
Disconnected from the target VM, address: 'localhost:51073', transport: 'socket'
> Task :spring-study:Main.main()
Hello world!
BUILD SUCCESSFUL in 3s
35 actionable tasks: 1 executed, 34 up-to-date
14:10:37: Execution finished ':spring-study:Main.main()'.
# 1.5. Bean验证
构建 Spring Bean 验证,先导入对应模块依赖,在 dependencies 下添加 compile(project(":spring-context"))
和 compile(project(":spring-instrument"))
plugins {
id 'java'
}
group 'org.springframework'
version '5.2.22.RELEASE'
repositories {
mavenCentral()
}
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
compile(project(":spring-context"))
compile(project(":spring-instrument"))
}
test {
useJUnitPlatform()
}
然后创建一个类及 Bean 配置类如下
public class App {
}
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SpringConfig {
@Bean
public App app() {
return new App();
}
}
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static void main(String[] args) {
System.out.println("Hello World!");
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
String[] beanDefinitionNameArray = applicationContext.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNameArray) {
System.out.println(beanDefinitionName);
}
}
}
运行输出成功
14:47:10: Executing ':spring-study:Main.main()'...
> Task :buildSrc:compileJava UP-TO-DATE
> Task :buildSrc:compileGroovy NO-SOURCE
> Task :buildSrc:pluginDescriptors UP-TO-DATE
> Task :buildSrc:processResources UP-TO-DATE
> Task :buildSrc:classes UP-TO-DATE
> Task :buildSrc:jar UP-TO-DATE
> Task :buildSrc:assemble UP-TO-DATE
> Task :buildSrc:pluginUnderTestMetadata UP-TO-DATE
> Task :buildSrc:compileTestJava NO-SOURCE
> Task :buildSrc:compileTestGroovy NO-SOURCE
> Task :buildSrc:processTestResources NO-SOURCE
> Task :buildSrc:testClasses UP-TO-DATE
> Task :buildSrc:test NO-SOURCE
> Task :buildSrc:validateTaskProperties UP-TO-DATE
> Task :buildSrc:check UP-TO-DATE
> Task :buildSrc:build UP-TO-DATE
> Task :spring-core:cglibRepackJar UP-TO-DATE
> Task :spring-core:objenesisRepackJar UP-TO-DATE
> Task :spring-core:processResources UP-TO-DATE
> Task :spring-aop:processResources UP-TO-DATE
> Task :spring-expression:processResources UP-TO-DATE
> Task :spring-study:processResources NO-SOURCE
> Task :spring-beans:processResources UP-TO-DATE
> Task :spring-context:processResources UP-TO-DATE
> Task :spring-instrument:compileJava UP-TO-DATE
> Task :spring-instrument:processResources NO-SOURCE
> Task :spring-instrument:classes UP-TO-DATE
> Task :spring-instrument:jar UP-TO-DATE
> Task :kotlin-coroutines:compileKotlin UP-TO-DATE
> Task :kotlin-coroutines:compileJava NO-SOURCE
> Task :kotlin-coroutines:processResources NO-SOURCE
> Task :kotlin-coroutines:classes UP-TO-DATE
> Task :kotlin-coroutines:inspectClassesForKotlinIC UP-TO-DATE
> Task :kotlin-coroutines:jar UP-TO-DATE
> Task :spring-jcl:compileJava UP-TO-DATE
> Task :spring-jcl:processResources UP-TO-DATE
> Task :spring-jcl:classes UP-TO-DATE
> Task :spring-jcl:jar UP-TO-DATE
> Task :spring-core:compileKotlin UP-TO-DATE
> Task :spring-core:compileJava UP-TO-DATE
> Task :spring-core:classes UP-TO-DATE
> Task :spring-core:inspectClassesForKotlinIC UP-TO-DATE
> Task :spring-core:jar UP-TO-DATE
> Task :spring-expression:compileKotlin UP-TO-DATE
> Task :spring-expression:compileJava UP-TO-DATE
> Task :spring-expression:classes UP-TO-DATE
> Task :spring-expression:inspectClassesForKotlinIC UP-TO-DATE
> Task :spring-expression:jar UP-TO-DATE
> Task :spring-beans:compileGroovy UP-TO-DATE
> Task :spring-beans:compileKotlin UP-TO-DATE
> Task :spring-beans:compileJava NO-SOURCE
> Task :spring-beans:classes UP-TO-DATE
> Task :spring-beans:inspectClassesForKotlinIC UP-TO-DATE
> Task :spring-beans:jar UP-TO-DATE
> Task :spring-aop:compileJava UP-TO-DATE
> Task :spring-aop:classes UP-TO-DATE
> Task :spring-aop:jar UP-TO-DATE
> Task :spring-context:compileKotlin UP-TO-DATE
> Task :spring-context:compileJava UP-TO-DATE
> Task :spring-context:compileGroovy NO-SOURCE
> Task :spring-context:classes UP-TO-DATE
> Task :spring-context:inspectClassesForKotlinIC UP-TO-DATE
> Task :spring-context:jar UP-TO-DATE
> Task :spring-study:compileJava
> Task :spring-study:classes
Connected to the target VM, address: 'localhost:55359', transport: 'socket'
Disconnected from the target VM, address: 'localhost:55359', transport: 'socket'
> Task :spring-study:Main.main()
Hello World!
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
springConfig
app
BUILD SUCCESSFUL in 6s
35 actionable tasks: 2 executed, 33 up-to-date
14:47:17: Execution finished ':spring-study:Main.main()'.
# 2. Spring启动
引入 Spring 必须在 xml 配置一个上下文监听器,而这个就是启动的入口
<!-- 配置Spring上下文监听器 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
可以看到 ContextLoaderListener 继承 ContextLoader 然后实现了 Tomcat 容器的 ServletContextListener 接口,所以它与普通的 Servlet 监听是一样的,同样是重写到两个方法:contextInitialized() 方法在 Web 容器初始化时执行,contextDestroyed() 方法在容器销毁时执行
# 2.1. initWebApplicationContext
Web 容器启动时会触发初始化事件,ContextLoaderListener 监听到这个事件,其 contextInitialized() 方法会被调用,方法中再调用父类 ContextLoader 的 initWebApplicationContext() 方法,这个就是整个 Spring IOC 开始启动的入口,Spring 会初始化一个根上下文,即 WebApplicationContext,其默认实现类是 XmlWebApplicationContext
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
// 首先判断 servlectContext 中是否已经存在根上下文,如果存在,则抛出异常,只能创建一次
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!");
}
servletContext.log("Initializing Spring root WebApplicationContext");
Log logger = LogFactory.getLog(ContextLoader.class);
if (logger.isInfoEnabled()) {
logger.info("Root WebApplicationContext: initialization started");
}
long startTime = System.currentTimeMillis();
try {
// Store context in local instance variable, to guarantee that
// it is available on ServletContext shutdown.
// context 参数为空去获取对应的 WebApplicationContext,其默认实现是 XmlWebApplicationContext
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
// 根据 context 类型处理,只要是 ConfigurableWebApplicationContext 就进去
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
// active 是否打开,未打开执行 refresh 刷新,refresh 是整个 IOC 最关键的核心方法
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent ->
// determine parent for root web application context, if any.
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
// 配置和刷新上下文,整个 IOC 的核心流程,具体看详细分析笔记
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
}
else if (ccl != null) {
currentContextPerThread.put(ccl, this.context);
}
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
}
return this.context;
}
catch (RuntimeException | Error ex) {
logger.error("Context initialization failed", ex);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
}
# 2.2. createWebApplicationContext
protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
Class<?> contextClass = determineContextClass(sc);
// 必须为 ConfigurableWebApplicationContext 的子类
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
}
// 根据类名创建类,返回强转为父类 ConfigurableWebApplicationContext
return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}
# 2.3. configureAndRefreshWebApplicationContext
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// The application context id is still set to its original default value
// -> assign a more useful id based on available information
String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
if (idParam != null) {
wac.setId(idParam);
}
else {
// Generate default id...
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(sc.getContextPath()));
}
}
wac.setServletContext(sc);
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (configLocationParam != null) {
wac.setConfigLocation(configLocationParam);
}
// The wac environment's #initPropertySources will be called in any case when the context
// is refreshed; do it eagerly here to ensure servlet property sources are in place for
// use in any post-processing or initialization that occurs below prior to #refresh
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
}
// 初始化
customizeContext(sc, wac);
// 调用父类 AbstractApplicationContext 类下 refresh() 方法
wac.refresh();
}
# 3. SpringMVC启动
虽然使用 SpringMVC 不需要我们写 Servlet,但 SpringMVC 是封装了 Servlet,提供 DispatcherServlet 来帮我们处理的,所以需要在 web.xml 配置 DispatcherServlet
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 加载SpringMVC配置文件 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-context-web.xml</param-value>
</init-param>
<!-- 启动就加载这个Servlet -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
可以看出 DispatcherServlet,映射的url是 /,所以所有的请求都会被它拦截处理,映射到 Controller 处理
# 3.1. init
我们知道,Servlet 初始化时,Servlet 的 init() 方法会被调用,进入 DispatcherServlet 中,发现并没有该方法,那么肯定在它集成的父类上,DispatcherServlet 继承于 FrameworkServlet,结果还是没找到,继续找它的父类 HttpServletBean,HttpServletBean 继承于 HttpServlet,实现了 init()
@Override
public final void init() throws ServletException {
// Set bean properties from init parameters.
// 读取配置
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
}
throw ex;
}
}
// Let subclasses do whatever initialization they like.
// 模板方法,给子类实现
initServletBean();
}
# 3.2. initServletBean
/**
* Overridden method of {@link HttpServletBean}, invoked after any bean properties
* have been set. Creates this servlet's WebApplicationContext.
* 创建此 servlet 的 WebApplicationContext
*/
@Override
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
if (logger.isInfoEnabled()) {
logger.info("Initializing Servlet '" + getServletName() + "'");
}
long startTime = System.currentTimeMillis();
try {
// 初始化WebApplicationContext
this.webApplicationContext = initWebApplicationContext();
// 调用initFrameworkServlet,空方法,提供给子类复写
initFrameworkServlet();
}
catch (ServletException | RuntimeException ex) {
logger.error("Context initialization failed", ex);
throw ex;
}
if (logger.isDebugEnabled()) {
String value = this.enableLoggingRequestDetails ?
"shown which may lead to unsafe logging of potentially sensitive data" :
"masked to prevent unsafe logging of potentially sensitive data";
logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
"': request parameters and headers will be " + value);
}
if (logger.isInfoEnabled()) {
logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
}
}
# 3.3. initWebApplicationContext
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
// 有参数构造方法,传入webApplicationContext对象,就会进入该判断
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
// 还没初始化过,容器的refresh()还没有调用
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext);
}
// 调用Spring的refresh()方法初始化IOC
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
// No context instance was injected at construction time -> see if one
// has been registered in the servlet context. If one exists, it is assumed
// that the parent context (if any) has already been set and that the
// user has performed any initialization such as setting the context id
// 获取ServletContext,之前通过setAttribute设置到了ServletContext中,现在通过getAttribute获取到
wac = findWebApplicationContext();
}
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
// 创建WebApplicationContext,设置环境Environment、父容器,本地资源文件
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
synchronized (this.onRefreshMonitor) {
// 模板模式空方法,子类重写进行逻辑处理,子类DispatcherServlet重写了它,开始SpringMvc的初始化流程
onRefresh(wac);
}
}
// 用setAttribute(),将容器设置到ServletContext中
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
# 4. SpringBoot启动
SpringBoot 启动都是直接使用的 SpringApplication.run(Application.class, args);
,进入源码如下
# 4.1. run
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
// 一般是执行的这个方法,然后把自己封装成Class数组调用下面的重载方法
return run(new Class<?>[] { primarySource }, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
// 创建执行构造方法初始化,再调用run方法,这个run方法便是启动的整体流程
return new SpringApplication(primarySources).run(args);
}
# 4.2. refresh
public ConfigurableApplicationContext run(String... args) {
long startTime = System.nanoTime();
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
// 1. 配置属性,设置系统属性java.awt.headless,为true则启用headless模式
// headless模式是应用的一种配置模式,在服务器缺少显示设备、键盘、鼠标等外设的情况下可以使用该模式
// 比如我们使用的Linux服务器就是缺少前述的这些设备,但是又需要使用这些设备提供的能力
configureHeadlessProperty();
// 2. 获取监听器,发布应用开始启动事件
// 通过SpringFactoriesLoader检索META-INF/spring.factories,
// 找到声明的所有SpringApplicationRunListener的实现类并将其实例化,
// 之后逐个调用其started()方法,广播SpringBoot要开始执行了
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
// 3. 初始化参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 4 配置环境,创建并配置当前SpringBoot应用将要使用的Environment
// 包括配置要使用的PropertySource以及Profile
// 并遍历调用所有的SpringApplicationRunListener的environmentPrepared()方法
// 广播Environment准备完毕
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
configureIgnoreBeanInfo(environment);
// 输出打印banner,如果项目有创建自定义的就打印项目的,否则默认使用Spring的
Banner printedBanner = printBanner(environment);
// 5. 创建应用上下文
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
// 6. 预处理上下文,为ApplicationContext加载environment
// 之后逐个执行ApplicationContextInitializer的initialize()方法来进一步封装ApplicationContext
// 并调用所有的SpringApplicationRunListener的contextPrepared()方法
// 【EventPublishingRunListener只提供了一个空的contextPrepared()方法】
// 调用SpringApplicationRunListener的contextLoaded()方法
// 广播ApplicationContext的IoC加载完成
// 这里就包括通过@EnableAutoConfiguration导入的各种自动配置类
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
// 7. 刷新上下文,调用Spring内AbstractApplicationContext的refresh()方法
// 初始化IOC,AOP等,就是执行Spring的启动流程
// 在refresh()方法中有个onRefresh()空方法,SpringBoot在这里实现了Web容器的创建
// onRefresh()方法是调用其子类实现的,也就是ServletWebServerApplicationContext
refreshContext(context);
// 8. 再一次刷新上下文,空方法,扩展需要,输出启动信息时间
afterRefresh(context, applicationArguments);
Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
}
// 9. 发布应用已经启动的事件
listeners.started(context, timeTakenToStartup);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
try {
Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
listeners.ready(context, timeTakenToReady);
}
catch (Throwable ex) {
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
return context;
}
参考
← Source 深入Spring源码 →