springboot框架启动流程二(源码分析)

1.引言

在工作中,虽然我们都在基于springboot框架开发项目,还是发现很多小伙伴熟悉业务开发,但是对框架的一些底层机制原理不够了解。因此有了这篇文章的分享。

本文我将从springboot启动入口开始,逐步跟踪源码,逐步分析,力求让大家通过本文能够了解springboot的整个启动流程,主要有以下几个关注点

  • springboot框架提供了哪些核心能力

  • springboot框架启动关键流程

    • 内嵌支持哪些web容器
    • 如何实现web容器的启动
    • 如何在不同的web容器之间实现切换

前方高能预警,需要翻阅大量源代码,请做好心理准备!

2.springboot框架核心能力

大体上来看,springboot框架提供了四方面核心能力

  • 统一依赖管理
  • 自动装配
  • 健康检查
  • cli

在日常开发中,我们直接受益的有统一依赖管理、自动装配、健康检查。想象一下

  • 平常开发,我们只需要引入spring-boot-starter-xxx,即可正常使用xxx提供的能力,不用再因为依赖包之间的兼容性,而花费大量的时间去调试兼容性问题了。这就是springboot统一依赖管理后,带来的直接收益!
  • 相信使用spring框架,从xml配置时代过来的小伙伴,在使用springboot框架后,神清气爽了很多!毕竟每每想到大量的xml配置文件内容,心有余悸!这就是springboot自动装配后,带来的直接收益!
  • 原来我们做应用监控,怎么做呢?需要在业务层面去开发一些监控接口,反正是要写代码!现在springboot直接提供了actuator,业务健康检查,是那么so easy的事情!甚至还可以扩展HealthIndicator接口,轻松就能实现新的指标监控检查。这就是springboot提供的actuator,带来的直接收益!

相比统一依赖管理、自动装配、健康检查,cli相对业界用的比较少。

3.springboot框架启动流程

3.1.内嵌支持的web容器

使用springboot框架以后,最直观的感受是,哪怕开发web应用,只需要

  • 引入spring-boot-starter-web依赖
  • 在application.yml文件中,增加配置与web容器相关的一些内容,比如端口、contextpath、静态资源等
  • 开发springmvc相关的controller,开放端点
  • 打成一个jar包
  • 通过java -jar xxx.jar启动应用,然后即可以通过浏览器访问应用

就是这么简单,稍后我们在启动流程中详细分析,web容器是如何启动的,先看一看,springboot都内嵌了哪些web容器。为了支持web容器,springboot提供了一个接口

java 复制代码
public interface WebServer {
    // 启动容器
    void start() throws WebServerException;
    // 停止容器
    void stop() throws WebServerException;

    int getPort();

    default void shutDownGracefully(GracefulShutdownCallback callback) {
        callback.shutdownComplete(GracefulShutdownResult.IMMEDIATE);
    }
}

并为WebServer接口,提供了如下实现:

3.2.web容器启动流程

默认情况下,即直接引入spring-boot-starter-web依赖,springboot启动应用的是tomcat容器

接下来,我们就以springboot启动tomcat容器为例,来尝试启动流程源码分析。 启动入口:FollowMeSpringbootActuatorApplication

java 复制代码
@SpringBootApplication
public class FollowMeSpringbootActuatorApplication {

	public static void main(String[] args) {
		
 //springboot应用启动入口, SpringApplication.run()方法      SpringApplication.run(FollowMeSpringbootActuatorApplication.class, args);
	}

}

源码解析

  • 对于springboot应用,分析它的源码,我们只需要从启动类开始即可
  • 进入SpringApplication内部,便可一窥究竟!源码之下无秘密!

进入方法:SpringApplication.run

java 复制代码
// 1.从启动类main方法,进入run方法
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
    	// 调用了重载的run方法
        return run(new Class[]{primarySource}, args);
    }

// 2.重载的run方法
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
        // new 了一个SpringApplication实例,并再次调用重载run方法
        return (new SpringApplication(primarySources)).run(args);
    }

// 3.真正启动spring应用的地方,返回ioc容器:ConfigurableApplicationContext
public ConfigurableApplicationContext run(String... args) {
        ......省略非关键代码......
        try {
            ......省略非关键代码......
            // 关键代码:创建spring应用ioc容器
            context = this.createApplicationContext();
    		......省略非关键代码......
            // 关键代码:刷新ioc容器
            this.refreshContext(context);
            ......省略非关键代码......
        } catch (Throwable var10) {
            ......省略非关键代码......
        }

        ......省略非关键代码......
 }

