SpringBoot的启动流程

转载:面试官:SpringBoot的启动流程熟悉吗?手写一个怎么样 - 掘金

SpringBoot的启动流程可以说是面试中常考的一个知识点,网上也有很多文章来讨论SpringBoot的启动流程。进一步,其实对于SpringBoot的启动的分析,主要集中在SpringApplication中的run之中。或许你已经看了很多相关的分析文章,但看了这么多分析的文章你真的读懂run方法了吗?换言之,如果让你来写一个方法来模拟run方法背后的逻辑,你有思路吗?

如果有,不妨看一看你与笔者思路是否一致;如果没有也不要慌,因为笔者带你手写一个SpringBoot的启动逻辑。


前言

相信每一个Java开发者对于SpringBoot都不会陌生, SpringBoot的出现,极大的简化了我们开发web应用的难度。此外,SpringBoot还具有如下优势:

  1. 简化配置Spring Boot 提供了默认配置,减少了开发人员需要进行手动配置的工作,从而提高了开发效率。
  2. 快速开发Spring Boot 提供了许多开箱即用的功能和模板,使开发人员能够快速构建应用程序,减少了样板代码的编写。
  3. 内嵌式服务器Spring Boot 支持内嵌式服务器(如Tomcat、Jetty等),无需额外配置,可以轻松部署应用程序。
  4. 自动化配置Spring Boot 提供了自动配置机制,可以根据项目的依赖自动配置应用程序,减少了手动配置的繁琐工作。

一言以概之,SpringBoot是基于Spring 开发的一种轻量级的全新框架,不仅继承了Spring框架原有的优秀特性,而且还通过简化配置来进一步简化应用的搭建开发过程。

接下来,我们会首先分析SpringBoot框架支撑下应用的启动逻辑,接着将讨论该如何手写实现SpringBoot的启动逻辑。

启动逻辑之run方法

SpringBoot框架下,通常会按照如下所示的逻辑来编写启动类:

 

java

复制代码

