一、SpringBoot启动流程
1.1 启动类
@SpringBootApplication
public class Start {
public static void main(String[] args) {
//从run方法开始
SpringApplication.run(Start.class, args);
System.out.println("aaaa");
}
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
//执行该方法
//创建一个SpringApplication实例
//执行run之后返回ApplicationContext
return (new SpringApplication(primarySources)).run(args);
}
1.2 创建SpringApplication
创建Spring应用
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
//进行一些初始化设置
this.sources = new LinkedHashSet();
this.bannerMode = Mode.CONSOLE;
this.logStartupInfo = true;
this.addCommandLineProperties = true;
this.addConversionService = true;
this.headless = true;
this.registerShutdownHook = true;
this.additionalProfiles = new HashSet();
this.isCustomEnvironment = false;
this.lazyInitialization = false;
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
//将启动类加入到主资源列表中
this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
//推断是否为web环境
this.webApplicationType = WebApplicationType.deduceFromClasspath();
//从spring.factories文件中获取所有的ApplicationContextInitializer类
//用于创建ApplicationContext执行初始化操作
//获取的原理是通过类加载器扫描classpath下所有jar包的META-INF
//扫描所有META-INF文件夹下的spring.facotries文件
//得到文件的内容,通过反射创建实例对象
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
//从spring.factories文件中获取所有的ApplicationListener监听器
//用于监听ApplicationContext创建、启动时发布的事件
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
//推断主启动类
this.mainApplicationClass = this.deduceMainApplicationClass();
}
1.2.1 deduceFromClasspath
推断是否为Web环境
static WebApplicationType deduceFromClasspath() {
//如果不含有servlet、DispatcherServlet
//但是含有reactive.DispatcherHandler,则项目类型为Reactive
if (ClassUtils.isPresent("org.springframework.web.reactive.DispatcherHandler", (ClassLoader)null) && !ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet", (ClassLoader)null) && !ClassUtils.isPresent("org.glassfish.jersey.servlet.ServletContainer", (ClassLoader)null)) {
return REACTIVE;
} else {
//否则判断是否含有
//new String[]{"javax.servlet.Servlet", "org.springframework.web.context.ConfigurableWebApplicationContext"};
//数组中的元素,如果含有则为servlet型的
//都不含有则为普通项目,不是web项目
String[] var0 = SERVLET_INDICATOR_CLASSES;
int var1 = var0.length;
for(int var2 = 0; var2 < var1; ++var2) {
String className = var0[var2];
//isPresent方法会使用forName尝试加载这些类
//如果返回false,则表示没有加载到
//则表示没有web相关的类
if (!ClassUtils.isPresent(className, (ClassLoader)null)) {
return NONE;
}
}
return SERVLET;
}
}
1.2.2 deduceMainApplicationClass
判断主启动类
private Class<?> deduceMainApplicationClass() {
try {
//通过创建一个异常,获取当前方法执行的调用栈信息
StackTraceElement[] stackTrace = (new RuntimeException()).getStackTrace();
StackTraceElement[] var2 = stackTrace;
int var3 = stackTrace.length;
//从栈底开始遍历,找到main方法的栈帧,得到该栈帧所属的类名
for(int var4 = 0; var4 < var3; ++var4) {
StackTraceElement stackTraceElement = var2[var4];
if ("main".equals(stackTraceElement.getMethodName())) {
return Class.forName(stackTraceElement.getClassName());
}
}
} catch (ClassNotFoundException var6) {
}
return null;
}
1.3 run方法(创建应用上下文)
返回一个ConfigurableApplicationContext供主程序使用。
public ConfigurableApplicationContext run(String... args) {
//启动计时器,计算应用启动时间,打印在日志中
StopWatch stopWatch = new StopWatch();
stopWatch.start();
//run方法返回的对象,应用上下文
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
//配置是否是缺少显示屏、鼠标或键盘的环境
this.configureHeadlessProperty();
//从spring.factories文件中获取所有的
//SpringApplicationRunListener
//用于监听SpringApplication运行过程中的各个启动事件
SpringApplicationRunListeners listeners = this.getRunListeners(args);
//1.3.1 触发应用启动中的事件监听器的方法
listeners.starting();
Collection exceptionReporters;
try {
//得到启动参数,并封装成应用上下文可用的参数形式
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//1.3.2 配置环境,包括系统变量、classpath、jdk路径等等
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
//配置忽略的属性
this.configureIgnoreBeanInfo(environment);
//打印banner
Banner printedBanner = this.printBanner(environment);
//1.3.3 创建应用上下文
context = this.createApplicationContext();
//从spring.factories文件中获取异常处理相关的类
exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
//1.3.4 将上述资源全部放入应用上下文中
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//1.3.5 刷新上下文
this.refreshContext(context);
//1.3.6 刷新上下文之后执行一些方法
this.afterRefresh(context, applicationArguments);
//停止计时,打印启动日志,输出应用启动时间
stopWatch.stop();
if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
}
//监听器执行应用启动完成的事件回调
listeners.started(context);
//从应用上下文获取ApplicationRunner、CommandLineRunner
//类型的Bean,并调用他们的run方法
this.callRunners(context, applicationArguments);
} catch (Throwable var10) {
this.handleRunFailure(context, var10, exceptionReporters, listeners);
throw new IllegalStateException(var10);
}
try {
//监听运行时发生的事件,并进行处理
listeners.running(context);
return context;
} catch (Throwable var9) {
this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
throw new IllegalStateException(var9);
}
}
1.3.1 listener.starting()
触发监听器的应用启动中事件回调
void starting() {
Iterator var1 = this.listeners.iterator();
while(var1.hasNext()) {
SpringApplicationRunListener listener = (SpringApplicationRunListener)var1.next();
//执行所有SpringApplicationRunListener的starting方法
//即触发所有监听器的应用启动中回调
listener.starting();
}
}
1.3.2 prepareEnvironment
准备环境变量,生成Environment对象,并将一些属性绑定到SpingApplication中
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {
//获取属性,包括resources目录下的application.proerties文件
//和application.yml文件
ConfigurableEnvironment environment = this.getOrCreateEnvironment();
this.configureEnvironment((ConfigurableEnvironment)environment, applicationArguments.getSourceArgs());
ConfigurationPropertySources.attach((Environment)environment);
//触发SpringApplicationRunListener的环境准备就绪回调
listeners.environmentPrepared((ConfigurableEnvironment)environment);
//将一些属性绑定到SpringApplication中,
//例如通过注解@ConfigurationProperties设置的
this.bindToSpringApplication((ConfigurableEnvironment)environment);
if (!this.isCustomEnvironment) {
environment = (new EnvironmentConverter(this.getClassLoader())).convertEnvironmentIfNecessary((ConfigurableEnvironment)environment, this.deduceEnvironmentClass());
}
ConfigurationPropertySources.attach((Environment)environment);
return (ConfigurableEnvironment)environment;
}
1.3.3 createApplicationContext
创建应用上下文
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
//根据前面判断的应用类型,创建不同的上下文对象
switch(this.webApplicationType) {
case SERVLET:
contextClass = Class.forName("org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext");
break;
case REACTIVE:
contextClass = Class.forName("org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext");
break;
default:
contextClass = Class.forName("org.springframework.context.annotation.AnnotationConfigApplicationContext");
}
} catch (ClassNotFoundException var3) {
throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3);
}
}
//创建上下文实例对象
return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass);
}
1.3.4 prepareContext
为应用上下文设置Environment、注册一些必要的组件
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
//设置环境变量
context.setEnvironment(environment);
this.postProcessApplicationContext(context);
//调用初始化组件,即调用ApplicationContextInitlizer类
//1.2步 通过spring.factories获取
this.applyInitializers(context);
//触发监听器的应用上下文准备就绪事件
listeners.contextPrepared(context);
if (this.logStartupInfo) {
this.logStartupInfo(context.getParent() == null);
this.logStartupProfileInfo(context);
}
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory)beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
//获取所有资源
Set<Object> sources = this.getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
//将资源加载到应用上下文中
this.load(context, sources.toArray(new Object[0]));
//触发监听器的资源加载完毕回调
listeners.contextLoaded(context);
}
1.3.5 refreshContext
核心是refresh()方法,和spring启动过程类似,只是springboot重写了部分方法,例如onRefresh()用于创建Tomcat容器。
private void refreshContext(ConfigurableApplicationContext context) {
this.refresh(context);
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
} catch (AccessControlException var3) {
}
}
}
----------refresh方法-----------------------
//由于创建的ApplicationContext并未重写该方法,所以调用的是抽象父类
//AbstractApplicationContext的该方法,子类只需要重写部分需要的方法即可
//模板方法模式,抽象类定义骨架,子类按需实现血肉
public void refresh() throws BeansException, IllegalStateException {
synchronized(this.startupShutdownMonitor) {
this.prepareRefresh();
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
this.prepareBeanFactory(beanFactory);
try {
this.postProcessBeanFactory(beanFactory);
this.invokeBeanFactoryPostProcessors(beanFactory);
this.registerBeanPostProcessors(beanFactory);
this.initMessageSource();
this.initApplicationEventMulticaster();
this.onRefresh();
this.registerListeners();
this.finishBeanFactoryInitialization(beanFactory);
this.finishRefresh();
} catch (BeansException var9) {
if (this.logger.isWarnEnabled()) {
this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
}
this.destroyBeans();
this.cancelRefresh(var9);
throw var9;
} finally {
this.resetCommonCaches();
}
}
}
具体每个过程可参考:
1.3.6 afterRefresh
抽象方法,由子类去具体实现,方便我们去扩展Springboot框架
protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {
}
二、SpringBoot如何启动Tomcat
这里主要是两个地方:
- 应用上下文的refresh()里的onRefresh()用于创建Tomcat
- 应用上下文的refresh()里的finishRefresh()用于启动Tomcat
2.1 内嵌Tomcat的创建
这里由子类的onRefresh()覆盖抽象类的该方法,抽象类AbstractApplicationContext
中的该方法为空,此处使用的普通Web项目对应的AnnotationConfigServletWebServerApplicationContext
的父类,AbstractApplicationContext
的子类ServletWebServerApplication
中的onRefresh()方法。
protected void onRefresh() {
super.onRefresh();
try {
//创建web容器,这里以默认的Tomcat为例
this.createWebServer();
} catch (Throwable var2) {
throw new ApplicationContextException("Unable to start web server", var2);
}
}
---------createWebServer()-----------------------
private void createWebServer() {
WebServer webServer = this.webServer;
//获取servletContext
ServletContext servletContext = this.getServletContext();
//应用刚启动时,webServer为空,web上下文也为空
//servletContext是给mvc用的
//所以进入if逻辑,先得到一个webServer工厂
//然后创建一个webServer实例,
//并将一些初始化webServer的初始化类当作参数传入(lambda)
if (webServer == null && servletContext == null) {
ServletWebServerFactory factory = this.getWebServerFactory();
this.webServer = factory.getWebServer(new ServletContextInitializer[]{this.getSelfInitializer()});
} else if (servletContext != null) {
try {
this.getSelfInitializer().onStartup(servletContext);
} catch (ServletException var4) {
throw new ApplicationContextException("Cannot initialize servlet context", var4);
}
}
this.initPropertySources();
}
2.1.1 getWebServerFactory
protected ServletWebServerFactory getWebServerFactory() {
//从bean工厂中获取webServer工厂
//此时beanFactory中的类都已经完成了实例化(还未初始化),
//且BeanPostProcessor也都已经加载到BeanFactory中了,
//不会影响AOP等bean增强功能
String[] beanNames = this.getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
//如果没获取到webServerFactory或者数量大于1,则抛出异常
if (beanNames.length == 0) {
throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to missing ServletWebServerFactory bean.");
} else if (beanNames.length > 1) {
throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames));
} else {
return (ServletWebServerFactory)this.getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
}
}
- webServerFactory是如何加载到BeanFactory中的呢?
这是通过配置类来完成的
配置类的名字为ServletWebServerFactoryConfiguration
- 该配置类又是如何被引入的呢?
该配置类是通过SpringBoot的自动配置引入的,SpringBoot的自动配置jar包下的META-INF/spring.factories文件中定义了:
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration
这样在SpringBoot只要引入了web包,就会自动配置ServletWebServerFactoryAutoConfiguration,而ServletWebServerFactoryAutoConfiguration通过@Import注解导入了ServletWebServerFactoryConfiguration
,同时还会导入一些用于配置webServer和webServerFactory的类
详细自动装配流程:
SpringBoot自动装配原理
2.2.2 getWebServer
getWebserver由具体的工厂去实现,此处看默认Tomcat工厂。
public WebServer getWebServer(ServletContextInitializer... initializers) {
if (this.disableMBeanRegistry) {
Registry.disableRegistry();
}
//创建Tomcat提供的同名类,该类用于内嵌tomcat的启动
Tomcat tomcat = new Tomcat();
//Tomcat的基础目录
File baseDir = this.baseDirectory != null ? this.baseDirectory : this.createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
//配置连接器,protocol属性在创建webFactory时就通过前面提到的
//配置类进行了设置
Connector connector = new Connector(this.protocol);
connector.setThrowOnFailure(true);
//将连接器和service关联,内嵌tomcat只有一个service,即当前应用
tomcat.getService().addConnector(connector);
this.customizeConnector(connector);
//为tomcat设置连接器,用于接收请求
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
//设置engine
this.configureEngine(tomcat.getEngine());
Iterator var5 = this.additionalTomcatConnectors.iterator();
//添加处理其他协议的connector
while(var5.hasNext()) {
Connector additionalConnector = (Connector)var5.next();
tomcat.getService().addConnector(additionalConnector);
}
//准备包含当前web应用的StandadrContext,
//并设置从应用上下文传递过来的初始化类
this.prepareContext(tomcat.getHost(), initializers);
//将web容器封装为Springboot统一的WebServer类型
//等待后面启动
return this.getTomcatWebServer(tomcat);
}
prepareContext()这一步很重要,Tomcat如何感知到当前web应用,并将请求传递给内嵌该tomcat的应用全都在一步设置。详细过程请参考:
Spring MVC(3)------Tomcat加载DispatcherServlet及MVC启动流程
2.2 Tomcat的启动
Tomcat启动是在Tomcat封装WebServer时进行,即调用getTomcatWebServer()时。在调用getTomcatWebServer()会创建一个WebServer对象,该对象的构造方法中会调用initilizer()方法,该方法调用了this.tomcat.start()启动tomcat。
注意finishRefresh()中也会有一个startWebServer()方法,但是此时Tomcat已经启动了,只会进行一些别的设置。该start()执行的是WebServer的start()方法,该方法会延迟加载一些其他的懒加载的Servlet的加载,即deferredLoadOnStartup()
,该方法会启动一个后台线程去加载那些懒加载的Servlet (那些loadOnstartup属性不是正数的,是懒加载的)。
详细的Tomcat启动流程,参考:
三、SpringBoot如何整合MVC
MVC框架以前是通过配置文件与Spring整合的,在Springboot通过自动配置替代了以前复杂的配置文件。
所以Spring MVC的启动也是通过自动配置来完成的。
在自动配置包下:
//DispatcherServletAutoConfiguration部分代码
@AutoConfigureOrder(-2147483648)
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnWebApplication(
type = Type.SERVLET
)
@ConditionalOnClass({DispatcherServlet.class})
@AutoConfigureAfter({ServletWebServerFactoryAutoConfiguration.class})
public class DispatcherServletAutoConfiguration {
public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";
public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = "dispatcherServletRegistration";
//.....
//注入DispatcherServlet
@Bean(
name = {"dispatcherServlet"}
)
public DispatcherServlet dispatcherServlet(HttpProperties httpProperties, WebMvcProperties webMvcProperties) {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
dispatcherServlet.setEnableLoggingRequestDetails(httpProperties.isLogRequestDetails());
return dispatcherServlet;
}