源码解析

  • 在SpringApplication内部,有多个重载的run方法

  • 我们需要一直跟踪到方法:run(String... args)

  • 在该方法内部,有两行关键代码需要我们关注

    • context = this.createApplicationContext():创建spring ioc容器
    • this.refreshContext(context):刷新ioc容器,启动web容器具体内容的入口就是这里

进入方法:createApplicationContext

java 复制代码
protected ConfigurableApplicationContext createApplicationContext() {
        Class<?> contextClass = this.applicationContextClass;
        if (contextClass == null) {
            try {
                // 重点关注代码:根据webApplicationType应用类型
                // 决定创建ioc容器的具体实现,取值有三类
                // SERVLET:创建web容器AnnotationConfigServletWebServerApplicationContext
                // REACTIVE:创建响应式web容器AnnotationConfigReactiveWebServerApplicationContext
                // 默认:创建普通jar应用容器AnnotationConfigApplicationContext  
                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);
    }

源码解析

  • 该方法,是一个关键方法,它决定了启动应用最终拿到的ApplicationContext具体实现

  • 有三种情况,根据webApplicationType取值

    • SERVLET:AnnotationConfigServletWebServerApplicationContext
    • REACTIVE:AnnotationConfigReactiveWebServerApplicationContext
    • 默认:AnnotationConfigApplicationContext
  • 第三个我们很熟悉,在还没有springboot框架以前,通过注解方式配置ioc的ApplicationContext就是它

  • 我们重点关注的应该是:AnnotationConfigServletWebServerApplicationContext,注意类名称中,有ServletWebServer关键字。有点意思了,跟web容器挂上钩了!

  • 但是最关键的地方是,webApplicationType的取值,从根据什么来的呢?这才是重点!

枚举类:WebApplicationType

java 复制代码
public enum WebApplicationType {
    NONE,
    SERVLET,
    REACTIVE;

    // 相关ioc容器ApplicationContext标识类常量定义,用于决定启动时,加载具体的ApplicationContext实现
    private static final String[] SERVLET_INDICATOR_CLASSES = new String[]{"javax.servlet.Servlet", "org.springframework.web.context.ConfigurableWebApplicationContext"};
    private static final String WEBMVC_INDICATOR_CLASS = "org.springframework.web.servlet.DispatcherServlet";
    private static final String WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler";
    private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";
    private static final String SERVLET_APPLICATION_CONTEXT_CLASS = "org.springframework.web.context.WebApplicationContext";
    private static final String REACTIVE_APPLICATION_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext";

    ......省略非关键代码......

    // 关键方法:从classpath下,决定加载ApplicationContext具体实现
    static WebApplicationType deduceFromClasspath() {
        // 响应式【暂时不关注它,不关注这个if判断】
        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 {
            // 重点关注这里:
            // 如果类路径classpath下,存在Servlet、存在ConfigurableWebApplicationContext,那么说明该应用是web应用
            // 返回 SERVLET
            String[] var0 = SERVLET_INDICATOR_CLASSES;
            int var1 = var0.length;

            for(int var2 = 0; var2 < var1; ++var2) {
                String className = var0[var2];
                if (!ClassUtils.isPresent(className, (ClassLoader)null)) {
                    return NONE;
                }
            }

            return SERVLET;
        }
    }

   ......省略非关键代码......
}

源码解析

  • WebApplicationType枚举类,用于从classpath下,是否存在Servlet、存在ConfigurableWebApplicationContext

  • 来决定启动普通的spring应用,还是启动web应用

  • 根据引入spring-boot-starter-web依赖以后

    • 会自动引入内嵌的tomcat,即存在Servlet
    • 会自动引入spring-web,即存在ConfigurableWebApplicationContext
    • 因此最终返回 SERVLET

类:AnnotationConfigServletWebServerApplicationContext

源码解析

  • 如上图所示,它的类层次结构说明一切!

小结:到此拿到容器(AnnotationConfigServletWebServerApplicationContext),它明确告诉我们了,这是一个web应用上下文容器。继续往下,看是如何加载创建webServer

此时,我们需要关注另外一行关键代码了,你还记得它吗?

java 复制代码
// 关键代码:刷新ioc容器
this.refreshContext(context);

进入方法:SpringApplication.refreshContext/refresh

java 复制代码
private void refreshContext(ConfigurableApplicationContext context) {
    // 进入resresh方法
    this.refresh((ApplicationContext)context);
    if (this.registerShutdownHook) {
        try {
            context.registerShutdownHook();
        } catch (AccessControlException var3) {
            ;
        }
 }
    
/** @deprecated */
@Deprecated
protected void refresh(ApplicationContext applicationContext) {
        Assert.isInstanceOf(ConfigurableApplicationContext.class, applicationContext);
      // 继续进入refresh
      this.refresh((ConfigurableApplicationContext)applicationContext);
}

protected void refresh(ConfigurableApplicationContext applicationContext) {
       // 关键代码:最终调用了ApplicationContext的refresh方法
        applicationContext.refresh();
}

源码解析

  • 通过一路源码跟踪,最终发现代码 applicationContext.refresh();
  • 即实现了ApplicationContext的刷新操作,而我们从上面知道,此时的applicationContext,它是ServletWebServerApplicationContext

进入方法:ServletWebServerApplicationContext.refresh

java 复制代码
 public final void refresh() throws BeansException, IllegalStateException {
     try {
         // 继续调用父类refresh方法
         super.refresh();
     } catch (RuntimeException var3) {
         WebServer webServer = this.webServer;
         if (webServer != null) {
             webServer.stop();
         }

      throw var3;
	}
 }

//抽闲父类:AbstractApplicationContext#refresh
public void refresh() throws BeansException, IllegalStateException {
    ......省略非关键代码......

        try {
            ......省略非关键代码......
            // 关键代码:调用onRefresh方法
            this.onRefresh();
            ......省略非关键代码......
        } catch (BeansException var9) {
            ......省略非关键代码......
            throw var9;
        } finally {
            this.resetCommonCaches();
        }

    }
}

// onRefresh方法,在父类中是一个空方法,即钩子方法
// 该方法的具体实现,留给子类实现
// 此时的具体实现子类是:ServletWebServerApplicationContext
protected void onRefresh() throws BeansException {
}

源码解析

  • 一路追踪refresh方法,最终跟到了onRefresh方法
  • 而onRefresh方法,在父类中是空实现,具体实现在子类
  • 此时的子类是:ServletWebServerApplicationContext

进入方法:ServletWebServerApplicationContext.onRefresh

java 复制代码
protected void onRefresh() {
        super.onRefresh();

        try {
            // 关键代码:创建WebServer,高兴!快要柳暗花明了!
            this.createWebServer();
        } catch (Throwable var2) {
            throw new ApplicationContextException("Unable to start web server", var2);
        }
    }

进入方法:ServletWebServerApplicationContext.createWebServer

java 复制代码
private void createWebServer() {
    WebServer webServer = this.webServer;
    ServletContext servletContext = this.getServletContext();
    if (webServer == null && servletContext == null) {
        // 关键代码:获取ServletWebServer工厂,该工厂用于创建WebServer
        ServletWebServerFactory factory = this.getWebServerFactory();
        this.webServer = factory.getWebServer(new ServletContextInitializer[]{this.getSelfInitializer()});
        ......省略非关键代码......
    } else if (servletContext != null) {
        ......省略非关键代码......
    }

    ......省略非关键代码......
}

 protected ServletWebServerFactory getWebServerFactory() {
        // 关键代码:从classpath中,检查加载具体的webServer:tomcat/jetty/netty/undertow
        String[] beanNames = this.getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
        if (beanNames.length == 0) {
           ......省略非关键代码......
        } else if (beanNames.length > 1) {
           ......省略非关键代码......
        } else {
            return (ServletWebServerFactory)this.getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
        }
    }

源码解析

  • 到此,我们看到最终需要获取ServletWebServerFactory工厂,该工厂用于创建具体的ServletWebServer实例
  • 即具体创建的web容器是:tomcat、或者jetty、或者netty、还是undertow,需要检查classpath类路径下的依赖来决定
  • 即具体的web容器依赖来决定,存在谁,那么加载的就是谁

小结:到此,我们得到了这么几个信息

  • 首先,已经明确这是一个web应用,获取到的ApplicationContext是ServletWebServerApplicationContext
  • 其次,启动web应用,需要创建一个WebServer,该WebServer由具体的工厂来创建,该工厂是ServletWebServerFactory
  • 最终,在ServletWebServerFactory工厂中,需要创建哪个具体的web容器(tomcat/jetty/netty/undertow),由类路径classpath依赖决定

最后的谜底,我们需要回到最开始介绍springboot提供的核心能力的知识点了,其中有一条是说自动装配

最后来看一下,最终获取的ServletWebServerFactory,到底是谁?

找到spring-boot-autoconfigure依赖,并展开它,一直找到包

properties 复制代码
org.springframework.boot.autoconfigure.web.embedded

并找到类

java 复制代码
@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnWebApplication
@EnableConfigurationProperties({ServerProperties.class})
public class EmbeddedWebServerFactoryCustomizerAutoConfiguration {
    public EmbeddedWebServerFactoryCustomizerAutoConfiguration() {
    }

    @Configuration(
        proxyBeanMethods = false
    )
    // 如果classpath下存在HttpServer类,那么启用NettyWebServer
    @ConditionalOnClass({HttpServer.class})
    public static class NettyWebServerFactoryCustomizerConfiguration {
        public NettyWebServerFactoryCustomizerConfiguration() {
        }

        @Bean
        public NettyWebServerFactoryCustomizer nettyWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {
            return new NettyWebServerFactoryCustomizer(environment, serverProperties);
        }
    }

    @Configuration(
        proxyBeanMethods = false
    )
    // 如果classpath下存在Undertow,那么启用UndertowWebServer
    @ConditionalOnClass({Undertow.class, SslClientAuthMode.class})
    public static class UndertowWebServerFactoryCustomizerConfiguration {
        public UndertowWebServerFactoryCustomizerConfiguration() {
        }

        @Bean
        public UndertowWebServerFactoryCustomizer undertowWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {
            return new UndertowWebServerFactoryCustomizer(environment, serverProperties);
        }
    }

    @Configuration(
        proxyBeanMethods = false
    )
    // 如果classpath下,存在Server类,那么启用JettyWebServer
    @ConditionalOnClass({Server.class, Loader.class, WebAppContext.class})
    public static class JettyWebServerFactoryCustomizerConfiguration {
        public JettyWebServerFactoryCustomizerConfiguration() {
        }

        @Bean
        public JettyWebServerFactoryCustomizer jettyWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {
            return new JettyWebServerFactoryCustomizer(environment, serverProperties);
        }
    }

    @Configuration(
        proxyBeanMethods = false
    )
    // 如果classpath下存在Tomcat类,那么启用TomcatWebServer
    @ConditionalOnClass({Tomcat.class, UpgradeProtocol.class})
    public static class TomcatWebServerFactoryCustomizerConfiguration {
        public TomcatWebServerFactoryCustomizerConfiguration() {
        }

        @Bean
        public TomcatWebServerFactoryCustomizer tomcatWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {
            return new TomcatWebServerFactoryCustomizer(environment, serverProperties);
        }
    }
}

源码解析

  • EmbeddedWebServerFactoryCustomizerAutoConfiguration,是一个自动装配类

  • 它根据classpath下,是否存在HttpServer、Undertow、Server、Tomcat类,来决定启用哪个web容器

    • HttpServer--->Netty
    • Undertow--->Undertow
    • Server--->Jetty
    • Tomcat--->Tomcat
  • 在我们当前应用中,classpath下存在的是Tomcat,因此启动的是Tomcat web容器

相信到这里,你可以理解springboot应用中,整个web应用的启动流程了。源码有点多,如有不适,请多看几遍!

3.3.不同web容器之间切换

从前面的内容,我们理清了springboot是如何启动web应用的,它启用不同的web容器关键点是,看classpath下依赖了谁?因此要想在不同的web容器之间切换,实现就非常简单,分两个步骤

  • 首先在pom.xml文件中,排除tomcat依赖
  • 加入其它web容器的依赖即可
相关推荐
森焱森4 小时前
水下航行器外形分类详解
c语言·单片机·算法·架构·无人机
Albert Edison4 小时前
【最新版】IntelliJ IDEA 2025 创建 SpringBoot 项目
java·spring boot·intellij-idea
Piper蛋窝5 小时前
深入 Go 语言垃圾回收:从原理到内建类型 Slice、Map 的陷阱以及为何需要 strings.Builder
后端·go
六毛的毛8 小时前
Springboot开发常见注解一览
java·spring boot·后端
AntBlack8 小时前
拖了五个月 ,不当韭菜体验版算是正式发布了
前端·后端·python
31535669138 小时前
一个简单的脚本,让pdf开启夜间模式
前端·后端
uzong8 小时前
curl案例讲解
后端
开开心心就好9 小时前
免费PDF处理软件,支持多种操作
运维·服务器·前端·spring boot·智能手机·pdf·电脑
一只叫煤球的猫9 小时前
真实事故复盘:Redis分布式锁居然失效了?公司十年老程序员踩的坑
java·redis·后端
猴哥源码9 小时前
基于Java+SpringBoot的农事管理系统
java·spring boot