springboot启动流程源码分析(一)

springboot启动流程源码分析(一)

前言:springboot相信基本上所有的人都使用过,但是对于一些初学者可能只是知道如何使用,但是对于它实现的原理不太熟悉,今天跟大家一起去分析下它的启动源码。其实也是比较简单,相信通过这篇文章能对一些初学者有一些帮助,在学习这篇文章之前最好有spring的基础知识。

一、引入问题

今天我们是以web应用为例来分析springboot的启动原理,首先我们看如下的代码

@SpringBootApplication
public class HellobootApplication {
   

   public static void main(String[] args) {
   
      SpringApplication.run(HellobootApplication.class, args);
   }

}

我们知道当我们run这个main方法时,web应用程序就能启动。那么首先请大家思考如下问题

1、springboot如何和spring容器关联上
2、我们没有看到有tomcat容器,为什么能够支撑起web应用
3、当我们引用第三方的starter时,为什么会自动实例化一些类,我们并没有扫描到第三方的包,甚至我们对第三方的包的路径都不知道

带着上面三个问题,我们一起来看下springboot的启动的原理吧

二、源码分析(1)

首先分析上面第一个问题(springboot如何和spring容器关联上)

SpringApplication#run()

public static ConfigurableApplicationContext run(Class<?> primarySource,
      String... args) {
   
    //看这里
   return run(new Class<?>[] {
    primarySource }, args);
}

run()

public static ConfigurableApplicationContext run(Class<?>[] primarySources,
      String[] args) {
   
    //看这个run方法
   return new SpringApplication(primarySources).run(args);
}

继续记者往下看

run()

