對于Spring Boot項目來說只需要如下代碼就可以啟動整個項目
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
那么Spring容器,Web容器等等是怎么啟動的?
- new SpringApplication
/** * Create a new {@link SpringApplication} instance. The application context will load * beans from the specified primary sources (see {@link SpringApplication class-level} * documentation for details. The instance can be customized before calling * {@link #run(String...)}. * @param resourceLoader the resource loader to use * @param primarySources the primary bean sources * @see #run(Class, String[]) * @see #setSources(Set) */ @SuppressWarnings({ "unchecked", "rawtypes" }) public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { this.resourceLoader = resourceLoader; Assert.notNull(primarySources, "PrimarySources must not be null"); this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); this.webApplicationType = WebApplicationType.deduceFromClasspath(); setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); this.mainApplicationClass = deduceMainApplicationClass(); }
WebApplicationType.deduceFromClasspath();
推斷Web容器類型,具體參考容器推斷getSpringFactoriesInstances(ApplicationContextInitializer.class)
從Spring factories中獲取ApplicationContextInitializer.class對應(yīng)的實現(xiàn)類融柬,具體參考Spring factories//把對應(yīng)的實現(xiàn)加進Initializers集合 setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); //把對應(yīng)的實現(xiàn)加進Listeners集合 setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
-
this.mainApplicationClass = deduceMainApplicationClass();
一個有意思的寫法仰楚,根據(jù)錯誤堆棧獲取當前調(diào)用的類private Class<?> deduceMainApplicationClass() { try { //獲取當前的堆棧 StackTraceElement[] stackTrace = new RuntimeException().getStackTrace(); for (StackTraceElement stackTraceElement : stackTrace) { //判斷方法名,獲取對應(yīng)的啟動類 if ("main".equals(stackTraceElement.getMethodName())) { return Class.forName(stackTraceElement.getClassName()); } } } catch (ClassNotFoundException ex) { // Swallow and continue } return null; }
至此举哟,完成了SpringApplication的初始化
- 調(diào)用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; }
//獲取SpringApplicationRunListener接口對應(yīng)的實現(xiàn)然后封裝成SpringApplicationRunListeners迅矛,其實就是內(nèi)部維護了一個集合 SpringApplicationRunListeners listeners = getRunListeners(args); //觀察者模式妨猩,循環(huán)調(diào)用內(nèi)部集合進行通知 listeners.starting();
-
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
創(chuàng)建準備Environment對象private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) { // 根據(jù)推斷的web應(yīng)用類型,創(chuàng)建對應(yīng)的Environment ConfigurableEnvironment environment = getOrCreateEnvironment(); // 對environment添加秽褒、刪除壶硅、重排序PropertySource // 設(shè)置environment的active profiles configureEnvironment(environment, applicationArguments.getSourceArgs()); //通知environment準備好 listeners.environmentPrepared(environment); bindToSpringApplication(environment); if (!this.isCustomEnvironment) { environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment, deduceEnvironmentClass()); } ConfigurationPropertySources.attach(environment); return environment; }
//根據(jù)推斷來的web應(yīng)用容器類型創(chuàng)建對應(yīng)的Spring context //servlet容器對應(yīng)的是AnnotationConfigServletWebServerApplicationContext context = createApplicationContext();
-
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
準備Spring contextprivate void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) { context.setEnvironment(environment); postProcessApplicationContext(context); // 從Spring factories中加載到的ApplicationContextInitializer對應(yīng)的實例威兜, // 調(diào)用對應(yīng)實例的initialize方法,把Spring context傳進去 applyInitializers(context); listeners.contextPrepared(context); if (this.logStartupInfo) { logStartupInfo(context.getParent() == null); logStartupProfileInfo(context); } // Add boot specific singleton beans ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); beanFactory.registerSingleton("springApplicationArguments", applicationArguments); if (printedBanner != null) { beanFactory.registerSingleton("springBootBanner", printedBanner); } if (beanFactory instanceof DefaultListableBeanFactory) { ((DefaultListableBeanFactory) beanFactory) .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding); } // Load the sources Set<Object> sources = getAllSources(); Assert.notEmpty(sources, "Sources must not be empty"); load(context, sources.toArray(new Object[0])); listeners.contextLoaded(context); }
-
refreshContext(context);
開始進入Web容器的啟動- 根據(jù)之前創(chuàng)建的Spring context實例庐椒,調(diào)用對應(yīng)的
onRefresh()
方法牡属。這里調(diào)用的是org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh()
最終進入到private void createWebServer() { WebServer webServer = this.webServer; ServletContext servletContext = getServletContext(); if (webServer == null && servletContext == null) { //webServer和servletContext都會空,會走到這里 //從當前的BeanFactory獲取類型為ServletWebServerFactory的bean //這里獲取到的是TomcatServletWebServerFactory 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(); }
getSelfInitializer()
獲取自身的初始化器扼睬,類型為ServletContextInitializer逮栅,這里返回的是一個lambda表達式,也就是返回了一個方法引用窗宇。 - 至此措伐,獲取到了TomcatServletWebServerFactory實例和一個ServletContextInitializer類型的lambda表達式
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); } //準備tomcat context prepareContext(tomcat.getHost(), initializers); return getTomcatWebServer(tomcat); } /** 準備tomcat context */ protected void prepareContext(Host host, ServletContextInitializer[] initializers) { File documentRoot = getValidDocumentRoot(); TomcatEmbeddedContext context = new TomcatEmbeddedContext(); if (documentRoot != null) { context.setResources(new LoaderHidingResourceRoot(context)); } context.setName(getContextPath()); context.setDisplayName(getDisplayName()); context.setPath(getContextPath()); File docBase = (documentRoot != null) ? documentRoot : createTempDir("tomcat-docbase"); context.setDocBase(docBase.getAbsolutePath()); context.addLifecycleListener(new FixContextListener()); context.setParentClassLoader((this.resourceLoader != null) ? this.resourceLoader.getClassLoader() : ClassUtils.getDefaultClassLoader()); resetDefaultLocaleMapping(context); addLocaleMappings(context); context.setUseRelativeRedirects(false); try { context.setCreateUploadTargets(true); } catch (NoSuchMethodError ex) { // Tomcat is < 8.5.39. Continue. } configureTldSkipPatterns(context); WebappLoader loader = new WebappLoader(context.getParentClassLoader()); loader.setLoaderClass(TomcatEmbeddedWebappClassLoader.class.getName()); loader.setDelegate(true); context.setLoader(loader); if (isRegisterDefaultServlet()) { addDefaultServlet(context); } if (shouldRegisterJspServlet()) { addJspServlet(context); addJasperInitializer(context); } context.addLifecycleListener(new StaticResourceConfigurer(context)); ServletContextInitializer[] initializersToUse = mergeInitializers(initializers); host.addChild(context); configureContext(context, initializersToUse); postProcessContext(context); } /** 配置tomcat context */ protected void configureContext(Context context, ServletContextInitializer[] initializers) { //初始化TomcatStarter,該類是tomcat容器初始化過程的一個委托或是代理的角色 TomcatStarter starter = new TomcatStarter(initializers); if (context instanceof TomcatEmbeddedContext) { TomcatEmbeddedContext embeddedContext = (TomcatEmbeddedContext) context; embeddedContext.setStarter(starter); embeddedContext.setFailCtxIfServletStartFails(true); } //在這里會進行ServletContextInitializer的初始化工作 context.addServletContainerInitializer(starter, NO_CLASSES); for (LifecycleListener lifecycleListener : this.contextLifecycleListeners) { context.addLifecycleListener(lifecycleListener); } for (Valve valve : this.contextValves) { context.getPipeline().addValve(valve); } for (ErrorPage errorPage : getErrorPages()) { 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); } for (MimeMappings.Mapping mapping : getMimeMappings()) { context.addMimeMapping(mapping.getExtension(), mapping.getMimeType()); } configureSession(context); new DisableReferenceClearingContextCustomizer().customize(context); for (TomcatContextCustomizer customizer : this.tomcatContextCustomizers) { customizer.customize(context); } }
- 最終ServletContext在啟動過程中會被ServletContextInitializer的onStartup進行配置军俊。會對ServletContext的任意servlet filters listeners context-params attributes進行必需的初始化配置侥加。這里的關(guān)于Spring Boot的Web容器啟動方式和傳統(tǒng)的War包部署啟動方式是有一定的差異
- 在啟動時TomcatStarter會獲取到三個ServletContextInitializer的實例
-
(servletContext) -> this.initParameters.forEach(servletContext::setInitParameter)
是一個lambda表達式,為servletContext設(shè)置初始化參數(shù) -
new SessionConfiguringInitializer(this.session)
配置session和cookie相關(guān)操作 - ServletWebServerApplicationContext.getSelfInitializer()這里返回的是一個lambda表達式粪躬,也就是返回了一個方法引用担败。
執(zhí)行到這里的時候,會在這里獲取到四個對應(yīng)ServletContextInitializer實例private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() { return this::selfInitialize; } private void selfInitialize(ServletContext servletContext) throws ServletException { prepareWebApplicationContext(servletContext); registerApplicationScope(servletContext); WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext); for (ServletContextInitializer beans : getServletContextInitializerBeans()) { beans.onStartup(servletContext); } }
- dispatcherServlet urls=[/]
- characterEncodingFilter urls=[/*]
- formContentFilter urls=[/*]
- requestContextFilter urls=[/*]
-
- tomcat都是通過編程的方式進行servlet等相關(guān)組件的注冊镰官,這里是用到Servlet 3.0規(guī)范的內(nèi)容提前。
例如Servlet的注冊就是通過ServletRegistrationBean完成的。
- 根據(jù)之前創(chuàng)建的Spring context實例庐椒,調(diào)用對應(yīng)的