springboot启动流程源码分析

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

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

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

  • springboot框架启动关键流程

    • 内嵌支持哪些web容器

    • 如何实现web容器的启动

    • 如何在不同的web容器之间实现切换

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

1.springboot框架核心能力

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

  • 统一依赖管理

  • 自动装配

  • 健康检查

  • cli

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

  • 平常开发,我们只需要引入spring-boot-starter-xxx,即可正常使用xxx提供的能力,不用再因为依赖包之间的兼容性,而花费大量的时间去调试兼容性问题了。这就是springboot统一依赖管理后,带来的直接收益!

  • 相信使用spring框架,从xml配置时代过来的小伙伴,在使用springboot框架后,神清气爽了很多!毕竟每每想到大量的xml配置文件内容,心有余悸!这就是springboot自动装配后,带来的直接收益!

  • 原来我们做应用监控,怎么做呢?需要在业务层面去开发一些监控接口,反正是要写代码!现在springboot直接提供了actuator,业务健康检查,是那么so easy的事情!甚至还可以扩展HealthIndicator接口,轻松就能实现新的指标监控检查。这就是springboot提供的actuator,带来的直接收益!

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

2.springboot框架启动流程

2.1.内嵌支持的web容器

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

  • 引入spring-boot-starter-web依赖

  • 在application.yml文件中,增加配置与web容器相关的一些内容,比如端口、contextpath、静态资源等

  • 开发springmvc相关的controller,开放端点

  • 打成一个jar包

  • 通过java -jar xxx.jar启动应用,然后即可以通过浏览器访问应用

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

public interface WebServer {
    // 启动容器
    void start() throws WebServerException;
    // 停止容器
    void stop() throws WebServerException;

    int getPort();

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

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

2.2.web容器启动流程

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

 

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

启动入口:FollowMeSpringbootActuatorApplication

@SpringBootApplication
public class FollowMeSpringbootActuatorApplication {

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

}

源码解析

  • 对于springboot应用,分析它的源码,我们只需要从启动类开始即可

  • 进入SpringApplication内部,便可一窥究竟!源码之下无秘密!

 

进入方法:SpringApplication.run

// 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

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

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

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

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

进入方法:SpringApplication.refreshContext/refresh

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

 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

protected void onRefresh() {
        super.onRefresh();

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

进入方法:ServletWebServerApplicationContext.createWebServer

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依赖,并展开它,一直找到包

org.springframework.boot.autoconfigure.web.embedded

并找到类

@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应用的启动流程了。源码有点多,如有不适,请多看几遍!

2.3.不同web容器之间切换

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

  • 首先在pom.xml文件中,排除tomcat依赖

  • 加入其它web容器的依赖即可

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章