public ConfigurableApplicationContext run(String... args) {

StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {

ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
//从这个方法名字看,大概就能猜测出来意思了,这里就是创建spring的context
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] {
 ConfigurableApplicationContext.class }, context);
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
//这个方法名字,看起来是不是很熟悉
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {

new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {

handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {

listeners.running(context);
}
catch (Throwable ex) {

handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}

这个方法,我们重点看两个地方

(1)createApplicationContext()

(2)refreshContext(context)

如果对spring源码有了解的话,我们大概能猜到这两个方法的意思,第一个时创建spring上下文,第二个就是刷新上下文,这两个地方其实就实现了springboot–>spring容器

比如我们单元测试的时候,经常编写如下代码进行启动spring容器

ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("xxx.xml")

1、我们先看createApplicationContext()方法

protected ConfigurableApplicationContext createApplicationContext() {

Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {

try {

switch (this.webApplicationType) {

case SERVLET:
//重点看加载的这个class的路径
contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
break;
case REACTIVE:
contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
break;
default:
contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
}
}
catch (ClassNotFoundException ex) {

throw new IllegalStateException(
"Unable create a default ApplicationContext, "
+ "please specify an ApplicationContextClass",
ex);
}
}
//后面就是通过反射创建ApplicationContext上下文了,后面不再继续跟了
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}

这里我们重点看一下加载的是哪个ApplicationContext的上下文,因为ApplicationContext有很多子类,我们看下常量(DEFAULT_SERVLET_WEB_CONTEXT_CLASS)对应的class是哪个

/** * The class name of application context that will be used by default for web * environments. */
public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = "org.springframework.boot."
+ "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";

所以这里创建的ApplicationContext的上下文是AnnotationConfigServletWebServerApplicationContext,这里很重要,大家先记一下(不同的ApplicationContext有些方法实现的细节是不一样的)

2、接着看上面的refreshContext(context)

private void refreshContext(ConfigurableApplicationContext context) {

//继续看该方法
refresh(context);
if (this.registerShutdownHook) {

try {

context.registerShutdownHook();
}
catch (AccessControlException ex) {

// Not allowed in some environments.
}
}
}

refresh()

protected void refresh(ApplicationContext applicationContext) {

Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
//这里就调用了spring的context包中的容器刷新了
((AbstractApplicationContext) applicationContext).refresh();
}

分析到这里,相信大家都知道springboot如何和spring容器相关联的了

三、源码分析(2)

上面介绍了springboot如何与spring容器进行关联的,接着我们看web应用时,我们没有将应用放入tomcat中时,为何能实现web应用

上面有一个地方,我让大家重点记住创建ApplicationContext时,到底是创建了哪个ApplicationContext的子类,其实是创建了AnnotationConfigServletWebServerApplicationContext那么我们来看下ApplicationContext.refresh()方法

AbstractApplicationContext.refresh()

public void refresh() throws BeansException, IllegalStateException {

synchronized (this.startupShutdownMonitor) {

// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {

// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
//咱们重点看这里,上面的英文注释写的也很详细,可以在子类中实例化其他bean
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {

if (logger.isWarnEnabled()) {

logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {

// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}

这个方法我相信对spring源码有了解的人都非常熟悉这个方法,这个方法就是spring代码的入口,非常重要。这里其实是实现了模板方法设计模式,这里是定义了基本的骨架,有些方法可以在不同的子类有不同的实现,我们上面看到创建的springboot的web应用程序默认创建的是AnnotationConfigServletWebServerApplicationContext,我们来看下它的onRefresh()方法(在他的父类ServletWebServerApplicationContext中)

ServletWebServerApplicationContext.onRefresh()

@Override
protected void onRefresh() {

super.onRefresh();
try {

//重点看这里
createWebServer();
}
catch (Throwable ex) {

throw new ApplicationContextException("Unable to start web server", ex);
}
}

咱们重点看createWebServer(),这里从方法名就能看出创建web服务

private void createWebServer() {

WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {

ServletWebServerFactory factory = getWebServerFactory();
//创建web服务在这里,这里是工厂方法
this.webServer = factory.getWebServer(getSelfInitializer());
}
else if (servletContext != null) {

try {

getSelfInitializer().onStartup(servletContext);
}
catch (ServletException ex) {

throw new ApplicationContextException("Cannot initialize servlet context",
ex);
}
}
initPropertySources();
}

在这里插入图片描述

上面是工厂方法,有Jetty和tomcat,我们这里点tomcat进去看看

TomcatServletWebServerFactory.getWebServer()

@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {

Tomcat tomcat = new Tomcat();
File baseDir = (this.baseDirectory != null) ? this.baseDirectory
: createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
Connector connector = new Connector(this.protocol);
tomcat.getService().addConnector(connector);
customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
configureEngine(tomcat.getEngine());
for (Connector additionalConnector : this.additionalTomcatConnectors) {

tomcat.getService().addConnector(additionalConnector);
}
prepareContext(tomcat.getHost(), initializers);
return getTomcatWebServer(tomcat);
}

这里相信大家就很清晰了,这里自己创建了一个Tomcat,最后会调用tomcat.start()这里我就不继续往下跟了,感兴趣的可以继续往下看看。另外如果感兴趣的同学可以看看这里的Tomcat的设置和我们下载的tomcat中的server.xml文件进行对比一下,看看有什么相似的地方

这篇文章就先分析到这里吧,还留了一个最后一个问题(如何自动加载第三方的starter),我们到下一篇文章再详细的分析

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/111213.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • anaconda和pycharm安装哪个版本好_pycharm专业版激活成功教程安装教程

    anaconda和pycharm安装哪个版本好_pycharm专业版激活成功教程安装教程文章目录Pycharm中嵌入AnacondaAnaconda下载Pycharm下载Anaconda安装Pycharm安装将Anaconda配置到Pycharm中添加一个python文件到工程Pycharm中嵌入AnacondaAnaconda下载关于这两个软件的介绍,相信不用我多说,大家都知道,Pycharm是一款很好用的Python的IDE支持很多牛逼的骚操作,而Anaconda则是一款集…

  • EJB学习

    EJB学习EJB:企业级JavaBean(EnterpriseJavaBean, EJB)是一个用来构筑企业级应用的服务器端可被管理组件。EJB主要有三种Bean:SessionBeans:&

  • camera密码错误_camera filter

    camera密码错误_camera filter01flicker(banding)现象出现flicker(banding)问题时,从视频上看会发现有规律的明暗相间的条纹,这种现象也叫做牛顿环。如下图所示。02产生flicker(ban…

    2022年10月13日
  • C语言-函数-递归数列

    C语言-函数-递归数列题目:递归数列类别函数与递归程序类型:代码片段时间限制:2S内存限制10000Kb问题描述一个数列A定义如下A(1)=1,A(2)=1/(1+A(1)),A(3)=1/(1+A(2)),……A(n)=1/(1+A(n-1))。定义一个函数function用来计算数列的第第n项的值,函数声明如下:doublefunction(intn);输入说明:输入为1个…

  • 内网IP段有哪些_为什么有些内网使用公网地址段

    内网IP段有哪些_为什么有些内网使用公网地址段常见的内网IP段有:10.0.0.0/810.0.0.0-10.255.255.255172.16.0.0/12172.16.0.0-172.31.255.255192.168.0.0/16192.168.0.0-192.168.255.255以上三个网段分别属于A、B、C三类IP地址,来自《RFC1918》。但是根据《ReservedIPaddresses-Wikipedia,thefreeencyclopedia》及《RFC6890-Special

  • st7789 旋转_ESP32驱动ST7789液晶屏

    让你的ESP32点亮一块ST7789液晶屏吧hello-world这块液晶屏尺寸是1.14寸,分辨率为135×240,驱动是ST7789。(不小心多买了一个并口版本,因为串口方式连接就能满足我的需求,所以并口屏幕吃灰预定了)序简单下介绍点亮这块屏幕的方法,介绍下如何配置参数并正确的显示内容。下载驱动库我使用的驱动库为TFT_eSPI接线如下:ESP32引脚名称液晶屏引脚名称3V3VCCGNDGND…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号