SpringBoot
的启动流程可以说是面试中常考的一个知识点,网上也有很多文章来讨论SpringBoot
的启动流程。进一步,其实对于SpringBoot
的启动的分析
,主要集中在SpringApplication
中的run
之中。或许你已经看了很多相关的分析文章,但看了这么多分析的文章你真的读懂run
方法了吗?换言之,如果让你来写一个方法来模拟run
方法背后的逻辑,你有思路吗?
如果有,不妨看一看你与笔者思路是否一致;如果没有也不要慌,因为笔者带你手写一个SpringBoot
的启动逻辑。
思考,输出,沉淀。用通俗的语言陈述技术,让自己和他人都有所收获。
作者:毅航😜
1前言
相信每一个Java
开发者对于SpringBoot
都不会陌生, SpringBoot
的出现,极大的简化了我们开发web
应用的难度。此外,SpringBoot
还具有如下优势:
-
简化配置:
Spring Boot
提供了默认配置,减少了开发人员需要进行手动配置的工作,从而提高了开发效率。 -
快速开发:
Spring Boot
提供了许多开箱即用的功能和模板,使开发人员能够快速构建应用程序,减少了样板代码的编写。 -
内嵌式服务器:
Spring Boot
支持内嵌式服务器(如Tomcat、Jetty
等),无需额外配置,可以轻松部署应用程序。 -
自动化配置:
Spring Boot
提供了自动配置机制,可以根据项目的依赖自动配置应用程序,减少了手动配置的繁琐工作。
一言以概之,SpringBoot
是基于Spring
开发的一种轻量级的全新框架,不仅继承了Spring
框架原有的优秀特性,而且还通过简化配置来进一步简化应用的搭建
和开发
过程。
接下来,我们会首先分析SpringBoot
框架支撑下应用的启动逻辑,接着将讨论该如何手写实现SpringBoot
的启动逻辑。
2启动逻辑之run
方法
在SpringBoot
框架下,通常会按照如下所示的逻辑来编写启动类:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
* SpringBoot应用启动类
* */
@SpringBootApplication
public class StartSpringBootApplication {
public static void main(String[] args) {
SpringApplication.run(StartSpringBootApplication.class, args);
}
}
-
提供一个
SpringBoot
的启动类StartSpringBootApplication
(注:启动类的名称可任意指定) -
在
启动类
上标注一个@SpringBootApplication
注解 -
编写一个
main
方法,调用SpringApplication
中的run
方法
接着,通过运行上述的main
方法就可完成一个SpringBoot
应用的启动。不难发现,启动一个SpringBoot
应用非常简单。
正如之前分析的那样,SpringBoot
应用启动的核心秘密都在于run
方法。由于本文重点不在分析run
方法逻辑,这里给出run
代码简单注释主要也是照顾那些之前不曾接触过run
方法的读者。
SpringAppliction # run()
public ConfigurableApplicationContext run(String... args) {
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
prepareContext(context, environment, listeners,
applicationArguments, printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
}
return context;
}
进一步,上述方法可总结为下图所示内容:
或许,你对上述run
内容已经如数家珍,甚至理解更在我之上。但回到我们开头的问题,如果不依赖SpringBoot
内部提供的run
方法我让你来仿照run
方法来写一个启动web
应用的逻辑你可以实现吗? 再具体一点,定义一个控制层,然后在启动类的main
方法模拟实现run
方法,最后,保证当访问控制层定义的url
时,可以访问到相应的逻辑。
3手写SpringBoot
启动逻辑
开始动手之前,不妨先来考虑下究竟需要哪些工具。首先,构建一个web
应用,那引入spring-mvc
相关依赖肯定是必然的。进一步,要在启动类中的main
方法完成Tomcat
的启动,那么此时我们就不能在通过外部Tomcat
进行应用部署,所以引入Tomcat
依赖也就是必然的。
明确了需要的依赖后,在开始动手编写代码之前,我们还需要简单介绍下SpringMVC
中的WebApplicationInitializer
接口。该接口是 Spring Framework
中的一个接口,主要用于配置和初始化 Web 应用程序的相关设置。主要用于代替传统的 web.xml
配置文件,以更灵活和程序化的方式配置 Servlet 3.0+
容器。
更进一步,WebApplicationInitializer
接口的主要作用包括:
-
替代 web.xml:通过实现该接口,您可以在 Java 代码中配置 Servlet、Filter、Listener 等 Web 组件,而不必使用传统的 XML 配置文件(web.xml)。这使得配置更加灵活和类型安全。
-
初始化 Spring Web 应用:您可以在
WebApplicationInitializer
实现类中配置 Spring 容器,包括加载应用程序上下文、注册Spring MVC
的DispatcherServlet
、设置数据源、添加过滤器、监听器等。
控制层相关定义
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/start")
public class StartController {
@GetMapping("/get")
public String startSpringBoot() {
return "Start SpringBoot ......";
}
}
上述控制层逻辑逻辑很简单,就是定义了一个StartController
,然后内部定义一个startSpringBoot
方法。方法的返回值为一个名为Start SpringBoot ......
的字符串,而方法对应的请求路径为/start/get
。
配置类
@Component
public class BootConfig implements
ApplicationContextAware, WebApplicationInitializer {
private static ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context = applicationContext;
}
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
ServletRegistration.Dynamic dynamic = servletContext.addServlet("dispatcherServlet",context.getBean(DispatcherServlet.class));
dynamic.setLoadOnStartup(1);
dynamic.addMapping("/");
}
上述配置类逻辑也很简单,就是将容器的DispatcherServlet
注册到Tomcat
容器中,从而将DispatcherServlet
交由Tomcat
容器管理。
启动类
@Configurable
@Import(value = DispatcherServlet.class)
@ComponentScan("com.start.web")
public class StartBootApplication {
public static void main(String[] args) throws ServletException, LifecycleException {
AnnotationConfigWebApplicationContext config = new AnnotationConfigWebApplicationContext();
config.register(StartBootApplication.class);
config.refresh();
Tomcat tomcat=new Tomcat();
tomcat.setPort(8080);
tomcat.addWebapp("/","D://mall");
tomcat.start();
tomcat.getServer().await();
}
}
最终效果如下:
(项目地址:gitee.com/ThanksCode/…[1]
4拆解"我们"的启动逻辑
看到上述实现效果,是不是觉得很酷?接下来,不妨让我们看看上述代码逻辑究竟完成了哪些逻辑,进一步,再具体分析这些逻辑与SpringBoot
的启动流程究竟有何关系。
其实,如果你细心看的话,你会发现,其实上述代码总共加起来不超过50
行。而核心逻辑基本全在启动类
中,那接下来让我们看看启动流究竟藏了那些秘密。
@Configurable
@Import(value = DispatcherServlet.class)
@ComponentScan("com.start.web")
public class StartBootApplication {
public static void main(String[] args) throws ServletException, LifecycleException {
AnnotationConfigWebApplicationContext config = new AnnotationConfigWebApplicationContext();
config.register(StartBootApplication.class);
config.refresh();
Tomcat tomcat=new Tomcat();
tomcat.setPort(8080);
tomcat.addWebapp("/","D://mall");
tomcat.start();
tomcat.getServer().await();
}
}
先看前三行的注解:
@configurable
@Import(value = DispatcherServlet.class)
@ComponentScan("com.start.web")
对于这样的注解风格,熟悉吗?当然熟悉啦,这些注解恰好就是@SpringBootApplication
的底层实现,那@SpringBootApplication
完成什么功能?当然是自动装配
啦!进一步,我们的代码主要完成两项功能:
-
将
DispatcherServlet
注入到的容器中,除此之外你也可以使用@Configuration
注解结合@Bean
的方式进行注入容器。 -
扫描路径下的
Controller
实现。
如果你对自动装配
理解的话,其实你会发现,我们本身就是在模拟实现自动装配
。那我们这样的做的目的是什么呢?当然是为了构建web
应用啦!
(注:不熟悉SpringMVC
的读者可参考笔者之前有关SPringMVC
的文章哦!传送门:`SpringMVC`流程分析[2])
接着,main
方法中主要完成了如下任务:
-
初始化
ioc
容器,然后刷新容器; -
构建
tomcat
实例,启动Tomcat
服务。
(注:将dispatcherServlet
放入到ioc
容器,并加载到tomcat
上下文中通过配置类BootConfig
完成。)
通过上述几行代码其实我们就手写了一个SPringBoot
的启动逻辑,或许你会疑惑,其他人长篇累牍的分析,到你这不到50
代码就实现了?你真的手写了一个SpringBoot
的run
方法吗?
我的回答是肯定的——当然不是!SpringBoot
的启动逻辑怎么可能通过不到50
行的代码就能实现呢!我们只不过抽取出来SpringBoot
启动逻辑中的核心部分:自动装配、容器刷新、服务器启动进行实现,换句话说,只要你明白了上述三块内容,其实你对于SpringBoot
的启动类逻辑其实已经掌握的很透彻啦~~。
5总结
事实上,网上有很多文章来专门分析SpringBoot
的启动逻辑并且都会都花费很长篇幅来进行介绍,但不知你是否考虑这样一个问题,那就是对于这样文章,读过后你真的能记住吗?换句话说,就算当下记住了,日后能保证不遗忘吗?遗忘,永远是我们学习道路上最大的绊脚石,这是我们必须面对的。 为此我们只能不断的重复,但我们不断的重复难道只是为了牢记SpringBoot
的启动逻辑,然后在面试中增加逼格吗?
当然笔者并不是反感这样的行为,因为笔者也是从这个时期走过来的,自然知道其中的辛酸。所以笔者更希望能换一种方式来分析SpringBooot
的启动逻辑,让你抓住重点
而不至于在众多的文章中逐渐迷失。这也是笔者坚持创作的动力之一,因为自己淋过雨,所以更想为他人撑伞!
最后,如果觉得笔者文章还可以,不妨点击一下关注啦!
参考资料
[1]
https://gitee.com/ThanksCode/start-web%EF%BC%89: https://link.juejin.cn/?target=https%3A%2F%2Fgitee.com%2FThanksCode%2Fstart-web%25EF%25BC%2589
[2]
https://juejin.cn/column/7256779530392256567: https://juejin.cn/column/7256779530392256567