SpringBoot(一)內嵌tomcat原理和DispatcherServlet配置原理


Springboot使用起來很簡單,在pom中引入如下依賴:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.5.3</version>
  </parent>
<dependencies>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-autoconfigure</artifactId>
    </dependency>
</dependencies>

其實就可以起來一個web服務,可以寫controller了。

 啟動入口也很簡單。

@SpringBootApplication
public class ApplicationStart {
    public static void main(String[] args) {
        SpringApplication.run(ApplicationStart.class);
    }
}

 

對於啟動流程來說,主要看SpringApplication.run方法了。

先挑着看,具體每步做什么,在啟動流程中分析。

1:看創建的容器   public ConfigurableApplicationContext run(String... args) {

        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();
        ConfigurableApplicationContext context = null;
        this.configureHeadlessProperty();
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
        listeners.starting(bootstrapContext, this.mainApplicationClass);

        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
            this.configureIgnoreBeanInfo(environment);
            Banner printedBanner = this.printBanner(environment);
// 創建的容器,分為三種 AnnotationConfigServletWebServerApplicationContext AnnotationConfigReactiveWebServerApplicationContext
// AnnotationConfigApplicationContext
context
= this.createApplicationContext(); context.setApplicationStartup(this.applicationStartup); this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
// 在這里啟動容器
this.refreshContext(context); this.afterRefresh(context, applicationArguments); stopWatch.stop(); if (this.logStartupInfo) { (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch); } listeners.started(context); this.callRunners(context, applicationArguments); } catch (Throwable var10) { this.handleRunFailure(context, var10, listeners); throw new IllegalStateException(var10); } try { listeners.running(context); return context; } catch (Throwable var9) { this.handleRunFailure(context, var9, (SpringApplicationRunListeners)null); throw new IllegalStateException(var9); } }

1.1創建容器:

 protected ConfigurableApplicationContext createApplicationContext() {
        return this.applicationContextFactory.create(this.webApplicationType);
    }

 

使用默認的容器工廠:是一個函數式接口。

 

 

 會根據是servlet編程還是響應式編程來創建容器。

我們這里是servlet編程,使用 AnnotationConfigServletWebServerApplicationContext容器

 

 

 

2:啟動容器 this.refreshContext(context);

 private void refreshContext(ConfigurableApplicationContext context) {
        if (this.registerShutdownHook) {
            shutdownHook.registerApplicationContext(context);
        }

        this.refresh(context);
    }
protected void refresh(ConfigurableApplicationContext applicationContext) {
        applicationContext.refresh();
    }

 

是調用AnnotationConfigServletWebServerApplicationContext 中的refresh()方法,這是spring啟動過程中的主要方法。

這個refresh()方法是在父類 AbstractApplicationContext 中的

public void refresh() throws BeansException, IllegalStateException {
        synchronized(this.startupShutdownMonitor) {
            this.prepareRefresh();
            ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
            this.prepareBeanFactory(beanFactory);

            try {
                this.postProcessBeanFactory(beanFactory);
                this.invokeBeanFactoryPostProcessors(beanFactory);
                this.registerBeanPostProcessors(beanFactory);
                this.initMessageSource();
                this.initApplicationEventMulticaster();
// 這里是spring提供的一個擴展點,在父類中是個空方法,要由子類重寫,這里就是springboot中的容器重寫了
this.onRefresh(); this.registerListeners(); this.finishBeanFactoryInitialization(beanFactory); this.finishRefresh(); } catch (BeansException var9) { if (this.logger.isWarnEnabled()) { this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9); } this.destroyBeans(); this.cancelRefresh(var9); throw var9; } finally { this.resetCommonCaches(); } } }

 

AnnotationConfigServletWebServerApplicationContext 的父類中ServletWebServerApplicationContext重寫了onResresh()方法

