SpringBoot中是如何创建WebServer的?

简介: SpringBoot中是如何创建WebServer的?

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

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


我们这里获取到的本质是一个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方法。



目录
相关文章
|
8月前
|
XML JSON Java
springboot如何创建并配置环境3 - 配置扩展属性(上)
springboot如何创建并配置环境3 - 配置扩展属性(上)
springboot如何创建并配置环境3 - 配置扩展属性(上)
|
8月前
|
XML 设计模式 Java
springboot如何创建并配置环境
springboot如何创建并配置环境
springboot如何创建并配置环境
|
8月前
|
Java Maven
idea中如何创建SpringBoot项目
idea中如何创建SpringBoot项目
|
8月前
|
XML 监控 Java
使用IDEA社区版如何创建SpringBoot项目?
使用IDEA社区版如何创建SpringBoot项目?
|
JSON 前端开发 Java
SpringBoot学习2:如何创建web项目
SpringBoot学习2:如何创建web项目
SpringBoot学习2:如何创建web项目
|
3月前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 实现动态路由和菜单功能,快速搭建前后端分离的应用框架
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 实现动态路由和菜单功能,快速搭建前后端分离的应用框架。首先,确保开发环境已安装必要的工具,然后创建并配置 Spring Boot 项目,包括添加依赖和配置 Spring Security。接着,创建后端 API 和前端项目,配置动态路由和菜单。最后,运行项目并分享实践心得,包括版本兼容性、安全性、性能调优等方面。
210 1
|
2月前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用。
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用。首先,创建并配置 Spring Boot 项目,实现后端 API;然后,使用 Ant Design Pro Vue 创建前端项目,配置动态路由和菜单。通过具体案例,展示了如何快速搭建高效、易维护的项目框架。
139 62
|
27天前
|
存储 JavaScript 前端开发
基于 SpringBoot 和 Vue 开发校园点餐订餐外卖跑腿Java源码
一个非常实用的校园外卖系统,基于 SpringBoot 和 Vue 的开发。这一系统源于黑马的外卖案例项目 经过站长的进一步改进和优化,提供了更丰富的功能和更高的可用性。 这个项目的架构设计非常有趣。虽然它采用了SpringBoot和Vue的组合,但并不是一个完全分离的项目。 前端视图通过JS的方式引入了Vue和Element UI,既能利用Vue的快速开发优势,
111 13
|
1月前
|
JavaScript 安全 Java
java版药品不良反应智能监测系统源码,采用SpringBoot、Vue、MySQL技术开发
基于B/S架构,采用Java、SpringBoot、Vue、MySQL等技术自主研发的ADR智能监测系统,适用于三甲医院,支持二次开发。该系统能自动监测全院患者药物不良反应,通过移动端和PC端实时反馈,提升用药安全。系统涵盖规则管理、监测报告、系统管理三大模块,确保精准、高效地处理ADR事件。
|
2月前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能。首先,确保开发环境已安装必要的工具,然后创建并配置 Spring Boot 项目,包括添加依赖和配置 Spring Security。接着,创建后端 API 和前端项目,配置动态路由和菜单。最后,运行项目并分享实践心得,帮助开发者提高开发效率和应用的可维护性。
137 2