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

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

如下所示,将会挨个触发这5个的onStartup方法。
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

流烟默

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值