ServletWebServerApplicationContext#onResresh()

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

        try {
//內嵌的服務就是這里面創建的
this.createWebServer(); } catch (Throwable var2) { throw new ApplicationContextException("Unable to start web server", var2); } }

 

 private void createWebServer() {
        WebServer webServer = this.webServer;
// 得到servlet上下文 ServletContext servletContext
= this.getServletContext();
// 這里判斷了下servlet上下文是否為空???見下面補充
if (webServer == null && servletContext == null) { StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");
// 如果沒有上下文就要創建服務了 ServletWebServerFactory factory
= this.getWebServerFactory(); createWebServer.tag("factory", factory.getClass().toString());
// 執行TomcatServletServerWebServerFactory
this.webServer = factory.getWebServer(new ServletContextInitializer[]{this.getSelfInitializer()}); createWebServer.end(); this.getBeanFactory().registerSingleton("webServerGracefulShutdown", new WebServerGracefulShutdownLifecycle(this.webServer)); this.getBeanFactory().registerSingleton("webServerStartStop", new WebServerStartStopLifecycle(this, this.webServer)); } else if (servletContext != null) { try { this.getSelfInitializer().onStartup(servletContext); } catch (ServletException var5) { throw new ApplicationContextException("Cannot initialize servlet context", var5); } } this.initPropertySources(); }

 

    補充:上面為什么會有判斷servlet上下文是否為空的邏輯呢?

    因為對於springboot項目可以部署到tomcat,jetty,undertow等web服務器中,默認是使用tomcat的。如果像使用其他web服務器,就要使用配置文件了來修改默認的屬性。這是約定大於配置。因為沒有配置文件springboot也可以起一個web服務,因為由一些默認的配置。

    對於jar部署的springboot項目,入口就是main方法,它要先讀取配置才能啟動servlet容器。這個時候走到上面的邏輯是沒有servlet上下文的。

   如果是war包部署的web服務,是tomcat先啟動的,那走到上面邏輯的時候已經有了servlet上下文。

需要看下這里創建的到底是什么:ServletWebServerFactory factory = this.getWebServerFactory();

就是找一個ServletWebServerFactory的一個實現類類。

protected ServletWebServerFactory getWebServerFactory() {
        String[] beanNames = this.getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
        if (beanNames.length == 0) {
            throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to missing ServletWebServerFactory bean.");
        } else if (beanNames.length > 1) {
            throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames));
        } else {
            return (ServletWebServerFactory)this.getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
        }
    }

 

這個接口呢有下面幾個實現類:我們這里肯定要看tomcat的實現類,那這個TomcatServletWebServerFactory 是在哪里加載進來的呢

 

 

 怎么加載的就要看springboot的自動配置原理了,在springboot中自動配置原理也是spi機制的一種原理,只不過加載的spring.factories文件這個后面再具體分析。

 

 

 里面有一個ServletWebServerFactoryAutoConfiguration類,通過@Import導入了一個EmbeddedTomcat類。

 

 

 

 

這里就是 TomcatServletWebServerFactory  放入spring容器中的地方。

回到上面的邏輯,得到TomcatServletWebServerFactory調用其getWebServer 方法。

這里就是創建內嵌tomcat的地方,下面的寫法是內嵌tomcat中創建有關tomcat組件的寫法。

 public WebServer getWebServer(ServletContextInitializer... initializers) {
        if (this.disableMBeanRegistry) {
            Registry.disableRegistry();
        }
        // new 一個tomcat實例
        Tomcat tomcat = new Tomcat();
        File baseDir = this.baseDirectory != null ? this.baseDirectory : this.createTempDir("tomcat");
        tomcat.setBaseDir(baseDir.getAbsolutePath());
// tomcat中的Connector組件 Connector connector
= new Connector(this.protocol); connector.setThrowOnFailure(true); tomcat.getService().addConnector(connector); this.customizeConnector(connector); tomcat.setConnector(connector); tomcat.getHost().setAutoDeploy(false); this.configureEngine(tomcat.getEngine()); Iterator var5 = this.additionalTomcatConnectors.iterator(); while(var5.hasNext()) { Connector additionalConnector = (Connector)var5.next(); tomcat.getService().addConnector(additionalConnector); } // DispatcherServlet就是在這里初始化的 this.prepareContext(tomcat.getHost(), initializers);
// tomcat的start,和socket啟動阻塞都在這個方法里
return this.getTomcatWebServer(tomcat); }
   protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
        return new TomcatWebServer(tomcat, this.getPort() >= 0, this.getShutdown());
    }

 

