美文网首页
Tomcat在SpringBoot中的启动

Tomcat在SpringBoot中的启动

作者: 狒狒_94d7 | 来源:发表于2020-03-18 14:53 被阅读0次

    tomcat作为springboot嵌入服务器时,是单独为当前springboot提供服务的,所以有很多地方可以简化:

    • 支持项目
      独立服务器:多项目、多Servlet
      嵌入式:一般单项目(嵌入的项目)、单Servlet(DispatchServlet)
    • server构建
      独立服务器:Catalina通过解析server.xml构建
      嵌入式:源码构建
    • 启动方式
      独立服务器:Bootstrap引导启动
      嵌入式:Tomcat类启动

    对比了两种启动方式的差别,下面重点来看下springboot中是如何简化创建server并注入DispatchServlet的。
    首先看springboot的启动入口:
    SpringApplication.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);
                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;
        }
    

    主要是创建spring容器ApplicationContext,并进行刷新,在servlet模式下实现类为AnnotationConfigServletWebServerApplicationContext,复写了onRefresh方法,在容器刷新时,进行webServer的创建工作。

        @Override
        protected void onRefresh() {
            super.onRefresh();
            try {
                createWebServer();
            }
            catch (Throwable ex) {
                throw new ApplicationContextException("Unable to start web server", ex);
            }
        }
        private void createWebServer() {
            WebServer webServer = this.webServer;
            ServletContext servletContext = getServletContext();
            if (webServer == null && servletContext == null) {
                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();
        }
    

    默认配置下,ServletWebServerFactory实现为TomcatServletWebServerFactory,采用工厂模式来创建Tomcat服务器:

    @Override
        public WebServer getWebServer(ServletContextInitializer... initializers) {
            if (this.disableMBeanRegistry) {
                Registry.disableRegistry();
            }
            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);
            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);
        }
    

    这里我们可以发现,和独立服务器不一样,并没有创建Bootstrap、Catalina类,也没有通过server.xml配置文件来创建server,而是直接new了一个Tomcat,配置上connector、Engine、Host、Context等组件,在创建TomcatWebServer时启动tomcat:

    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 {
            logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
            ...省略其他代码
            this.tomcat.start();
            ...省略其他代码
    

    创建完成的服务器包含一个service,一个connector, 一个engine,一个host和一个context,tomcat.start时调用了server.start(),剩下的就和独立部署时一样,启动这些connector,container了。
    tomcat启动的流程就结束了,但是还有一个问题,这个过程中并没有看到tomcat加载Servlet,也没有解析web.xml,spring是通过DispatchServlet来处理请求的,它是怎么加载到容器中的呢?
    回到spring创建webServer的地方:

    ServletWebServerFactory factory = getWebServerFactory();
    this.webServer = factory.getWebServer(getSelfInitializer());
    

    可以看到它传入了一个ServletContextInitializer,这个initializer经过一系列封装最终传入了前面创建的唯一的Context里面,在Context启动的初始化阶段会进行回调,DispatchServlet的添加就在这个initializer里面,initializer的实现为:

    private void selfInitialize(ServletContext servletContext) throws ServletException {
            prepareWebApplicationContext(servletContext);
            registerApplicationScope(servletContext);
            WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
            for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
                beans.onStartup(servletContext);
            }
        }
    

    它的功能是从spring容器中找出所有实现了ServletContextInitializer来进行调用,而springboot的自动装配机制注入了DispatcherServletRegistrationBean,它实现了ServletContextInitializer,在tomcat容器启动时向context中注册了DispatcherServlet,具体流程代码就不贴了。
    Tomcat启动完成注入了DispatcherServlet后,DispatcherServlet即可处理请求了。

    相关文章

      网友评论

          本文标题:Tomcat在SpringBoot中的启动

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