avatar

08.SpringBoot中web嵌入式容器的启动原理

上一篇分析到SpringBoot中web嵌入式容器的配置方式和原理。今天我们分析下嵌入式web容器的启动原理。

一、什么时候创建嵌入式的Servlet容器工厂?

  1. 如果看了上一篇的分析,则对EmbeddedWebServerFactoryCustomizerAutoConfiguration 这个类有一点了解**(注意:SpringBoot2.0.x以下不是这个类,对应的类为EmbeddedServletContainerAutoConfiguration)**, 这个类会在SpringBoot启动时,根据当前环境的jar包,来判断具体生成哪个WebServerFactoryCustomizer,比如tomcat、jetty等。因为默认的是tomcat,所以我们在返回TomcatWebServerFactoryCustomizer处下断点。然后以调试模式运行application。下断点处源码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    @ConditionalOnClass({ Tomcat.class, UpgradeProtocol.class })
    public static class TomcatWebServerFactoryCustomizerConfiguration {
    @Bean
    public TomcatWebServerFactoryCustomizer tomcatWebServerFactoryCustomizer(
    Environment environment, ServerProperties serverProperties) {
    return new TomcatWebServerFactoryCustomizer(environment, serverProperties); //在此处下断点
    }
    }
  2. 断下来后,调用堆栈如下:

    接下来拿关键代码来分析下。

  3. SpringApplication类下的run方法。(…代表忽略无关代码)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    public ConfigurableApplicationContext run(String... args) {
    ....
    try {
    ....
    //创建上下文环境 有关代码在下面给出
    context = createApplicationContext();
    ....
    //更新Context。在这个方法里创建web容器并启动
    refreshContext(context);
    ....
    }
    catch (Throwable ex) {
    ....
    }
    ....
    return context;
    }

    //创建上下文环境
    protected ConfigurableApplicationContext createApplicationContext() {
    Class<?> contextClass = this.applicationContextClass;
    if (contextClass == null) {
    try {
    //根据应用类型来判断创建哪种上下文环境
    switch (this.webApplicationType) {
    case SERVLET:
    //如果是web型应用,则创建AnnotationConfigServletWebServerApplicationContext
    contextClass = Class.forName(DEFAULT_WEB_CONTEXT_CLASS);
    break;
    case REACTIVE:
    //如果是reactive类型,则创建AnnotationConfigReactiveWebServerApplicationContext
    contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
    break;
    default:
    //默认创建AnnotationConfigApplicationContext
    contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
    }
    }
    catch (ClassNotFoundException ex) {
    throw new IllegalStateException(
    "Unable create a default ApplicationContext, "
    + "please specify an ApplicationContextClass",
    ex);
    }
    }
    return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
    }
  4. 一路ctrl+左键,点击refreshContext() 来到AbstractApplicationContextrefreshContext方法,代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
    ....
    try {
    ....
    //初始化容器
    onRefresh();
    //注册监听器
    registerListeners();
    //初始化我们自己写的类,并加入到容器
    finishBeanFactoryInitialization(beanFactory);
    // Last step: publish corresponding event.
    finishRefresh();
    }catch (BeansException ex) {
    ...
    }finally {
    ...
    }
    }
    }

    因为onRefresh方法是一个抽象方法,所以我们需要找他的实现类ServletWebServerApplicationContext

  5. ServletWebServerApplicationContext中的onRefresh 方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    @Override
    protected void onRefresh() {
    super.onRefresh();
    try {
    //创建web容器,方法实现如下
    createWebServer();
    }
    catch (Throwable ex) {
    throw new ApplicationContextException("Unable to start web server", ex);
    }
    }

    //创建web容器
    private void createWebServer() {
    WebServer webServer = this.webServer;
    //获取Servlet上下文环境
    ServletContext servletContext = getServletContext();
    if (webServer == null && servletContext == null) {
    //获取WebServerFactory,调用这个方法就是去创建WebServerFactory,当创建webServerFactory时,就会触发WebServerFactoryCustomizerBeanPostProcessor这个后置处理器。获取我们的定制器,初始化配置。具体分析在下面
    ServletWebServerFactory factory = getWebServerFactory();
    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();
    }
  6. WebServerFactoryCustomizerBeanPostProcessor 这个后置处理器,在上一篇已经分析过了。在这里再次分析下。首先看代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName)throws BeansException {
    if (bean instanceof WebServerFactory) {
    //如果bean是WebServerFactory类型,就会去调用postProcessBeforeInitialization方法,刚好,我们创建的bean就是这个类型
    //postProcessBeforeInitialization()方法如下
    postProcessBeforeInitialization((WebServerFactory) bean);
    }
    return bean;
    }
    //postProcessBeforeInitialization方法
    @SuppressWarnings("unchecked")
    private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {
    //因为是使用lambda表达式,而我又不怎么熟悉。所以只能说个大概。如有错误,希望大家能够批评指正。
    //大致意识是,通过getCustomizers()获取所有的定制器,即会拿到TomcatWebServerFactoryCustomizer。然后调用他们的customize()进行属性设置。
    LambdaSafe
    .callbacks(WebServerFactoryCustomizer.class, getCustomizers(),webServerFactory)
    .withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)
    .invoke((customizer) -> customizer.customize(webServerFactory));
    }

    到此,容器已经创建,接下来我们看如何启动的。

