美文网首页
SpringBoot加载web项目流程源码解析

SpringBoot加载web项目流程源码解析

作者: Joshua1919 | 来源:发表于2020-07-20 09:34 被阅读0次

​上一篇文章中我们写了一个极简版的SpringBoot,基本完成了Controller的映射和访问,真实的SpringBoot是如何来做的?我们以2.3.0.RELEASE版本的SpringBoot为例来看下它是如何一步一步创建内嵌的tomcat并且注册DispatcherServlet的,它是否跟上篇文章中讲的一样,也是利用了Servlet3.0的ServletContainerInitializer和WebApplicationInitializer呢? 友情提示:公众号的排版对源码的文章非常不友好,建议直接上IDEA。
首先从SpringApplication#run方法开始:

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

继续往下走,一直到:

public ConfigurableApplicationContext run(String... args) {
    …
    try {
        …
        //这里会创建AnnotationConfigServletWebServerApplicationContext
        context = createApplicationContext();
        //重点看下这里,其实里面是调用的context.refresh();
        refreshContext(context);
        …
    }
    …
    return context;
}

SpringApplication#refresh:

protected void refresh(ConfigurableApplicationContext applicationContext) {
    //这个就是前面创建出来的AnnotationConfigServletWebServerApplicationContext
    applicationContext.refresh();
}

这个refresh就是spring核心的那个refresh方法,因为AnnotationConfigServletWebServerApplicationContext继承了ServletWebServerApplicationContext,因此,会调用到ServletWebServerApplicationContext#refresh:

@Override
public final void refresh() throws BeansException, IllegalStateException {
    try {
        super.refresh();
    }
    catch (RuntimeException ex) {
        WebServer webServer = this.webServer;
        if (webServer != null) {
            webServer.stop();
        }
        throw ex;
    }
}

通过上面的代码也能很容易的看出来,里面肯定创建了webServer,也就是tomcat,它的refresh实际上啥也没干,直接是调用的父类的refresh,也就是AbstractApplicationContext#refresh,熟悉spring的小伙伴对这个方法应该非常熟悉,这个方法就是用来初始化整个spring的环境的,做的事情很多,我们就不再做太多介绍,里面会执行到一行onRefresh(),因为ServletWebServerApplicationContext重写了onRefresh():

@Override
protected void onRefresh() {
    super.onRefresh();
    try {
        //就是在这里创建的webserver
        createWebServer();
    }
    catch (Throwable ex) {
        throw new ApplicationContextException("Unable to start web server", ex);
    }
}

ServletWebServerApplicationContext#createWebServer():

private void createWebServer() {
    WebServer webServer = this.webServer;
    ServletContext servletContext = getServletContext();
    if (webServer == null && servletContext == null) {
        ServletWebServerFactory factory = getWebServerFactory();
        //这里真正去创建tomcat
        this.webServer = factory.getWebServer(getSelfInitializer());
        。。。
    }
    。。。
}

这里首先是调用了getSelfInitializer(),然后把拿到的ServletContextInitializer传递给getWebServer()方法,注意下ServletContextInitializer,SpringBoot就是用这个接口来完成类似Servlet3.0的ServletContainerInitializer的功能的,进去到getWebServer()里面,TomcatServletWebServerFactory#getWebServer():

public WebServer getWebServer(ServletContextInitializer... initializers) {
    if (this.disableMBeanRegistry) {
        Registry.disableRegistry();
    }
    //这里创建了tomcat实例
    Tomcat tomcat = new Tomcat();
    File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
    tomcat.setBaseDir(baseDir.getAbsolutePath());
    Connector connector = new Connector(this.protocol);
    connector.setThrowOnFailure(true);
    //添加了connector
    tomcat.getService().addConnector(connector);
    //在这里设置了connector的端口号
    customizeConnector(connector);
    tomcat.setConnector(connector);
    tomcat.getHost().setAutoDeploy(false);
    configureEngine(tomcat.getEngine());
    for (Connector additionalConnector : this.additionalTomcatConnectors) {
        tomcat.getService().addConnector(additionalConnector);
    }
    //这里会把ServletContextInitializer传进去
    prepareContext(tomcat.getHost(), initializers);
    return getTomcatWebServer(tomcat);
}

