1. 前言
在 Spring WebFlux源码分析(1)-服务启动流程 中笔者大致分析了 Spring WebFlux
的启动流程,其实这部分可以说是 SprintBoot
的启动主流程,Spring WebMVC
的启动也由此触发。需要注意的是, Spring WebMVC
框架默认集成 Tomcat 作为底层服务器,如果不指定 WebApplicationType
, 那 AbstractApplicationContext
的实现类就变成了 AnnotationConfigServletWebServerApplicationContext
,该类继承于 ServletWebServerApplicationContext
。 Spring WebMVC
框架中的重要逻辑基本都在 ServletWebServerApplicationContext
中,本文的分析将从这个类开始
2. Tomcat 服务器的启动分析
2.1 Tomcat 服务器的启动
-
我们知道
SpringBoot
框架启动过程中最核心的容器刷新方法AbstractApplicationContext#refresh()
将被调用,而作为其子类的ServletWebServerApplicationContext#onRefresh()
方法将被回调,可以看到这个方法的核心逻辑是调用ServletWebServerApplicationContext#createWebServer()
方法去创建底层服务器@Override protected void onRefresh() { super.onRefresh(); try { createWebServer(); } catch (Throwable ex) { throw new ApplicationContextException("Unable to start web server", ex); } }
-
ServletWebServerApplicationContext#createWebServer()
方法的实现如下,比较重要的处理是调用ServletWebServerApplicationContext#getWebServerFactory()
方法确定一个 Servlet 服务器工厂实例,使用 Tomcat 的话就会取到一个TomcatServletWebServerFactory
实例,随后使用这个工厂去获取服务器实例,此处需要注意ServletWebServerApplicationContext#getSelfInitializer()
方法的返回值将作为参数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(); }
-
ServletWebServerApplicationContext#getSelfInitializer()
方法非常简单,就是将私有方法ServletWebServerApplicationContext#selfInitialize()
作为函数式接口ServletContextInitializer
的实现返回,此处是将上层的Servlet/Filter
注入到 Tomcat 中的关键private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() { return this::selfInitialize; }
-
TomcatServletWebServerFactory#getWebServer()
方法将上层的ServletContextInitializer
初始化对象作为入参,开始创建 Tomcat 服务器对象实例及其重要组件,关键步骤如下,不了解 Tomcat 组件的读者可参考 Tomcat源码分析(1)-结构组成与核心组件- 新建 Tomcat 内部的
Tomcat
实例 - 新建 Tomcat 的
Connector
组件,其内部包含的组件都将被创建 tomcat.getService().addConnector()
将先创建StandardServer
对象,随后创建StandardService
对象- 调用
TomcatServletWebServerFactory#customizeConnector()
方法将外部对Connector
组件的定制化配置载入 tomcat.getHost().setAutoDeploy()
首先创建顶级容器对象StandardEngine
,之后将创建代表一个站点的StandardHost
对象- 调用
TomcatServletWebServerFactory#prepareContext()
方法创建代表一个 web 应用的TomcatEmbeddedContext
对象,并使用上层的ServletContextInitializer
对其进行配置 - 调用
TomcatServletWebServerFactory#getTomcatWebServer()
方法获取一个面向上层的WebServer
对象,并启动 Tomcat 服务器
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); }
- 新建 Tomcat 内部的
-
Tomcat#getService()
方法的内部逻辑是首先尝试获取 Tomcat 的Server
实例,没有则创建新的StandardServer
并为其创建StandardService
组件public Service getService() { return getServer().findServices()[0]; } public Server getServer() { if (server != null) { return server; } System.setProperty("catalina.useNaming", "false"); server = new StandardServer(); initBaseDir(); // Set configuration source ConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(new File(basedir), null)); server.setPort( -1 ); Service service = new StandardService(); service.setName("Tomcat"); server.addService(service); return server; }
-
Tomcat#getHost()
方法主要负责在服务启动时创建 Tomcat 的重要容器,其内部逻辑与步骤5类似,首先创建顶级顶级容器对象StandardEngine
,之后将创建代表一个站点的StandardHost
对象public Host getHost() { Engine engine = getEngine(); if (engine.findChildren().length > 0) { return (Host) engine.findChildren()[0]; } Host host = new StandardHost(); host.setName(hostname); getEngine().addChild(host); return host; } public Engine getEngine() { Service service = getServer().findServices()[0]; if (service.getContainer() != null) { return service.getContainer(); } Engine engine = new StandardEngine(); engine.setName( "Tomcat" ); engine.setDefaultHost(hostname); engine.setRealm(createDefaultRealm()); service.setContainer(engine); return engine; }
-
TomcatServletWebServerFactory#prepareContext()
将创建 Tomcat 的Context
容器并对其进行配置,本文关注的是TomcatServletWebServerFactory#configureContext()
方法配置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); }
-
TomcatServletWebServerFactory#configureContext()
方法的重要逻辑如下:- 将上层传入的
ServletContextInitializer
列表封装到容器初始化类TomcatStarter
中,通过context.addServletContainerInitializer()
添加到Context
容器内部 - 借助
TomcatContextCustomizer
将外部对Context
容器的定制化配置载入
protected void configureContext(Context context, ServletContextInitializer[] initializers) { TomcatStarter starter = new TomcatStarter(initializers); if (context instanceof TomcatEmbeddedContext) { TomcatEmbeddedContext embeddedContext = (TomcatEmbeddedContext) context; embeddedContext.setStarter(starter); embeddedContext.setFailCtxIfServletStartFails(true); } 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); } }
- 将上层传入的
-
TomcatServletWebServerFactory#getTomcatWebServer()
方法将创建一个面向上层的TomcatWebServer
对象,并在这个类的构造方法中调用TomcatWebServer#initialize()
方法,最终调用Tomcat#satrt()
方法启动 Tomcat 服务器protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) { return new TomcatWebServer(tomcat, getPort() >= 0); } 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)); synchronized (this.monitor) { try { addInstanceIdToEngineName(); Context context = findContext(); context.addLifecycleListener((event) -> { if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) { // Remove service connectors so that protocol binding doesn't // happen when the service is started. removeServiceConnectors(); } }); // Start the server to trigger initialization listeners this.tomcat.start(); // We can re-throw failure exception directly in the main thread rethrowDeferredStartupExceptions(); try { ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader()); } catch (NamingException ex) { // Naming is not enabled. Continue } // Unlike Jetty, all Tomcat threads are daemon threads. We create a // blocking non-daemon to stop immediate shutdown startDaemonAwaitThread(); } catch (Exception ex) { stopSilently(); destroySilently(); throw new WebServerException("Unable to start embedded Tomcat", ex); } } }
-
Tomcat#satrt()
方法其实就是使用StandardServer
对象开始整个服务器的启动流程,这部分不再赘述,有兴趣的读者可以参考 Tomcat源码分析(3)-容器 Container 的启动,这个过程将一直调用到Context
容器的启动方法,也就是StandardContext#startInternal()
,到这一步开始进入下一节分析public void start() throws LifecycleException { getServer(); server.start(); }
2.2 Filter 过滤器的注入
-
StandardContext#startInternal()
这个方法比较长,本文主要关注上层组件的注入流程。在上一节步骤8中笔者提到了上层容器初始化类TomcatStarter
对象已经添加到Context
容器内部,此处则主要关注其方法回调的流程- 首先调用
StandardContext#getServletContext()
获取ServletContext
对象,此处会创建一个ApplicationContext
对象,并将其包装类ApplicationContextFacade
对象作为返回值返回 - 在启动过程中,会遍历
Context
容器内部初始化器列表并回调ServletContainerInitializers
接口方法,则此处将回调到TomcatStarter#onStartup()
方法
protected synchronized void startInternal() throws LifecycleException { ...... // Call ServletContainerInitializers for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry : initializers.entrySet()) { try { entry.getKey().onStartup(entry.getValue(), getServletContext()); } catch (ServletException e) { log.error(sm.getString("standardContext.sciFail"), e); ok = false; break; } } ...... } public ServletContext getServletContext() { if (context == null) { context = new ApplicationContext(this); if (altDDName != null) context.setAttribute(Globals.ALT_DD_ATTR,altDDName); } return context.getFacade(); }
- 首先调用
-
TomcatStarter#onStartup()
逻辑非常简单,同样是回调内部的初始化器ServletContextInitializer
接口方法,读者如没有印象,可以回到上节步骤3,则可以知道此处将回调到 Spring 上层的ServletWebServerApplicationContext#selfInitialize()
方法public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException { try { for (ServletContextInitializer initializer : this.initializers) { initializer.onStartup(servletContext); } } catch (Exception ex) { this.startUpException = ex; // Prevent Tomcat from logging and re-throwing when we know we can // deal with it in the main thread, but log for information here. if (logger.isErrorEnabled()) { logger.error("Error starting Tomcat context. Exception: " + ex.getClass().getName() + ". Message: " + ex.getMessage()); } } }
-
ServletWebServerApplicationContext#selfInitialize()
方法的实现并不复杂,关键是调用ServletWebServerApplicationContext#getServletContextInitializerBeans()
方法新建一个ServletContextInitializerBeans
对象,在该对象的构造方法中获取 Spring 容器内实现了ServletContextInitializer
接口的类实例并保存在内部,最后遍历这个新对象并逐一回调其保存的实例的接口方法。ServletContextInitializer
接口的继承结构如下:
private void selfInitialize(ServletContext servletContext) throws ServletException { prepareWebApplicationContext(servletContext); registerApplicationScope(servletContext); WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext); for (ServletContextInitializer beans : getServletContextInitializerBeans()) { beans.onStartup(servletContext); } }
-
本文以
Filter 过滤器
为例分析上层组件注入 Tomcat 容器的过程,则接上一步骤可以知道FilterRegistrationBean#onStartup()
方法将被触发。这个方法实际由其父类RegistrationBean#onStartup()
实现,核心的逻辑是调用子类实现的DynamicRegistrationBean#register()
方法public final void onStartup(ServletContext servletContext) throws ServletException { String description = getDescription(); if (!isEnabled()) { logger.info(StringUtils.capitalize(description) + " was not registered (disabled)"); return; } register(description, servletContext); }
-
DynamicRegistrationBean#register()
方法的重要逻辑分为两步:- 调用子类实现的
addRegistration()
方法,返回一个注册器对象,此处将调用到AbstractFilterRegistrationBean#addRegistration()
方法 - 通过这个注册器对象对 Tomcat 服务器做进一步配置,将调用到子类实现
AbstractFilterRegistrationBean#configure()
方法
protected final void register(String description, ServletContext servletContext) { D registration = addRegistration(description, servletContext); if (registration == null) { logger.info(StringUtils.capitalize(description) + " was not registered (possibly already registered?)"); return; } configure(registration); }
- 调用子类实现的
-
AbstractFilterRegistrationBean#addRegistration()
方法比较简单,核心就是调用本节步骤1创建的ServletContext
对象方法将上层封装在FilterRegistrationBean
中的过滤器添加到 Tomcat 内部,此处最终将触发ApplicationContext#addFilter()
方法protected Dynamic addRegistration(String description, ServletContext servletContext) { Filter filter = getFilter(); return servletContext.addFilter(getOrDeduceName(filter), filter); }
-
ApplicationContext#addFilter()
方法核心逻辑如下:- 首先将上层的
过滤器 Filter
封装为FilterDef
,并将其添加到 Tomcat 的Context
容器内部列表中 - 返回一个封装了
Context
容器的ApplicationFilterRegistration
对象
private FilterRegistration.Dynamic addFilter(String filterName, String filterClass, Filter filter) throws IllegalStateException { if (filterName == null || filterName.equals("")) { throw new IllegalArgumentException(sm.getString( "applicationContext.invalidFilterName", filterName)); } if (!context.getState().equals(LifecycleState.STARTING_PREP)) { //TODO Spec breaking enhancement to ignore this restriction throw new IllegalStateException( sm.getString("applicationContext.addFilter.ise", getContextPath())); } FilterDef filterDef = context.findFilterDef(filterName); // Assume a 'complete' FilterRegistration is one that has a class and // a name if (filterDef == null) { filterDef = new FilterDef(); filterDef.setFilterName(filterName); context.addFilterDef(filterDef); } else { if (filterDef.getFilterName() != null && filterDef.getFilterClass() != null) { return null; } } if (filter == null) { filterDef.setFilterClass(filterClass); } else { filterDef.setFilterClass(filter.getClass().getName()); filterDef.setFilter(filter); } return new ApplicationFilterRegistration(filterDef, context); }
- 首先将上层的
-
回到本节步骤5第二步,
AbstractFilterRegistrationBean#configure()
方法会将Filter 过滤器
和它应该拦截的URL
通过新建的注册器对象ApplicationFilterRegistration#addMappingForUrlPatterns()
方法添加到 Tomcat 的Context
容器中protected void configure(FilterRegistration.Dynamic registration) { super.configure(registration); EnumSet<DispatcherType> dispatcherTypes = this.dispatcherTypes; if (dispatcherTypes == null) { dispatcherTypes = EnumSet.of(DispatcherType.REQUEST); } Set<String> servletNames = new LinkedHashSet<>(); for (ServletRegistrationBean<?> servletRegistrationBean : this.servletRegistrationBeans) { servletNames.add(servletRegistrationBean.getServletName()); } servletNames.addAll(this.servletNames); if (servletNames.isEmpty() && this.urlPatterns.isEmpty()) { registration.addMappingForUrlPatterns(dispatcherTypes, this.matchAfter, DEFAULT_URL_MAPPINGS); } else { if (!servletNames.isEmpty()) { registration.addMappingForServletNames(dispatcherTypes, this.matchAfter, StringUtils.toStringArray(servletNames)); } if (!this.urlPatterns.isEmpty()) { registration.addMappingForUrlPatterns(dispatcherTypes, this.matchAfter, StringUtils.toStringArray(this.urlPatterns)); } } }
-
ApplicationFilterRegistration#addMappingForUrlPatterns()
方法如下,可以看到此处新建了一个FilterMap
对象,并随后将其添加到了Context
容器中。在网络请求到来时,Tomcat 将会把 Servlet 和对应的 Filter 封装到一个ApplicationFilterChain
执行链对象中,先执行过滤器逻辑再执行 Servlet 逻辑,读者不清楚可参考 Tomcat源码分析(4)-网络请求处理,至此Spring MVC 框架
中 Tomcat 服务器的整合分析告一段落public void addMappingForUrlPatterns( EnumSet<DispatcherType> dispatcherTypes, boolean isMatchAfter, String... urlPatterns) { FilterMap filterMap = new FilterMap(); filterMap.setFilterName(filterDef.getFilterName()); if (dispatcherTypes != null) { for (DispatcherType dispatcherType : dispatcherTypes) { filterMap.setDispatcher(dispatcherType.name()); } } if (urlPatterns != null) { // % decoded (if necessary) using UTF-8 for (String urlPattern : urlPatterns) { filterMap.addURLPattern(urlPattern); } if (isMatchAfter) { context.addFilterMap(filterMap); } else { context.addFilterMapBefore(filterMap); } } // else error? }