二、嵌入式web容器在什么时候创建并启动?

上面已经分析了容器工厂的创建,下面说说是怎样通过容器工厂创建容器并启动的。接看上面的第5点中的createWebServer()方法,源码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
//创建web容器
private void createWebServer() {
WebServer webServer = this.webServer;
//获取Servlet上下文环境
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
//获取WebServerFactory
ServletWebServerFactory factory = getWebServerFactory();
//上面获取对应的WebServerFactory,调用getWebServer创建容器并启动,getWebServer()方法源码如下
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();
}

//getWebServer()方法,注意,是TomcatServletWebServerFactory类中的
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
Tomcat tomcat = new Tomcat(); //创建Tomcat容器
//设置路径
File baseDir = (this.baseDirectory != null ? this.baseDirectory : createTempDir("tomcat"));
tomcat.setBaseDir(baseDir.getAbsolutePath());
//对Tomcat的一些设置,包括端口之类的
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);
//在getTomcatWebServer()方法中启动tomcat容器
return getTomcatWebServer(tomcat);
}

//getTomcatWebServer() 方法如下:
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
return new TomcatWebServer(tomcat, getPort() >= 0);
}

public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
Assert.notNull(tomcat, "Tomcat Server must not be null");
this.tomcat = tomcat;
this.autoStart = autoStart;
initialize();
}

private void initialize() throws WebServerException {
TomcatWebServer.logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
synchronized (this.monitor) {
try {
...
//启动Tomcat容器
this.tomcat.start();
....
}catch (Exception ex) {
stopSilently();
throw new WebServerException("Unable to start embedded Tomcat", ex);
}
}
}

三、总结

  1. web应用启动时,调用SpringApplicationrun方法。
  2. 在run方法中,首先会根据应用类型创建对应的上下文环境,之后再调用refreshContext()更新上下文环境。
  3. 来到AbstractApplicationContextrefreshContext() 首先调用onRefresh初始化容器(在此之前还有其他的初始化调用,我们就不说了),然后再初始化注册监听器,然后再是把我们定义的类加入IOC容器中。
  4. 因为是web类型应用,所以我们会调用ServletWebServerApplicationContextonRefresh()
  5. onRefresh中调用createWebServer() 先获取对应的ServletWebServerFactory ,并通过factory.getWebServer() 创建容器,并启动。
  6. 在获取对应的ServletWebServerFactory 时会触发``WebServerFactoryCustomizerBeanPostProcessor 后置处理器,在里面获取所有的定制器,初始化容器的配置,比如端口号、项目路径等。
  7. 在调用getWebServer() 时, 创建tomcat容器,再调用getTomcatWebServer() 启动容器。
  8. 最终启动容器是创建TomcatWebServer这个类,并调用其初始化方法initialize() 启动服务器。
  9. 初始化监听器。
  10. 把我们自定义的类加入到IOC容器中。

评论