本文這裡環境是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
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;
}
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
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);
}