SpringBoot中是如何创建WebServer的?

SpringBoot中是如何创建WebServer的?本文这里环境是 springboot2 2 4 RELEASE 创建 WebServer 是在 refresh 方法的 onRefresh 方法中实现的 其也是 refresh 方法体系的一个重要步骤 ServletWebSe 的 onRefresh 方法 如下所示其首先调用父类的 onRefresh 方法初始化 ThemeSource 然后调用 createWebSer 创建 WebServer java Overrideprot

本文这里环境是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); } 

② getSelfInitializer

ServletWebServerApplicationContextgetSelfInitializer方法,返回的是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; } 

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..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; } 

prepareContext

这里会实例化TomcatEmbeddedContext并对其进行配置。

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); } 

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

ServletWebServerApplicationContextprepareWebApplicationContext方法如下所示,简单来讲就是为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

ServletWebServerApplicationContextregisterApplicationScope方法如下所示,简单来讲就是(扩展)注册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); } 
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/201654.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月20日 上午8:55
下一篇 2026年3月20日 上午8:55


相关推荐

  • Apache配置虚拟主机出现forbidden的问题

    Apache配置虚拟主机出现forbidden的问题1.httpd.conf中配置过网站根目录该配置文件的250行左右,修改了根目录①httpd.conf配置文件中这两处都要修改②httpd-vhosts.conf配置文件中检查DocumentRoot是不是有默认值,有的话要修改根目录③重启Apache服务,清空浏览器缓存2.httpd-vhosts.conf中配置过虚拟主机httpd.conf配置中文件中找到VirtualHost,…

    2025年11月22日
    6
  • python接口自动化实战(框架)

    python接口自动化实战(框架)    python接口测试的原理,就不解释了,百度一大堆。   先看目录,可能这个框架比较简单,但是麻雀虽小五脏俱全。各个文件夹下的文件如下:一.理清思路   我这个自动化框架要实现什么   1.从excel里面提取测试用例   2.测试报告的输出,并且测试报告得包括执行的测试用例的数量、成功的数量、失败的数量以及哪条成功了,失败的是哪一个,失败的原因是什么;测试结果的总体情况通过图表…

    2025年5月28日
    5
  • mysql文件导入sqlserver_mysql导入sql文件命令

    mysql文件导入sqlserver_mysql导入sql文件命令问题来源有的时候,在使用MySQL数据库建表时,可能不需要直接在mysql数据库中建表,而需要导入外部已有的数据库表文件,方便我们使用。那么导入的方法呢?这里介绍一个很普遍也很简单的方法,步骤如下:导入步骤打开MySQL数据库,黑窗界面,如图:这里输入密码‘root’,回车。。。先确定你要建立的数据库名字,比如这里我新建数据库名字叫“house-01”,如下图。(说明:如果sql文件的内容中有创建数据库的语句,或者想将表存放在已有的数据库,在这里就不需要再创建数据库。即直接使用已经

    2022年10月2日
    4
  • 南京大学LAMDA面经汇总

    南京大学LAMDA面经汇总节选自 https://www.jianshu.com/p/7640174a15561.南大计算机网址:南京大学计算机系2016“本科生开放日”申请流程时间:5月13日-5月15日入营条件:985院校的话,绩点排名前5%基本可以入营吃住补助:LAMDA实验室报销车票,住宿费,但是南大不报销车票,但管吃管住,住的很高级的宾馆,条件特别好。参营记录:南大的夏令营是开的最早的一个计算机夏令营,正因为开…

    2022年6月9日
    134
  • WSDL说明

    WSDL说明WSDL 是一种 XMLApplicati 他将 Web 服务描述定义为一组服务访问点 客户端可以通过这些服务访问点对包含面向文档信息或面向过程调用的服务进行访问 类似远程过程调用 WSDL 首先对访问的操作和访问时使用的请求 响应消息进行抽象描述 然后将其绑定到具体的传输协议和消息格式上 以最终定义具体部署的服务访问点 相关的具体部署的服务访问点通过组合就成为抽象的 Web 服务 WSDL

    2026年3月17日
    4
  • HTTP响应代码(Response Status Code)中文详解

    HTTP响应代码(Response Status Code)中文详解

    2021年9月4日
    154

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号