public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) {
        this.monitor = new Object();
        this.serviceConnectors = new HashMap();
        Assert.notNull(tomcat, "Tomcat Server must not be null");
        this.tomcat = tomcat;
        this.autoStart = autoStart;
        this.gracefulShutdown = shutdown == Shutdown.GRACEFUL ? new GracefulShutdown(tomcat) : null;
        this.initialize();
    }
 private void initialize() throws WebServerException {
        logger.info("Tomcat initialized with port(s): " + this.getPortsDescription(false));
        synchronized(this.monitor) {
            try {
                this.addInstanceIdToEngineName();
                Context context = this.findContext();
                context.addLifecycleListener((event) -> {
                    if (context.equals(event.getSource()) && "start".equals(event.getType())) {
                        this.removeServiceConnectors();
                    }

                });
// tomcat就是在這里啟動的
this.tomcat.start(); this.rethrowDeferredStartupExceptions(); try { ContextBindings.bindClassLoader(context, context.getNamingToken(), this.getClass().getClassLoader()); } catch (NamingException var5) { } // 這里就是設置socket監聽 this.startDaemonAwaitThread(); } catch (Exception var6) { this.stopSilently(); this.destroySilently(); throw new WebServerException("Unable to start embedded Tomcat", var6); } } }

 

 private void startDaemonAwaitThread() {
        Thread awaitThread = new Thread("container-" + containerCounter.get()) {
            public void run() {
                TomcatWebServer.this.tomcat.getServer().await();
            }
        };
        awaitThread.setContextClassLoader(this.getClass().getClassLoader());
        awaitThread.setDaemon(false);
        awaitThread.start();
    }

 


 

上面就是內嵌tomcat的啟動過程,下面分析下DispatcherServlet加載的源碼分析。

在上面createServer方法中有一行調用: this.webServer = factory.getWebServer(new ServletContextInitializer[]{this.getSelfInitializer()});

參數是一個ServletContextInitializer 的數組,是從getSelfInitializer() 方法中得到,DispatcherServlet就是這里加載的。

  // 但是這里返回是個lambda 表達式,ServletContextInitializer是個一個函數式接口,里面只有一個onStartup方法,這里相當於返回了一個接口的實例,只是這個實例不再容器中
// 只是這個實例的onStartup的邏輯就是下面的selfInitialize方法, 調用getSerlfInitializer方法的時候,下面那個方法不會馬上執行。
private ServletContextInitializer getSelfInitializer() { return this::selfInitialize; } private void selfInitialize(ServletContext servletContext) throws ServletException { this.prepareWebApplicationContext(servletContext); this.registerApplicationScope(servletContext); WebApplicationContextUtils.registerEnvironmentBeans(this.getBeanFactory(), servletContext);
// 這里就是找ServletContextInitializer的實現類 Iterator var2
= this.getServletContextInitializerBeans().iterator(); while(var2.hasNext()) {
// 這里遍歷調用onStarup方法 ServletContextInitializer beans
= (ServletContextInitializer)var2.next(); beans.onStartup(servletContext); } }

 

@FunctionalInterface
public interface ServletContextInitializer {
    void onStartup(ServletContext servletContext) throws ServletException;
}

 

回到上面的getWebServer方法,它的參數就是getSelfInitializer()得到的lamda表達式。在調用 this.prepareContext(tomcat.getHost(), initializers);

時初始化DispatcherServlet。這是在TomcatServletWebServerFactory 中的。

protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
        File documentRoot = this.getValidDocumentRoot();
// 這個new出來的類,繼承自tomcat中的StandardContext
// tomcat在初始化過程中會觸發StandardContext中的一個全局變量 Map<ServletContainerInitializer,Set<Class<?>>> initializers
// 遍歷它並執行ServletContainerInitializer.onStartup方法 這個過程在springmvc初始化servlet中有提到過 TomcatEmbeddedContext context
= new TomcatEmbeddedContext(); if (documentRoot != null) { context.setResources(new TomcatServletWebServerFactory.LoaderHidingResourceRoot(context)); } context.setName(this.getContextPath()); context.setDisplayName(this.getDisplayName()); context.setPath(this.getContextPath()); File docBase = documentRoot != null ? documentRoot : this.createTempDir("tomcat-docbase"); context.setDocBase(docBase.getAbsolutePath()); context.addLifecycleListener(new FixContextListener()); context.setParentClassLoader(this.resourceLoader != null ? this.resourceLoader.getClassLoader() : ClassUtils.getDefaultClassLoader()); this.resetDefaultLocaleMapping(context); this.addLocaleMappings(context); try { context.setCreateUploadTargets(true); } catch (NoSuchMethodError var8) { } this.configureTldPatterns(context); WebappLoader loader = new WebappLoader(); loader.setLoaderClass(TomcatEmbeddedWebappClassLoader.class.getName()); loader.setDelegate(true); context.setLoader(loader); if (this.isRegisterDefaultServlet()) { this.addDefaultServlet(context); } if (this.shouldRegisterJspServlet()) { this.addJspServlet(context); this.addJasperInitializer(context); } context.addLifecycleListener(new TomcatServletWebServerFactory.StaticResourceConfigurer(context)); ServletContextInitializer[] initializersToUse = this.mergeInitializers(initializers); host.addChild(context);
// 到這里 initializersToUse還是上面的lambda表達式,只是做了一些合並操作 ,執行也是在這里面的
this.configureContext(context, initializersToUse); this.postProcessContext(context); }
this.configureContext(context, initializersToUse);
 protected void configureContext(Context context, ServletContextInitializer[] initializers) {
// 它就實現了ServletContainerInitializer的接口 並通過構造函數把上面的那個lambda表達式傳遞進去
// 它里面的onStartup方法,就會在執行的時候遍歷這個數組中的實例執行其onStartup方法,也就時執行了上面的lambda表達式 TomcatStarter starter
= new TomcatStarter(initializers); if (context instanceof TomcatEmbeddedContext) { TomcatEmbeddedContext embeddedContext = (TomcatEmbeddedContext)context; embeddedContext.setStarter(starter); embeddedContext.setFailCtxIfServletStartFails(true); } // 這個context就是StandardContext的子類,把starter添加進去,上面TomcatStarter能執行也是這一步把它添加到了StandardContext的全局變量中了。 context.addServletContainerInitializer(starter, NO_CLASSES); Iterator var7 = this.contextLifecycleListeners.iterator(); while(var7.hasNext()) { LifecycleListener lifecycleListener = (LifecycleListener)var7.next(); context.addLifecycleListener(lifecycleListener); } var7 = this.contextValves.iterator(); while(var7.hasNext()) { Valve valve = (Valve)var7.next(); context.getPipeline().addValve(valve); } var7 = this.getErrorPages().iterator(); while(var7.hasNext()) { ErrorPage errorPage = (ErrorPage)var7.next(); org.apache.tomcat.util.descriptor.web.ErrorPage tomcatErrorPage = new org.apache.tomcat.util.descriptor.web.ErrorPage(); tomcatErrorPage.setLocation(errorPage.getPath()); tomcatErrorPage.setErrorCode(errorPage.getStatusCode()); tomcatErrorPage.setExceptionType(errorPage.getExceptionName()); context.addErrorPage(tomcatErrorPage); } var7 = this.getMimeMappings().iterator(); while(var7.hasNext()) { Mapping mapping = (Mapping)var7.next(); context.addMimeMapping(mapping.getExtension(), mapping.getMimeType()); } this.configureSession(context); (new DisableReferenceClearingContextCustomizer()).customize(context); var7 = this.getWebListenerClassNames().iterator(); while(var7.hasNext()) { String webListenerClassName = (String)var7.next(); context.addApplicationListener(webListenerClassName); } var7 = this.tomcatContextCustomizers.iterator(); while(var7.hasNext()) { TomcatContextCustomizer customizer = (TomcatContextCustomizer)var7.next(); customizer.customize(context); } }

 

知道了怎么走到ServletContextInitializer接口的onStartup方法,看下它有那些實現類。

 

 這里會走到這個RegistrationBean,為什么會是它,而且看下也沒有加上什么注解,它是怎么加載到spring容器中的呢,下面會說。在這個類里有onStartup方法。

 

 

 它里面有一個注冊方法this.register(description, servletContext); ,這個方法實在子類DynamicRegistrationBean中的

protected final void register(String description, ServletContext servletContext) {
        D registration = this.addRegistration(description, servletContext);
        if (registration == null) {
            logger.info(StringUtils.capitalize(description) + " was not registered (possibly already registered?)");
        } else {
            this.configure(registration);
        }
    }

 

這個register方法中又有一個this.addRegistration(description, servletContext); 方法的調用,這個方法還是子類中調用的。子類是ServletRegistrationBean

到這里才真正看到Servlet的影子。

   protected Dynamic addRegistration(String description, ServletContext servletContext) {
        String name = this.getServletName();
        return servletContext.addServlet(name, this.servlet);
    }

 

 

上面三個的繼承關系如下:

 

 

 

 那上面那個servlet哪里來的呢?  這又要說到springboot的自動裝配了,在spring.factories中有一個DispatcherServletAutoConfiguration 的配置類。

 

 

 在這個配置類中有兩個重要的地方和DispatcherServlet有關。

首先定義了一個DispatcherServlet

 

 

 把它和上面的ServletRegistrationBean 關聯起來的是下面的一個Bean

 

 這個dispatcherServletRegistration 的bean中會自動注入上面創建的DispatcherServlet,然后把它傳遞給了DispatcherServletRegistrationBean構造函數。

 

 

 從這個bean的繼承和構造函數的調用就應該能明白,這里把DispatcherServlet傳遞給了ServletRegistrationBean 

 

 這就把上面整個串起來了。

這里貼上上面兩個流程的圖解,有助於理解。可能圖的版本和貼出來的代碼版本不一致,但是不影響理解主線的流程。

tomcat內嵌:

 

 

dispatcherServlet的裝配

 

 

 






 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM