本文这里环境是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的servletContextServletContext servletContext = getServletContext();if (webServer == null && servletContext == null) {// 本文环境获取的是tomcatServletWebServerFactoryServletWebServerFactory 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 hierarchyString[] 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配置ServletContextvoid 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() {@Overridepublic 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();}//实例化TomcatTomcat tomcat = new Tomcat();//获取临时路径 C:\Users\12746\AppData\Local\Temp\tomcat.9051357942624975261.8188File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");//设置基础路径tomcat.setBaseDir(baseDir.getAbsolutePath());//实例化Connector 并进行配置Connector connector = new Connector(this.protocol);connector.setThrowOnFailure(true);//这里会实例化server  servicetomcat.getService().addConnector(connector);customizeConnector(connector);//对Connector做配置比如Protocol、URIEncodingtomcat.setConnector(connector);//这里会实例化Engine、Hosttomcat.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");// 实例化 serverserver = new StandardServer();//  对basedir做处理initBaseDir();// Set configuration sourceConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(new File(basedir), null));// 为server设置port和serviceserver.setPort( -1 );//实例化serviceService 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中获取rootContextObject 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_ATTRIBUTEservletContext.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);// applicationgetBeanFactory().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 为nullif (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方法。

SpringBoot中是如何创建WebServer的?相关推荐

  1. 通过 SpringBoot 中使用 lombok 实现自动创建JavaBean的get/set方法、全参/无参构造方法、toString()、equals()、hashCode()

    通过SpringBoot中使用lombok实现 先导入依赖 <dependency><groupId>org.projectlombok</groupId>< ...

  2. 在SpringBoot中使用Spring Session解决分布式会话共享问题

    在SpringBoot中使用Spring Session解决分布式会话共享问题 问题描述: 每次当重启服务器时,都会导致会员平台中已登录的用户掉线.这是因为每个用户的会话信息及状态都是由session ...

  3. SpringBoot 中 JPA 的使用

    前言 第一次使用 Spring JPA 的时候,感觉这东西简直就是神器,几乎不需要写什么关于数据库访问的代码一个基本的 CURD 的功能就出来了.下面我们就用一个例子来讲述以下 JPA 使用的基本操作 ...

  4. springboot yml怎么建常量_【Java】SpringBoot 中从application.yml中获取自定义常量

    由于这里我想通过java连接linux,connection连接需要host.port.username.password及其他路径等等.不想每次修改的时候都去改源文件,所以想写在applicatio ...

  5. WebSocket的故事(六)—— Springboot中,实现更灵活的WebSocket

    概述 WebSocket的故事系列计划分五大篇六章,旨在由浅入深的介绍WebSocket以及在Springboot中如何快速构建和使用WebSocket提供的能力.本系列计划包含如下几篇文章: 第一篇 ...

  6. springboot怎么替代jsp_如何在SpringBoot中使用JSP ?但强烈不推荐,果断改Themeleaf吧...

    做WEB项目,一定都用过JSP这个大牌.Spring MVC里面也可以很方便的将JSP与一个View关联起来,使用还是非常方便的.当你从一个传统的Spring MVC项目转入一个Spring Boot ...

  7. springboot处理参数再转发请求_SpringBoot图文教程6—SpringBoot中过滤器的使用

    有天上飞的概念,就要有落地的实现 概念十遍不如代码一遍,朋友,希望你把文中所有的代码案例都敲一遍 先赞后看,养成习惯 SpringBoot 图文系列教程技术大纲 SpringBoot 图文教程系列文章 ...

  8. SpringBoot中使用hikariCP

    本篇文章主要实现SpringBoot中使用hikariCP:  一 .使用工具  1. JDK1.8  2. springToolSuit(STS)  3. maven  二.创建项目  1.首先创建 ...

  9. SpringBoot中使用POI实现自定义Excel布局式导出

    场景 Apache POI 是创建和维护操作各种符合Office Open XML(OOXML)标准和微软的OLE 2复合文档格式(OLE2)的Java API.用它可以使用Java读取和创建,修改M ...

  10. SpringBoot中定义全局异常处理器

    场景 SpringBoot中创建全局异常处理器,使发生异常时能处理. 项目搭建专栏: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/column/info/356 ...

最新文章

  1. Java8的集合:LinkedList的实现原理
  2. 怕蛇的人如何学Python?
  3. Codility算法测验(三)
  4. php 获取文件扩展名的方法,PHP获取文件扩展名的4种方法,_PHP教程
  5. 《复杂》读书笔记(part6)--计算机中的自我复制
  6. C#.NET根据数据库中0,1返回对应代表含义“男”,“女”等问题
  7. 群英论道聚北京,共话PostgreSQL
  8. python分布式开发容易吗_Python能实现分布式的进程吗?
  9. 【POJ2752】Seek the Name, Seek the Fame(hash)
  10. Shiro框架基本知识及应用
  11. WPS Office Pro v10.8.2.6726 绿色便携专业增强版
  12. 高数 | 反函数常见问题解决思路
  13. 开发自己的DJI四旋翼无人机(A3飞控Onboard SDK和Mobile SDK介绍)
  14. 可以将 UI 设计图直接生成代码,简直太神奇了,这真的是解放了程序员生产力吗?...
  15. 我看:“不患寡而患不均” 以及 “饥寒起盗心”
  16. 高性价比40W双C口氮化镓快充方案,提供Demo板测试!
  17. SpringBoot 接口防止恶意刷新和暴力请求
  18. 解决 Hyper-V R2 虚拟网卡影响网速变慢问题
  19. 教你彻底屏蔽百度的广告与智能推荐
  20. python映射类型是什么意思_Python中字典映射类型的学习教程

热门文章

  1. 心电图分析软件_狼疮性心肌炎39例临床特点及预后分析
  2. python绘制动态心电图_长时动态心电图ECG数据可视化
  3. 计算机操作系统考试习题
  4. fastjson:map转json字符串、json字符串转map、map根据key名拿value、JSONArray转list
  5. 990.00 php,一键自动采集 体育赛事 直播网站。仿直播吧源码、仿篮球直播源码、NBA直播源码 视频网站...
  6. 微信小程序开发(一):小程序代码构成
  7. 模仿人人网客户端应用源码
  8. Java程序性能优化技巧
  9. JavaWeb案例:抽奖小游戏
  10. 10 分钟上手 Vim 编辑器,常用命令大盘点!