对于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对应的实现类,具体参考Spring factories//把对应的实现加进Initializers集合 setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); //把对应的实现加进Listeners集合 setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
-
this.mainApplicationClass = deduceMainApplicationClass();
一个有意思的写法,根据错误堆栈获取当前调用的类private Class<?> deduceMainApplicationClass() { try { //获取当前的堆栈 StackTraceElement[] stackTrace = new RuntimeException().getStackTrace(); for (StackTraceElement stackTraceElement : stackTrace) { //判断方法名,获取对应的启动类 if ("main".equals(stackTraceElement.getMethodName())) { return Class.forName(stackTraceElement.getClassName()); } } } catch (ClassNotFoundException ex) { // Swallow and continue } return null; }
至此,完成了SpringApplication的初始化
- 调用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接口对应的实现然后封装成SpringApplicationRunListeners,其实就是内部维护了一个集合 SpringApplicationRunListeners listeners = getRunListeners(args); //观察者模式,循环调用内部集合进行通知 listeners.starting();
-
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
创建准备Environment对象private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) { // 根据推断的web应用类型,创建对应的Environment ConfigurableEnvironment environment = getOrCreateEnvironment(); // 对environment添加、删除、重排序PropertySource // 设置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; }
//根据推断来的web应用容器类型创建对应的Spring context //servlet容器对应的是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对应的实例, // 调用对应实例的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容器的启动- 根据之前创建的Spring context实例,调用对应的
onRefresh()
方法。这里调用的是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进行必需的初始化配置。这里的关于Spring Boot的Web容器启动方式和传统的War包部署启动方式是有一定的差异
- 在启动时TomcatStarter会获取到三个ServletContextInitializer的实例
-
(servletContext) -> this.initParameters.forEach(servletContext::setInitParameter)
是一个lambda表达式,为servletContext设置初始化参数 -
new SessionConfiguringInitializer(this.session)
配置session和cookie相关操作 - ServletWebServerApplicationContext.getSelfInitializer()这里返回的是一个lambda表达式,也就是返回了一个方法引用。
执行到这里的时候,会在这里获取到四个对应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等相关组件的注册,这里是用到Servlet 3.0规范的内容。
例如Servlet的注册就是通过ServletRegistrationBean完成的。
- 根据之前创建的Spring context实例,调用对应的