看下TomcatServletWebServerFactory#prepareContext():

protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
    …
    //这里又添加了2个ServletContextInitializer
    ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
    host.addChild(context);
    //重点看下这里
    configureContext(context, initializersToUse);
    postProcessContext(context);
}

TomcatServletWebServerFactory#configureContext():

protected void configureContext(Context context, ServletContextInitializer[] initializers) {
    //这里创建了一个TomcatStarter,传递进去ServletContextInitializer
    TomcatStarter starter = new TomcatStarter(initializers);
    //TomcatEmbeddedContext继承了tomcat里面的StandardContext
    if (context instanceof TomcatEmbeddedContext) {
        TomcatEmbeddedContext embeddedContext = (TomcatEmbeddedContext) context;
        //把TomcatStarter添加到StandardContext的一个成员变量starter中
        embeddedContext.setStarter(starter);
        embeddedContext.setFailCtxIfServletStartFails(true);
    }
    //把TomcatStarter添加到StandardContext的initializers中,
    //initializers是一个Map
    context.addServletContainerInitializer(starter, NO_CLASSES);
    。。。
}

TomcatStarter是一个非常关键的类,它实现了ServletContainerInitializer接口,这个接口是servlet提供的,因此TomcatStarter是连接SpringBoot和tomcat的一个关键纽带。
继续往下执行就到了TomcatServletWebServerFactory#getTomcatWebServer():

protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
    return new TomcatWebServer(tomcat, getPort() >= 0, getShutdown());
}
public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) {
    ….
    //这里会去启动tomcat
    initialize();
}

TomcatWebServer#initialize():

private void initialize() throws WebServerException {
    logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
    。。。
    // 启动tomcat
    this.tomcat.start();
    //启动一个阻塞线程,防止tomcat退出
    startDaemonAwaitThread();
。。。
}

Tomcat启动以后,会执行到tomcat里面的StandardContext#startInternal():

protected synchronized void startInternal() throws LifecycleException {
    ...
    // initializers是前面存进去的,里面只有一个TomcatStarter
    for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry :
        initializers.entrySet()) {
        try {
            //这里会调用TomcatStarter的onStartup()
            //TomcatStarter里面有所有的ServletContextInitializer,因此这里就会回调到SpringBoot里面的ServletContextInitializer里面去
            entry.getKey().onStartup(entry.getValue(),
                    getServletContext());
        } catch (ServletException e) {
            log.error(sm.getString("standardContext.sciFail"), e);
            ok = false;
            break;
        }
    }
    ...
}

TomcatStarter#onStartup():

@Override
public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException {
    try {
        // initializers也是前面存进去的,里面一共3个,其中一个是ServletWebServerApplicationContext里面的selfInitialize
        for (ServletContextInitializer initializer : this.initializers) {
            initializer.onStartup(servletContext);
        }
    }
    catch (Exception ex) {
        ...
    }
}

ServletWebServerApplicationContext#selfInitialize():

private void selfInitialize(ServletContext servletContext) throws ServletException {
    prepareWebApplicationContext(servletContext);
    registerApplicationScope(servletContext);
    WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
    //这里面就拿到了DispatcherServletRegistrationBean
    for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
        beans.onStartup(servletContext);
    }
}

首先看下ServletWebServerApplicationContext#getServletContextInitializerBeans():

