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的裝配