import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * SpringBoot应用启动类 * */ @SpringBootApplication public class StartSpringBootApplication { public static void main(String[] args) { // 启动SpringBoot SpringApplication.run(StartSpringBootApplication.class, args); } }

  1. 提供一个SpringBoot的启动类StartSpringBootApplication (注:启动类的名称可任意指定)
  2. 启动类上标注一个@SpringBootApplication注解
  3. 编写一个main方法,调用SpringApplication中的run方法

接着,通过运行上述的main方法就可完成一个SpringBoot应用的启动。不难发现,启动一个SpringBoot应用非常简单。

正如之前分析的那样,SpringBoot应用启动的核心秘密都在于run方法。由于本文重点不在分析run方法逻辑,这里给出run代码简单注释主要也是照顾那些之前不曾接触过run方法的读者。

SpringAppliction # run()

 

java

复制代码

public ConfigurableApplicationContext run(String... args) { // .......省略其他无关代码 listeners.starting(); try { // 构建一个应用参数解析器 ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); // 加载系统的属性配置信息 ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); // 用于控制是否忽略BeanInfo的配置 configureIgnoreBeanInfo(environment); // 打印banner信息 Banner printedBanner = printBanner(environment); // 创建一个容器,类型为ConfigurableApplicationContext context = createApplicationContext(); exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context); // 容器准备工作 (可暂时忽略) prepareContext(context, environment, listeners, // 解析传入参数信息 applicationArguments, printedBanner); // 容器刷新 (重点关注) refreshContext(context); afterRefresh(context, applicationArguments); } // .......省略其他无关代码 return context; }

进一步,上述方法可总结为下图所示内容:

image.png

或许,你对上述run内容已经如数家珍,甚至理解更在我之上。但回到我们开头的问题,如果不依赖SpringBoot内部提供的run方法我让你来仿照run方法来写一个启动web应用的逻辑你可以实现吗? 再具体一点,定义一个控制层,然后在启动类的main方法模拟实现run方法,最后,保证当访问控制层定义的url时,可以访问到相应的逻辑。

手写SpringBoot启动逻辑

开始动手之前,不妨先来考虑下究竟需要哪些工具。首先,构建一个web应用,那引入spring-mvc相关依赖肯定是必然的。进一步,要在启动类中的main方法完成Tomcat的启动,那么此时我们就不能在通过外部Tomcat进行应用部署,所以引入Tomcat依赖也就是必然的。

明确了需要的依赖后,在开始动手编写代码之前,我们还需要简单介绍下SpringMVC中的WebApplicationInitializer 接口。该接口是 Spring Framework 中的一个接口,主要用于配置和初始化 Web 应用程序的相关设置。主要用于代替传统的 web.xml 配置文件,以更灵活和程序化的方式配置 Servlet 3.0+ 容器。

更进一步,WebApplicationInitializer 接口的主要作用包括:

  1. 替代 web.xml:通过实现该接口,您可以在 Java 代码中配置 Servlet、Filter、Listener 等 Web 组件,而不必使用传统的 XML 配置文件(web.xml)。这使得配置更加灵活和类型安全。
  2. 初始化 Spring Web 应用:您可以在 WebApplicationInitializer 实现类中配置 Spring 容器,包括加载应用程序上下文、注册 Spring MVC DispatcherServlet、设置数据源、添加过滤器、监听器等。

控制层相关定义

 

java

复制代码

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

配置类

 

java

复制代码

@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容器管理。

启动类

 

java

复制代码

@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(); // 手动去注册一个bean config.register(StartBootApplication.class); // 刷新容器 config.refresh(); // 构建Tomcat容器 Tomcat tomcat=new Tomcat(); tomcat.setPort(8080); tomcat.addWebapp("/","D://mall"); tomcat.start(); //因为 tomcat.start();是非阻塞型的,所以要阻塞一下,避免服务停止。 tomcat.getServer().await(); } }

最终效果如下:

(项目地址:gitee.com/ThanksCode/…

拆解"我们"的启动逻辑

看到上述实现效果,是不是觉得很酷?接下来,不妨让我们看看上述代码逻辑究竟完成了哪些逻辑,进一步,再具体分析这些逻辑与SpringBoot的启动流程究竟有何关系。

其实,如果你细心看的话,你会发现,其实上述代码总共加起来不超过50行。而核心逻辑基本全在启动类中,那接下来让我们看看启动流究竟藏了那些秘密。

 

java

复制代码

@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(); // 手动去注册一个bean config.register(StartBootApplication.class); // 刷新容器 config.refresh(); // 构建Tomcat容器 Tomcat tomcat=new Tomcat(); tomcat.setPort(8080); tomcat.addWebapp("/","D://mall"); tomcat.start(); //因为 tomcat.start();是非阻塞型的,所以要阻塞一下,避免服务停止。 tomcat.getServer().await(); } }

先看前三行的注解:

 

java

复制代码

@configurable @Import(value = DispatcherServlet.class) @ComponentScan("com.start.web")

对于这样的注解风格,熟悉吗?当然熟悉啦,这些注解恰好就是@SpringBootApplication的底层实现,那@SpringBootApplication完成什么功能?当然是自动装配啦!进一步,我们的代码主要完成两项功能:

  1. DispatcherServlet注入到的容器中,除此之外你也可以使用@Configuration注解结合@Bean的方式进行注入容器。
  2. 扫描路径下的Controller实现。

如果你对自动装配理解的话,其实你会发现,我们本身就是在模拟实现自动装配。那我们这样的做的目的是什么呢?当然是为了构建web应用啦!

(注:不熟悉SpringMVC的读者可参考笔者之前有关SPringMVC的文章哦!传送门:SpringMVC流程分析)

接着,main方法中主要完成了如下任务:

  1. 初始化ioc容器,然后刷新容器;

  2. 构建tomcat实例,启动Tomcat服务。

(注:将dispatcherServlet放入到ioc容器,并加载到tomcat上下文中通过配置类BootConfig 完成。)

通过上述几行代码其实我们就手写了一个SPringBoot的启动逻辑,或许你会疑惑,其他人长篇累牍的分析,到你这不到50代码就实现了?你真的手写了一个SpringBootrun方法吗?

我的回答是肯定的——当然不是!SpringBoot的启动逻辑怎么可能通过不到50行的代码就能实现呢!我们只不过抽取出来SpringBoot启动逻辑中的核心部分:自动装配、容器刷新、服务器启动进行实现,换句话说,只要你明白了上述三块内容,其实你对于SpringBoot的启动类逻辑其实已经掌握的很透彻啦~~。

作者:毅航
链接:https://juejin.cn/post/7281486153416884239
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 28
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值