protected Collection<ServletContextInitializer> getServletContextInitializerBeans() {
    return new ServletContextInitializerBeans(getBeanFactory());
}
public ServletContextInitializerBeans(ListableBeanFactory beanFactory,
        Class<? extends ServletContextInitializer>... initializerTypes) {
    this.initializers = new LinkedMultiValueMap<>();
    this.initializerTypes = (initializerTypes.length != 0) ? Arrays.asList(initializerTypes)
            : Collections.singletonList(ServletContextInitializer.class);
    //这里去加载DispatcherServletRegistrationBean
    addServletContextInitializerBeans(beanFactory);
    addAdaptableBeans(beanFactory);
    List<ServletContextInitializer> sortedInitializers = this.initializers.values().stream()
            .flatMap((value) -> value.stream().sorted(AnnotationAwareOrderComparator.INSTANCE))
            .collect(Collectors.toList());
    this.sortedList = Collections.unmodifiableList(sortedInitializers);
    logMappings(this.initializers);
}
private void addServletContextInitializerBeans(ListableBeanFactory beanFactory) {
    for (Class<? extends ServletContextInitializer> initializerType : this.initializerTypes) {
        for (Entry<String, ? extends ServletContextInitializer> initializerBean : getOrderedBeansOfType(beanFactory,initializerType)) {
            //这里就有DispatcherServletRegistrationBean
            addServletContextInitializerBean(initializerBean.getKey(), initializerBean.getValue(), beanFactory);
        }
    }
}

拿到了DispatcherServletRegistrationBean,上面继续执行它的onStartup(),RegistrationBean#onStartup():

@Override
public final void onStartup(ServletContext servletContext) throws ServletException {
    //注册DispatcherServlet
    register(description, servletContext);
}

DynamicRegistrationBean# register():

@Override
protected final void register(String description, ServletContext servletContext) {
    //注册DispatcherServlet
    D registration = addRegistration(description, servletContext);
    if (registration == null) {
        logger.info(StringUtils.capitalize(description) + " was not registered (possibly already registered?)");
        return;
    }
    //设置映射路径和启动级别
    configure(registration);
}

ServletRegistrationBean#configure():

@Override
protected void configure(ServletRegistration.Dynamic registration) {
    super.configure(registration);
    //设置映射路径
    String[] urlMapping = StringUtils.toStringArray(this.urlMappings);
    if (urlMapping.length == 0 && this.alwaysMapUrl) {
        urlMapping = DEFAULT_MAPPINGS;
    }
    if (!ObjectUtils.isEmpty(urlMapping)) {
        registration.addMapping(urlMapping);
    }
    //设置启动级别
    registration.setLoadOnStartup(this.loadOnStartup);
    if (this.multipartConfig != null) {
        registration.setMultipartConfig(this.multipartConfig);
    }
}

至此,整个Spring环境和DispatcherServlet就都加载起来了。
现在还剩下一个问题,DispatcherServletRegistrationBean是如何加载到Spring的容器的呢?
在spring-boot-autoconfigure-**.jar的META-INF下面的spring.factories里面配置了一个DispatcherServletAutoConfiguration,在这里面去创建了DispatcherServletRegistrationBean。
(1)SpringBoot首先扫描classpath,根据里面是否有web相关的类去创建了AnnotationConfigServletWebServerApplicationContext。
(2)AnnotationConfigServletWebServerApplicationContext在它的refresh()里面回调了ServletWebServerApplicationContext的onRefresh(),在这里面去创建了tomcat。
(3)SpringBoot把实现了ServletContextInitializer接口的实现类传递到了TomcatStarter里面,TomcatStarter是由SpringBoot提供的,它同时实现了ServletContainerInitializer接口,而ServletContainerInitializer是Servlet提供的,因此,TomcatStarter是一个非常关键的纽带。
(4)当tomcat启动以后,内部会调用所有的ServletContainerInitializer的onStartup(),因此就回调到了TomcatStarter的onStartup(),进而回调到了ServletContextInitializer里面。
(5)在ServletWebServerApplicationContext里面的一个ServletContextInitializer里面去加载了DispatcherServletRegistrationBean,在它的onStartup()回调里面去创建了DispatcherServlet,并且设置了映射路径和启动级别。
(6)整个启动流程可以看出来,SpringBoot并不是向SpringMVC那样通过回调ServletContainerInitializer来完成加载。SpringBoot是直接生成了它的一个叫TomcatStarter的子类,然后在其中把对ServletContainerInitializer的回调转移到对ServletContextInitializer的回调,然后在ServletContextInitializer中去加载的DispatcherServlet。

欢迎扫码查看更多文章:


qrcode.jpg

相关文章

网友评论

      本文标题:SpringBoot加载web项目流程源码解析

      本文链接:https://www.haomeiwen.com/subject/vxhekktx.html