1 概述
前面搭建工程的例子,运行的是一个桌面程序,并不是一个Web程序,在这篇中我们把它改为Web程序,同时从启动角度看看它们的区别。
2 Web模式
2.1 桌面例子
回顾一下前面的例子,其pom.xml的配置如下:
// pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.18</version>
<relativePath/>
</parent>
<groupId>com.qqian.stepfmk</groupId>
<artifactId>srvpro</artifactId>
<version>1.0.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
代码如下:
@SpringBootApplication
public class SrvproApplication {
public static void main(String[] args) {
SpringApplication.run(SrvproApplication.class, args);
}
@Bean
public CommandLineRunner commandLineRunner(ApplicationContext ctx) {
return args -> {
System.out.println("Hello World");
};
}
}
2.2 Web例子
(1) 之前看<parent>节点上一级的parent所用的spring-boot-dependencies的时候,pom文件见 https://repo.maven.apache.org/maven2/org/springframework/boot/spring-boot-dependencies/2.7.18/spring-boot-dependencies-2.7.18.pom ,里面比较多starter,其中spring-boot-starter-web就是和web有关的starter。在pom.xml中,用spring-boot-starter-web代替spring-boot-starter即可转换为web程序。
// pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.18</version>
<relativePath/>
</parent>
<groupId>com.qqian.stepfmk</groupId>
<artifactId>srvpro</artifactId>
<version>1.0.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
查看一下spring-boot-starter-web里的依赖:https://repo.maven.apache.org/maven2/org/springframework/boot/spring-boot-starter-web/2.7.18/spring-boot-starter-web-2.7.18.pom
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.7.18</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
<version>2.7.18</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>2.7.18</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.3.31</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.31</version>
<scope>compile</scope>
</dependency>
</dependencies>
从里面看到,也引用了spring-boot-starter,由这个starter提供springboot的基础功能;另外引用的spring-boot-starter-tomcat、spring-web和spring-webmvc,则提供了web相关的基础功能。
(2) 在入口代码中去掉CommandLineRunner这个bean,在web程序中一般不需要用到它(留着也可以运行)。
// 只留main()方法的运行
@SpringBootApplication
public class SrvproApplication {
public static void main(String[] args) {
SpringApplication.run(SrvproApplication.class, args);
}
}
(3) 新建一个Controller类,提供一个接口方法:
// com.qqian.stepfmk.srvpro.hello.HelloController
@RestController
public class HelloController {
@RequestMapping("sayHello")
public String say(@RequestParam("message") String messge) {
return "Hello world: " + messge;
}
}
(4) 运行程序,在控制台上打印的日志
Starting SrvproApplication using Java 1.8.0_60 on DESKTOP-1 with PID 21336
No active profile set, falling back to 1 default profile: "default"
Tomcat initialized with port(s): 8080 (http)
Starting service [Tomcat]
Starting Servlet engine: [Apache Tomcat/9.0.83]
Initializing Spring embedded WebApplicationContext
Root WebApplicationContext: initialization completed in 1602 ms
Tomcat started on port(s): 8080 (http) with context path ''
Started SrvproApplication in 2.673 seconds (JVM running for 3.208)
从日志可以看出,web程序运行在8080端口上,context path是空字符串。
5、从浏览器上访问:http://localhost:8080/sayHello?message=zhangsan,返回以下结果:
Hello world: zhangsan
3 原理
从上面例子看,就更换了一个依赖,再增加Controller接口,就可以用浏览器的方式访问了,main函数里还是只有一行代码这么整洁,传统的tomcat和把war发布到tomcat里等操作都不需要了,简单了很多。如此简洁的代码,是如何实现web功能的?
3.1 run()方法的主流程
// 1. 通过SpringApplication.run运行程序
// 源码位置:com.qqian.stepfmk.srvpro.SrvproApplication
public static void main(String[] args) {
SpringApplication.run(SrvproApplication.class, args);
}
// 2. 在SpringApplication提供了两个静态方法run,和一个对象方法run,在第二个静态run方法中new了一个SpringApplication,执行对象方法run()
// 源码位置:org.springframework.boot.SpringApplication
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
public ConfigurableApplicationContext run(String... args) {
// 省略部分代码...
try {
// 3. 创建上下文类,通过上下文类区分是否是web程序
context = createApplicationContext();
// 4. 初始化程序
refreshContext(context);
// 5. 执行Runner
callRunners(context, applicationArguments);
}
// 省略部分代码...
return context;
}
3.2 初始化Web应用类型标记
在主流程步骤2里new了一个SpringApplication,在里面初始化了webApplicationType这个Web应用类型标识,应用类型大致分为Servlet Web应用、响应式Web应用、普通应用,这里把Application翻译为“应用”:
// 源码位置:org.springframework.boot.SpringApplication
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
// 1. 创建SpringApplication对象,并运行其run()方法
return new SpringApplication(primarySources).run(args);
}
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 2. 推演Web应用类型标识,因为其是根据依赖的类来确定的,而不是在哪里有对应的配置,所以是推演来的
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.bootstrapRegistryInitializers = new ArrayList<>(getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
// 源码位置:org.springframework.boot.WebApplicationType
public enum WebApplicationType {
// 3. 在枚举中定义三种Web应用类型,NONE代表不是Web应用(普通应用),另外两种分别代表Servlet Web应用、响应式Web应用
NONE, SERVLET, REACTIVE;
// 4. 预先初始化一些帮助推演的常量,大概是当引用的包里面有哪些类的时候,就认为是哪种应用类型
// javax.servlet.Servlet在tomcat-embed-core包里,ConfigurableWebApplicationContext在spring-web包里
private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet", "org.springframework.web.context.ConfigurableWebApplicationContext" };
// DispatcherServlet在spring-webmvc包里
private static final String WEBMVC_INDICATOR_CLASS = "org.springframework.web.servlet.DispatcherServlet";
// DispatcherHandler在spring-webflux包里
private static final String WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler";
// ServletContainer在org.glassfish.jersey.containers:jersey-container-servlet-core包里,Jersey是一个Web框架
private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";
static WebApplicationType deduceFromClasspath() {
// 5. 只有引了spring-webflux包且没引另外两个包的任意一个,才是响应式Web模式
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
// 6. 没有引tomcat-embed-core包和spring-web包中的任意一个则不是Web模式
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
// 7. 引了tomcat-embed-core包和spring-web包中的任意一个则是Web模式
return WebApplicationType.SERVLET;
}
}
3.3 创建上下文
主流程步骤3中的创建上下文SpringApplication.createApplicationContext():
// 源码位置:org.springframework.boot.SpringApplication
protected ConfigurableApplicationContext createApplicationContext() {
// 1. 调用工厂的create()方法创建上下文,这里以DefaultApplicationContextFactory工厂为例
// applicationContextFactory为org.springframework.boot.DefaultApplicationContextFactory
return this.applicationContextFactory.create(this.webApplicationType);
}
// 源码位置:org.springframework.boot.DefaultApplicationContextFactory
public ConfigurableApplicationContext create(WebApplicationType webApplicationType) {
try {
// 2. 调用getFromSpringFactories方法创建Context
// 提供的webApplicationType这个Web应用类型标识作为参数,参考前面推演这个值的说明
// 另外两个是方法的引用,类似函数式编程的函数,前一个是预期用来场景Context的,后一个是在没有创建到Context的时候作为默认补救的
return getFromSpringFactories(webApplicationType, ApplicationContextFactory::create, AnnotationConfigApplicationContext::new);
} catch (Exception ex) {
throw new IllegalStateException("Unable create a default ApplicationContext instance, "
+ "you may need a custom ApplicationContextFactory", ex);
}
}
private <T> T getFromSpringFactories(WebApplicationType webApplicationType, BiFunction<ApplicationContextFactory, WebApplicationType, T> action, Supplier<T> defaultResult) {
// 3. SpringFactoriesLoader.loadFactories()加载到工厂有两个,遍历工厂去调用工厂创建Context对象:
// org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext.Factory
// org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext.Factory
for (ApplicationContextFactory candidate : SpringFactoriesLoader.loadFactories(ApplicationContextFactory.class, getClass().getClassLoader())) {
// 4. action为ApplicationContextFactory::create,尝试根据Web应用类型标识创建Context对象
T result = action.apply(candidate, webApplicationType);
if (result != null) {
return result;
}
}
return (defaultResult != null) ? defaultResult.get() : null;
}
// 源码位置:org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext.Factory
public ConfigurableApplicationContext create(WebApplicationType webApplicationType) {
// 5. 如果webApplicationType类型为REACTIVE则创建AnnotationConfigReactiveWebServerApplicationContext,否则为null
return (webApplicationType != WebApplicationType.REACTIVE) ? null : new AnnotationConfigReactiveWebServerApplicationContext();
}
// 源码位置:org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext.Factory
public ConfigurableApplicationContext create(WebApplicationType webApplicationType) {
// 6. 如果webApplicationType类型为SERVLET则创建AnnotationConfigServletWebServerApplicationContext,否则为null
return (webApplicationType != WebApplicationType.SERVLET) ? null : new AnnotationConfigServletWebServerApplicationContext();
}
// 回到DefaultApplicationContextFactory的getFromSpringFactories()
// 源码位置:org.springframework.boot.DefaultApplicationContextFactory
private <T> T getFromSpringFactories(WebApplicationType webApplicationType, BiFunction<ApplicationContextFactory, WebApplicationType, T> action, Supplier<T> defaultResult) {
// 3. SpringFactoriesLoader.loadFactories()加载到工厂有两个,遍历工厂去调用工厂创建Context对象
for (ApplicationContextFactory candidate : SpringFactoriesLoader.loadFactories(ApplicationContextFactory.class, getClass().getClassLoader())) {
// 4. action为ApplicationContextFactory::create,尝试根据Web应用类型标识创建Context对象
T result = action.apply(candidate, webApplicationType);
// 7. AnnotationConfigReactiveWebApplicationContext.Factory只能创建webApplicationType=REACTIVE的Context对象,两者不匹配时result为AnnotationConfigReactiveWebServerApplicationContext对象
// AnnotationConfigServletWebServerApplicationContext.Factory只能创建webApplicationType=SERVLET的Context对象,两者匹配时result为AnnotationConfigServletWebServerApplicationContext对象
// 匹配到一个就返回,REACTIVE的工厂排在前面,优先级更高
if (result != null) {
return result;
}
}
// 8. 不是web相关的模式则使用默认的org.springframework.context.annotation.AnnotationConfigApplicationContext
// defaultResult为AnnotationConfigApplicationContext::new,defaultResult.get()就是执行new AnnotationConfigApplicationContext()的结果
return (defaultResult != null) ? defaultResult.get() : null;
}
3.4 初始化
在主流程4进行初始化refreshContext(context),这个方法名称起得不太表意,就当是刷新吧。
// 源码位置:org.springframework.boot.SpringApplication#refreshContext
private void refreshContext(ConfigurableApplicationContext context) {
if (this.registerShutdownHook) {
shutdownHook.registerApplicationContext(context);
}
// 1. 调私refresh()方法刷新
refresh(context);
}
protected void refresh(ConfigurableApplicationContext applicationContext) {
// 2. 调用context的refresh()刷新方法
// 从上面看这个context可能有三种,需分别大致看一下各个context的刷新:
// AnnotationConfigReactiveWebServerApplicationContext
// AnnotationConfigServletWebServerApplicationContext
// AnnotationConfigApplicationContext
applicationContext.refresh();
}
3.4.1 AnnotationConfigReactiveWebServerApplicationContext刷新
AnnotationConfigReactiveWebServerApplicationContext本身并没有refresh()刷新方法,刷新方法来自于父类:
// 源码位置:org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext
// 1. 继承关系:AnnotationConfigReactiveWebServerApplicationContext < ReactiveWebServerApplicationContext
< GenericReactiveWebApplicationContext < GenericApplicationContext < AbstractApplicationContext
public class ReactiveWebServerApplicationContext extends GenericReactiveWebApplicationContext implements ConfigurableWebServerApplicationContext {
public final void refresh() throws BeansException, IllegalStateException {
try {
// 2. 调用父类refresh()方法刷新,直接父类GenericReactiveWebApplicationContext、GenericApplicationContext没有重载refresh()方法,
// 调的是AbstractApplicationContext的refresh()方法
super.refresh();
}
catch (RuntimeException ex) {
WebServerManager serverManager = this.serverManager;
if (serverManager != null) {
serverManager.getWebServer().stop();
}
throw ex;
}
}
}
// 源码位置:org.springframework.context.support.AbstractApplicationContext
public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 省略部分代码
try {
postProcessBeanFactory(beanFactory);
StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
invokeBeanFactoryPostProcessors(beanFactory);
registerBeanPostProcessors(beanFactory);
beanPostProcess.end();
initMessageSource();
initApplicationEventMulticaster();
// 3. 不同子类有不同的的初始化,Web应用的体现就在此方法
onRefresh();
registerListeners();
finishBeanFactoryInitialization(beanFactory);
finishRefresh();
}
// 省略部分代码
}
}
}
protected void onRefresh() throws BeansException {
// 4. AbstractApplicationContext没有实现此方法,实际实现要回到子类当中
}
// 5. AnnotationConfigReactiveWebServerApplicationContext没有重载onRefresh()方法
// 在其父类ReactiveWebServerApplicationContext(为AbstractApplicationContext子类)重载了onRefresh()方法
// 源码位置:org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext
protected void onRefresh() {
super.onRefresh();
try {
// 6. 创建web server对象
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start reactive web server", ex);
}
}
private void createWebServer() {
WebServerManager serverManager = this.serverManager;
if (serverManager == null) {
StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");
String webServerFactoryBeanName = getWebServerFactoryBeanName();
ReactiveWebServerFactory webServerFactory = getWebServerFactory(webServerFactoryBeanName);
createWebServer.tag("factory", webServerFactory.getClass().toString());
boolean lazyInit = getBeanFactory().getBeanDefinition(webServerFactoryBeanName).isLazyInit();
// 7. 在此创建web server对象
this.serverManager = new WebServerManager(this, webServerFactory, this::getHttpHandler, lazyInit);
getBeanFactory().registerSingleton("webServerGracefulShutdown", new WebServerGracefulShutdownLifecycle(this.serverManager.getWebServer()));
getBeanFactory().registerSingleton("webServerStartStop", new WebServerStartStopLifecycle(this.serverManager));
createWebServer.end();
}
initPropertySources();
}
3.4.2 AnnotationConfigServletWebServerApplicationContext刷新
AnnotationConfigServletWebServerApplicationContext本身并没有refresh()刷新方法,刷新方法来自于父类,整个过程和AnnotationConfigReactiveWebServerApplicationContext的创建基本相似,只有最后用来创建Web Server的工厂ServletWebServerFactory不一样,创建出来的Web Server就不一样:
// 源码位置:org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext
// 1. 继承关系:AnnotationConfigServletWebServerApplicationContext < ServletWebServerApplicationContext < GenericApplicationContext < AbstractApplicationContext
public class ServletWebServerApplicationContext extends GenericWebApplicationContext implements ConfigurableWebServerApplicationContext {
public final void refresh() throws BeansException, IllegalStateException {
try {
// 2. 调用父类refresh()方法刷新,调的是AbstractApplicationContext的refresh()方法
super.refresh();
}
catch (RuntimeException ex) {
WebServer webServer = this.webServer;
if (webServer != null) {
webServer.stop();
}
throw ex;
}
}
}
// 源码位置:org.springframework.context.support.AbstractApplicationContext
public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 省略部分代码
try {
postProcessBeanFactory(beanFactory);
StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
invokeBeanFactoryPostProcessors(beanFactory);
registerBeanPostProcessors(beanFactory);
beanPostProcess.end();
initMessageSource();
initApplicationEventMulticaster();
// 3. 调用子类刷新
onRefresh();
registerListeners();
finishBeanFactoryInitialization(beanFactory);
finishRefresh();
}
// 省略部分代码
}
}
}
// 源码位置:org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext
protected void onRefresh() {
super.onRefresh();
try {
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");
ServletWebServerFactory factory = getWebServerFactory();
createWebServer.tag("factory", factory.getClass().toString());
// 4. 创建web server对象
this.webServer = factory.getWebServer(getSelfInitializer());
createWebServer.end();
getBeanFactory().registerSingleton("webServerGracefulShutdown", new WebServerGracefulShutdownLifecycle(this.webServer));
getBeanFactory().registerSingleton("webServerStartStop", new WebServerStartStopLifecycle(this, this.webServer));
}
else if (servletContext != null) {
try {
getSelfInitializer().onStartup(servletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context", ex);
}
}
initPropertySources();
}
3.4.3 AnnotationConfigApplicationContext刷新
AnnotationConfigApplicationContext本身没有refresh()方法,需要找到父类AbstractApplicationContext,最终会调onRefresh()方法,由于这几层类都没有重载该方法,所以此onRefresh()没有做什么,跟之前两个Context比,最大的区别在于没有创建Web Server。
// 源码位置:org.springframework.context.support.AbstractApplicationContext
// 1. 继承关系:AnnotationConfigApplicationContext < GenericApplicationContext < AbstractApplicationContext
public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 省略部分代码
try {
postProcessBeanFactory(beanFactory);
StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
invokeBeanFactoryPostProcessors(beanFactory);
registerBeanPostProcessors(beanFactory);
beanPostProcess.end();
initMessageSource();
initApplicationEventMulticaster();
// 2. 调用子类的onRefresh()刷新
onRefresh();
registerListeners();
finishBeanFactoryInitialization(beanFactory);
finishRefresh();
}
// 省略部分代码
}
}
}
protected void onRefresh() throws BeansException {
// 3. 由于子类GenericApplicationContext和AnnotationConfigApplicationContext都没有重载此方法,所以执行了空方法
}
3.5 执行Runner
在主流程步骤5执行runner:
// 源码位置:org.springframework.boot.SpringApplication
private void callRunners(ApplicationContext context, ApplicationArguments args) {
// 1. 用context.getBeanProvider()找所有实现了Runner接口的类,并遍历这些类
context.getBeanProvider(Runner.class).orderedStream().forEach((runner) -> {
// 2. 支持两种Runner:ApplicationRunner、CommandLineRunner,分别都执行
if (runner instanceof ApplicationRunner) {
callRunner((ApplicationRunner) runner, args);
}
if (runner instanceof CommandLineRunner) {
callRunner((CommandLineRunner) runner, args);
}
});
}
private void callRunner(ApplicationRunner runner, ApplicationArguments args) {
try {
// 3. 执行Runner,传的参数是ApplicationArguments
(runner).run(args);
}
catch (Exception ex) {
throw new IllegalStateException("Failed to execute ApplicationRunner", ex);
}
}
private void callRunner(CommandLineRunner runner, ApplicationArguments args) {
try {
// 4. 执行Runner,传的参数是原始数组类型(main方法的参数类型)
(runner).run(args.getSourceArgs());
}
catch (Exception ex) {
throw new IllegalStateException("Failed to execute CommandLineRunner", ex);
}
}
3.6 小结
概括地看,SpringApplication的启动就是根据导入的包情况,分三种情况创建不同的Context:响应式流web、普通web、非web,然后执行Context的refresh进行初始化,下图为Context体系的继承情况,对于响应式流web、普通web这两种Context,分别在子类实现了不同的onRefresh(),用来创建不同的web server。
最后,还执行了runner(如果有实现runner的话)。注意,runner的执行与context种类无关,也就是不管哪种context都会执行。如果没有引任何web相关的包,那么就不会有web server的执行,只执行了runner,就变成了一个普通的桌面程序。runner的继承情况如下,两种Runner的差别仅在于参数的类型:
注:上面并没有把启动流程的每个细节都进行解析,这算看源码的一个小技巧,先看自己关心的部分(或者重点部分),如这次只想了解Web程序和普通程序的区别,以及SpringBoot用什么方式来区分的。
5 架构一小步
依赖spring-boot-starter-web,开启Web应用模式。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.18</version>
<relativePath/>
</parent>
<groupId>com.qqian.stepfmk</groupId>
<artifactId>srvpro</artifactId>
<version>1.0.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>