本文这里环境是springboot 2.2.4.RELEASE。创建WebServer是在refresh方法的onRefresh方法中实现的。其也是refresh方法体系的一个重要步骤。
ServletWebServerApplicationContext的onRefresh方法。如下所示其首先调用父类的onRefresh方法初始化ThemeSource,然后调用createWebServer创建WebServer。
@Override protected void onRefresh() { super.onRefresh(); try { createWebServer(); } catch (Throwable ex) { throw new ApplicationContextException("Unable to start web server", ex); } } //GenericWebApplicationContext @Override protected void onRefresh() { this.themeSource = UiApplicationContextUtils.initThemeSource(this); }
【1】createWebServer
ServletWebServerApplicationContext的createWebServer方法如下。
private void createWebServer() { WebServer webServer = this.webServer; // 获取的是GenericWebApplicationContext的servletContext ServletContext servletContext = getServletContext(); if (webServer == null && servletContext == null) { // 本文环境获取的是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(); }
关于initPropertySources();方法可以参考博文:Spring中refresh分析之onRefresh方法详解 。
① 获取WebServerFactory
如下所示,从容器中获取ServletWebServerFactory类型的bean,唯一一个,否则抛出异常。本文环境获取的是tomcatServletWebServerFactory。
protected ServletWebServerFactory getWebServerFactory() { // Use bean names so that we don't consider the hierarchy String[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class); if (beanNames.length == 0) { throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to missing " + "ServletWebServerFactory bean."); } if (beanNames.length > 1) { throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple " + "ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames)); } return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class); }
这里就有疑问了,为什么获取到的是tomcatServletWebServerFactory而不是undertowServletWebServerFactory?那么需要看一下这两个服务:ServletWebServerFactoryAutoConfiguration和ServletWebServerFactoryConfiguration。
② getSelfInitializer
ServletWebServerApplicationContext
的getSelfInitializer
方法,返回的是ServletContextInitializer
。
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() { return this::selfInitialize; }
看到this::selfInitialize
是不是比较迷糊?典型的java8的lambda写法。我们看一下ServletContextInitializer 可能就明白了。
如下所示,其是一个函数式接口,只有一个onStartup方法。函数式接口(有且仅有一个抽象方法的接口)可以使用lambda式的写法。
@FunctionalInterface public interface ServletContextInitializer { // 初始化过程中,使用给定的servlets、filters、listeners //context-params and attributes necessary配置ServletContext void onStartup(ServletContext servletContext) throws ServletException; }
我们这里获取到的本质是一个lambda,参数则是当前this,如下图所示:
this::selfInitialize
中的selfInitialize则指的是ServletWebServerApplicationContext的selfInitialize方法。
this指的是AnnotationConfigServletWebServerApplicationContext,其继承于ServletWebServerApplicationContext
private void selfInitialize(ServletContext servletContext) throws ServletException { prepareWebApplicationContext(servletContext); registerApplicationScope(servletContext); WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext); for (ServletContextInitializer beans : getServletContextInitializerBeans()) { beans.onStartup(servletContext); } }
其实换成匿名类的写法则是:
new ServletContextInitializer() { @Override public void onStartup(ServletContext servletContext) throws ServletException { selfInitialize(servletContext); } };
【2】getWebServer
本文这里是TomcatServletWebServerFactory的getWebServer方法。
@Override public WebServer getWebServer(ServletContextInitializer... initializers) { if (this.disableMBeanRegistry) { // registry = new NoDescriptorRegistry(); Registry.disableRegistry(); } //实例化Tomcat Tomcat tomcat = new Tomcat(); //获取临时路径 C:\Users\12746\AppData\Local\Temp\tomcat.9051357942624975261.8188 File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat"); //设置基础路径 tomcat.setBaseDir(baseDir.getAbsolutePath()); //实例化Connector 并进行配置 Connector connector = new Connector(this.protocol); connector.setThrowOnFailure(true); //这里会实例化server service tomcat.getService().addConnector(connector); customizeConnector(connector); //对Connector做配置比如Protocol、URIEncoding tomcat.setConnector(connector); //这里会实例化Engine、Host tomcat.getHost().setAutoDeploy(false); configureEngine(tomcat.getEngine()); for (Connector additionalConnector : this.additionalTomcatConnectors) { tomcat.getService().addConnector(additionalConnector); } prepareContext(tomcat.getHost(), initializers); return getTomcatWebServer(tomcat); }
getService
getService首先会触发getServer然后获取service。getServer如下所示会实例化Server并对其进行配置。
public Service getService() { return getServer().findServices()[0]; } public Server getServer() { if (server != null) { return server; } System.setProperty("catalina.useNaming", "false"); // 实例化 server server = new StandardServer(); // 对basedir做处理 initBaseDir(); // Set configuration source ConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(new File(basedir), null)); // 为server设置port和service server.setPort( -1 ); //实例化service Service service = new StandardService(); service.setName("Tomcat"); server.addService(service); return server; }
getService
getService首先会触发getServer然后获取service。getServer如下所示会实例化Server并对其进行配置。
public Service getService() { return getServer().findServices()[0]; } public Server getServer() { if (server != null) { return server; } System.setProperty("catalina.useNaming", "false"); // 实例化 server server = new StandardServer(); // 对basedir做处理 initBaseDir(); // Set configuration source ConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(new File(basedir), null)); // 为server设置port和service server.setPort( -1 ); //实例化service Service service = new StandardService(); service.setName("Tomcat"); server.addService(service); return server; }
prepareContext
这里会实例化TomcatEmbeddedContext并对其进行配置。
proteprotected 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); }
getTomcatWebServer
这个方法很简单,只是直接实例化了TomcatWebServer返回。其构造方法触发了initialize,这会引起后续一系列动作,包括tomcat.start。
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(); }
【3】selfInitialize
获取到TomcatWebServer后,就触发了selfInitialize方法。这里servletContext其实是获取了ApplicationContext的一个门面/外观–ApplicationContextCade。
// ServletWebServerApplicationContext private void selfInitialize(ServletContext servletContext) throws ServletException { prepareWebApplicationContext(servletContext); registerApplicationScope(servletContext); WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext); for (ServletContextInitializer beans : getServletContextInitializerBeans()) { beans.onStartup(servletContext); } }
① prepareWebApplicationContext
ServletWebServerApplicationContext的prepareWebApplicationContext方法如下所示,简单来讲就是为servletContext设置根容器属性并为当前应用上下文ApplicationContext设置servletContext引用。
protected void prepareWebApplicationContext(ServletContext servletContext) { //尝试从servletContext中获取rootContext Object rootContext = servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); if (rootContext != null) { if (rootContext == this) { throw new IllegalStateException( "Cannot initialize context because there is already a root application context present - " + "check whether you have multiple ServletContextInitializers!"); } return; } Log logger = LogFactory.getLog(ContextLoader.class); // 这个日志是不是很熟悉?! servletContext.log("Initializing Spring embedded WebApplicationContext"); try { //向servletContext设置属性 ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this); if (logger.isDebugEnabled()) { logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" + WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]"); } // 为ApplicationContext设置servletContext引用 setServletContext(servletContext); if (logger.isInfoEnabled()) { long elapsedTime = System.currentTimeMillis() - getStartupDate(); logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms"); } } catch (RuntimeException | Error ex) { logger.error("Context initialization failed", ex); servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex); throw ex; } }
② registerApplicationScope
ServletWebServerApplicationContext的registerApplicationScope方法如下所示,简单来讲就是(扩展)注册scope-application。这里会实例化一个ServletContextScope (包装了servletContext),然后注册到BeanFactory中并为servletContext设置属性。
private void registerApplicationScope(ServletContext servletContext) { ServletContextScope appScope = new ServletContextScope(servletContext); // application getBeanFactory().registerScope(WebApplicationContext.SCOPE_APPLICATION, appScope); // Register as ServletContext attribute, for ContextCleanupListener to detect it. servletContext.setAttribute(ServletContextScope.class.getName(), appScope); }
我们在Spring中refresh分析之postProcessBeanFactory方法详解提到了request-RequestScope,session–SessionScope的注册,本文这里注册了application-ServletContextScope注册。
③ registerEnvironmentBeans
WebApplicationContextUtils的registerEnvironmentBeans方法。
public static void registerEnvironmentBeans(ConfigurableListableBeanFactory bf, @Nullable ServletContext sc) { registerEnvironmentBeans(bf, sc, null); } public static void registerEnvironmentBeans(ConfigurableListableBeanFactory bf, @Nullable ServletContext servletContext, @Nullable ServletConfig servletConfig) { //将servletContext作为单例注册容器 if (servletContext != null && !bf.containsBean(WebApplicationContext.SERVLET_CONTEXT_BEAN_NAME)) { bf.registerSingleton(WebApplicationContext.SERVLET_CONTEXT_BEAN_NAME, servletContext); } // 将servletConfig 作为单例注册容器本文这里没有触发 if (servletConfig != null && !bf.containsBean(ConfigurableWebApplicationContext.SERVLET_CONFIG_BEAN_NAME)) { bf.registerSingleton(ConfigurableWebApplicationContext.SERVLET_CONFIG_BEAN_NAME, servletConfig); } // String CONTEXT_PARAMETERS_BEAN_NAME = "contextParameters"; if (!bf.containsBean(WebApplicationContext.CONTEXT_PARAMETERS_BEAN_NAME)) { Map<String, String> parameterMap = new HashMap<>(); if (servletContext != null) { // 获取servletContextd的初始化参数 Enumeration<?> paramNameEnum = servletContext.getInitParameterNames(); while (paramNameEnum.hasMoreElements()) { String paramName = (String) paramNameEnum.nextElement(); parameterMap.put(paramName, servletContext.getInitParameter(paramName)); } } // 本文这里servletConfig 为null if (servletConfig != null) { // // 获取servletConfig的初始化参数 Enumeration<?> paramNameEnum = servletConfig.getInitParameterNames(); while (paramNameEnum.hasMoreElements()) { String paramName = (String) paramNameEnum.nextElement(); parameterMap.put(paramName, servletConfig.getInitParameter(paramName)); } } // 将contextParameters作为单例注册到容器 bf.registerSingleton(WebApplicationContext.CONTEXT_PARAMETERS_BEAN_NAME, Collections.unmodifiableMap(parameterMap)); } // String CONTEXT_ATTRIBUTES_BEAN_NAME = "contextAttributes"; if (!bf.containsBean(WebApplicationContext.CONTEXT_ATTRIBUTES_BEAN_NAME)) { Map<String, Object> attributeMap = new HashMap<>(); if (servletContext != null) { Enumeration<?> attrNameEnum = servletContext.getAttributeNames(); while (attrNameEnum.hasMoreElements()) { String attrName = (String) attrNameEnum.nextElement(); attributeMap.put(attrName, servletContext.getAttribute(attrName)); } } // 将contextAttributes作为单例注册到容器 bf.registerSingleton(WebApplicationContext.CONTEXT_ATTRIBUTES_BEAN_NAME, Collections.unmodifiableMap(attributeMap)); } }
④ 触发ServletContextInitializer的onStartup
如下所示,这里会获取ServletContextInitializer的所有实例,遍历触发其onStartup方法。
for (ServletContextInitializer beans : getServletContextInitializerBeans()) { beans.onStartup(servletContext); }
如下所示,将会挨个触发这5个的onStartup方法。