SpringMVC源码阅读-服务器启动

在没有使用SpringMVC之前,我们开发Web应用是使用Servlet+Jsp的方式,后来又演化成HTML+AJAX+Servlet的方式。XiXi对于原生的JavaWeb那套开发也是相当不熟悉,因为我刚毕业就是SpringBoot、SpringCloud微服务那套东西了,但是毕竟大学时候也用原生的JavaWeb做过几个课设,所以也略知一二,但是知道的确实不多。 在使用SpringMVC之后,我们就不用写Servlet了,只要简单的写一个标注有@Controller@RestController的类,在类中定义相关处理方法并标识@GetMapping@RequestMapping等注解,请求会自动的找到我们这个方法,执行方法中的逻辑。在Servlet开发中,解析请求参数、解析响应参数等等一系列麻烦的操作,SpringMVC都帮我们处理,只需要我们添加相应的注解即可。 大家在感受到框架带来的便利时,是不是也对其背后的原理产生强烈的好奇,那么今天,我们就开始研究一下SpringMVC是如何让我们可以这么方便的开发一个后端接口的。

原理概述

SpringMVC框架也是要以Servlet为基础的,也就是说,请求发送,Tomcat服务器接收并最终到Servlet组件去处理,这段流程不管你用不用SpringMVC框架,都是一样。SpringMVC帮我们写好了一个Servlet组件(DispatcherServlet)去处理所有请求,此时处理请求需要运用到一些其他类,这些类用于解析入参、逻辑处理(用户自定义的Controller)、解析出参等。这些类都在一个“百宝箱”中,这个“百宝箱”就是我们的IOC容器(Spring应用上下文)。SpringMVC源码阅读-服务器启动

图中描述的就是我想说的一个原理 SpringMVC框架实现原理本质上:一段请求处理逻辑+ApplicationContext

  • 处理逻辑:统一所有请求的处理逻辑
  • ApplicationContext:获取处理请求时用到的类

关于请求到Tomcat服务器最后到的Servlet处理这段逻辑,属于Tomcat源码内容,XiXi这里也不会,我总不能再把Tomcat的源码读一遍吧。下图是我找的一张Tomcat架构图。SpringMVC源码阅读-服务器启动

图中我们注意一下,一个Tomcat服务器可以部署多个应用(每个应用对应一个Context),每个Context里都有多个Servlet

Spring上下文何时被创建

文中服务器以Tomcat为例

我们知道DispatcherServlet需要一个Spring应用上下文,Tomcat服务器启动后,这个Spring应用上下文在哪里被创建呢? 这个还要分两种情况讨论,非SpringBoot方式和SpringBoot方式

  • 非SpringBoot方式:应用写好后打成war包放在Tomcat服务器上,Tomcat服务器启动后创建Spring应用上下文
  • SpringBoot方式:先创建Spring应用上下文,再启动内嵌Tomcat服务器

可以看到非SpringBoot方式和SpringBoot方式,服务器和Spring应用上下文的创建顺序还是不同的。下面我们来具体分析一下

非SpringBoot方式

非SpringBoot方式,Tomcat服务器启动后,触发ServletContextListener#contextInitialized,因此SpringMVC框架在这个地方存在创建Spring应用上下文的机会。另外Servlet#init()会在Servlet初始化时调用,SpringMVC框架在这个地方也存在创建Spring应用上下文的机会。

JavaWeb基础知识

@WebListener
public class ServletContextListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("servletContext 启动");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("servletContext 销毁");
    }
}

Tomcat服务启动后,contextInitialized方法被调用

@WebServlet(name = "MyServlet", urlPatterns = { "/myservlet" }, loadOnStartup = 1)
public class MyServlet extends HttpServlet {
    @Override
    public void init(ServletConfig config) throws ServletException {
        System.out.println("init ......");
    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ServletContext servletContext = req.getServletContext();
    }
}

Servlet的init()会在创建时被调用1次

web.xml方式

<web-app>

 <listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
 </listener>

 <context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>/WEB-INF/root-context.xml</param-value>
 </context-param>

 <servlet>
  <servlet-name>app1</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <init-param>
   <param-name>contextConfigLocation</param-name>
   <param-value>/WEB-INF/app1-context.xml</param-value>
  </init-param>
  <load-on-startup>1</load-on-startup>
 </servlet>

 <servlet-mapping>
  <servlet-name>app1</servlet-name>
  <url-pattern>/app1/*</url-pattern>
 </servlet-mapping>

</web-app>

ContextLoaderListener#contextInitialized

<web-app>

  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/root-context.xml</param-value>
  </context-param>

 //..............
</web-app>

在Spring官方的web.xml配置中,可以看到配置了一个监听器和一段context-param,这两处就是用于创建Spring应用上下文的。下面我们一起来看一下

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {

    public ContextLoaderListener() {
    }
    public ContextLoaderListener(WebApplicationContext context) {
        super(context);
    }

    @Override
    public void contextInitialized(ServletContextEvent event) {
        initWebApplicationContext(event.getServletContext());
    }


    @Override
    public void contextDestroyed(ServletContextEvent event) {
        closeWebApplicationContext(event.getServletContext());
        ContextCleanupListener.cleanupAttributes(event.getServletContext());
    }

}

contextInitialized()->initWebApplicationContext

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
    if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
        throw new IllegalStateException(
            "Cannot initialize context because there is already a root application context present - " +
            "check whether you have multiple ContextLoader* definitions in your web.xml!");
    }
    
 //........
    
    try {
        if (this.context == null) {
            this.context = createWebApplicationContext(servletContext);
        }
        if (this.context instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
            if (!cwac.isActive()) {
                if (cwac.getParent() == null) {
                    ApplicationContext parent = loadParentContext(servletContext);
                    cwac.setParent(parent);
                }
                configureAndRefreshWebApplicationContext(cwac, servletContext);
            }
        }
        servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
    
        ClassLoader ccl = Thread.currentThread().getContextClassLoader();
        if (ccl == ContextLoader.class.getClassLoader()) {
            currentContext = this.context;
        }
        else if (ccl != null) {
            currentContextPerThread.put(ccl, this.context);
        }
    
        return this.context;
    }
    catch (RuntimeException | Error ex) {
        logger.error("Context initialization failed", ex);
        servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
        throw ex;
    }
}

代码如上,这里会创建Root-ApplicationContext,具体创建Spring应用上下文逻辑又调用了createWebApplicationContext,创建Root-ApplicationContext后被塞到ServletContext的属性中, 属性名称为:WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE

protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
Class<?> contextClass = determineContextClass(sc);
    if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
        throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
                                              "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
    }
    return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}

上面这段代码是反射的方式创建应用上下文Root-Application

protected void configureAndRefreshWebApplicationContext(
    ConfigurableWebApplicationContext wac,
    ServletContext sc)
 
{
    if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
        String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
        if (idParam != null) {
            wac.setId(idParam);
        }
        else {
            wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
                      ObjectUtils.getDisplayString(sc.getContextPath()));
        }
    }
 //应用上下文 设置了 ServletContext
    wac.setServletContext(sc);
    String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
    if (configLocationParam != null) {
        wac.setConfigLocation(configLocationParam);
    }

    ConfigurableEnvironment env = wac.getEnvironment();
    if (env instanceof ConfigurableWebEnvironment) {
        ((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
    }

    customizeContext(sc, wac);
    wac.refresh();
}

这段代码是完成了Root-ApplicationContext的刷新

可以看到,最终Root-ApplicationContextServletContext是做到了一个你中有我,我中有你的状态

HttpServletBean#init()

<web-app>

 //.....................
 <servlet>
  <servlet-name>app1</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <init-param>
   <param-name>contextConfigLocation</param-name>
   <param-value>/WEB-INF/app1-context.xml</param-value>
  </init-param>
  <load-on-startup>1</load-on-startup>
 </servlet>

 <servlet-mapping>
  <servlet-name>app1</servlet-name>
  <url-pattern>/app1/*</url-pattern>
 </servlet-mapping>

</web-app>
 @Override
 public final void init() throws ServletException {

  // Set bean properties from init parameters.
  PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
  if (!pvs.isEmpty()) {
   try {
    BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
    ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
    bw.registerCustomEditor(Resource.classnew ResourceEditor(resourceLoadergetEnvironment()));
    initBeanWrapper(bw);
    bw.setPropertyValues(pvs, true);
   }
   catch (BeansException ex) {
    if (logger.isErrorEnabled()) {
     logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
    }
    throw ex;
   }
  }

  // Let subclasses do whatever initialization they like.
  initServletBean();
 }

该方法调用了initServletBean()

@Override
protected final void initServletBean() throws ServletException {
    try {
        this.webApplicationContext = initWebApplicationContext();
        initFrameworkServlet();
    }
    catch (ServletException | RuntimeException ex) {
        logger.error("Context initialization failed", ex);
        throw ex;
    }
}

可以清除的看到FrameworkServlet#initServletBean会继续调用initWebApplicationContext()

protected WebApplicationContext initWebApplicationContext() {
  WebApplicationContext rootContext =
    WebApplicationContextUtils.getWebApplicationContext(getServletContext());
  WebApplicationContext wac = null;

  if (this.webApplicationContext != null) {
   // A context instance was injected at construction time -> use it
   wac = this.webApplicationContext;
   if (wac instanceof ConfigurableWebApplicationContext) {
    ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
    if (!cwac.isActive()) {
     // The context has not yet been refreshed -> provide services such as
     // setting the parent context, setting the application context id, etc
     if (cwac.getParent() == null) {
      // The context instance was injected without an explicit parent -> set
      // the root application context (if any; may be null) as the parent
      cwac.setParent(rootContext);
     }
     configureAndRefreshWebApplicationContext(cwac);
    }
   }
  }
  if (wac == null) {
   wac = findWebApplicationContext();
  }
  if (wac == null) {
   // No context instance is defined for this servlet -> create a local one
   wac = createWebApplicationContext(rootContext);
  }
  if (!this.refreshEventReceived) {
   synchronized (this.onRefreshMonitor) {
    onRefresh(wac);
   }
  }

  if (this.publishContext) {
   String attrName = getServletContextAttributeName();
   getServletContext().setAttribute(attrName, wac);
  }
  return wac;
 }

最终进行了一个DispatcherServlet应用上下文的创建

编程式

Servlet3.0开始可以不通过web.xml配置web应用,因此出现了纯编程式的Web应用配置方式。

public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

 @Override
 protected Class<?>[] getRootConfigClasses() {
  return new Class<?>[] { RootConfig.class };
 }

 @Override
 protected Class<?>[] getServletConfigClasses() {
  return new Class<?>[] { AppConfig.class };
 }

 @Override
 protected String[] getServletMappings() {
  return new String[] { "/" };
 }

 @Override
 protected Filter[] getServletFilters() {
  return new Filter[] {new LoggerFilter()};
 }
}

这种方式应用上下文的创建方式和web.xml不同,其核心是Servlet3.0出现的ServletContainerInitializer可以用于注册ServletListenerFilter。这里我就不细说了。

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer 
{
 @Override
 public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
   throws ServletException 
{
     //.....................
  for (WebApplicationInitializer initializer : initializers) {
   initializer.onStartup(servletContext);
  }
 }

}

可以看到循环调用了WebApplicationInitializer#onStartup,我们看下AbstractContextLoaderInitializer,它是WebApplicationInitializer实现类

public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer {

 /** Logger available to subclasses. */
 protected final Log logger = LogFactory.getLog(getClass());


 @Override
 public void onStartup(ServletContext servletContext) throws ServletException {
  registerContextLoaderListener(servletContext);
 }
    
 protected void registerContextLoaderListener(ServletContext servletContext) {
  WebApplicationContext rootAppContext = createRootApplicationContext();
  if (rootAppContext != null) {
   ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
   listener.setContextInitializers(getRootApplicationContextInitializers());
   servletContext.addListener(listener);
  }
  else {
   logger.debug("No ContextLoaderListener registered, as " +
     "createRootApplicationContext() did not return an application context");
  }
 }
 @Nullable
 protected abstract WebApplicationContext createRootApplicationContext();
 @Nullable
 protected ApplicationContextInitializer<?>[] getRootApplicationContextInitializers() {
  return null;
 }

}

可以看到先创建一个Spring应用上下文放到ContextLoaderListener中,随后ContextLoaderListener会执行容器启动的方法contextInitialized完成Spring应用上下文设置到ServletContext,对于创建应用上下文,我们看下AbstractDispatcherServletInitializerAbstractAnnotationConfigDispatcherServletInitializer的实现

public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer {

 public static final String DEFAULT_SERVLET_NAME = "dispatcher";

 @Override
 public void onStartup(ServletContext servletContext) throws ServletException {
  super.onStartup(servletContext);
  registerDispatcherServlet(servletContext);
 }

 protected void registerDispatcherServlet(ServletContext servletContext) {
  String servletName = getServletName();
  Assert.hasLength(servletName, "getServletName() must not return null or empty");

  WebApplicationContext servletAppContext = createServletApplicationContext();
  Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");

  FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
  Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
  dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());

  ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
  if (registration == null) {
   throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. " +
     "Check if there is another servlet registered under the same name.");
  }

  registration.setLoadOnStartup(1);
  registration.addMapping(getServletMappings());
  registration.setAsyncSupported(isAsyncSupported());

  Filter[] filters = getServletFilters();
  if (!ObjectUtils.isEmpty(filters)) {
   for (Filter filter : filters) {
    registerServletFilter(servletContext, filter);
   }
  }

  customizeRegistration(registration);
 }


}

这个类创建了DispatcherServlet并设置了Spring应用上下文

public abstract class AbstractAnnotationConfigDispatcherServletInitializer
  extends AbstractDispatcherServletInitializer 
{

 @Override
 @Nullable
 protected WebApplicationContext createRootApplicationContext() {
  Class<?>[] configClasses = getRootConfigClasses();
  if (!ObjectUtils.isEmpty(configClasses)) {
   AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
   context.register(configClasses);
   return context;
  }
  else {
   return null;
  }
 }

 @Override
 protected WebApplicationContext createServletApplicationContext() {
  AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
  Class<?>[] configClasses = getServletConfigClasses();
  if (!ObjectUtils.isEmpty(configClasses)) {
   context.register(configClasses);
  }
  return context;
 }


 @Nullable
 protected abstract Class<?>[] getRootConfigClasses();
 @Nullable
 protected abstract Class<?>[] getServletConfigClasses();

}

这个类的createRootApplicationContext()创建了根上下文被AbstractContextLoaderInitializer调用

好的兄弟们,我们可以总结一下,编程式的Spring应用上下文设置到ServletContextDispatcherServlet中的方式主要是在WebApplicationInitializer实现类中new好,给到ContextLoaderListenerDispatcherServlet的。下面我们看看SpringBoot又是怎么去设置的。

SpringBoot方式

SpringBoot在启动Web应用时,使用的是AnnotationConfigServletWebServerApplicationContext,这个应用上下文在refresh()方法中的onRefresh()完成了内嵌服务器的创建和启动工作。就是在这个时候应用上下文被设置到ServletContext中,后续DispatcherServlet启动,使用的是ServletContext中的应用上下文

JavaWeb基础知识

在了解SpringBoot方式前,我们需要先介绍一个接口ServletContainerInitializer,在web容器启动时会调用接口的onStartup()方法,这个方法里可以注册Filter、Servlet、Listener,是Servlet3.0引入的接口。

package javax.servlet;
public interface ServletContainerInitializer {
    public void onStartup(Set<Class<?>> c, ServletContext ctx)
    throws ServletException

}

https://blog.csdn.net/lixinlong_8888/article/details/133002147

下面我给大家简单演示一下用法

  1. 在自己工程的包下写一个ServletContainerInitializer实现类
@HandlesTypes(HttpServlet.class)
public class MyServletContainerInitializer implements ServletContainerInitializer 
{
    @Override
    public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException {
        System.out.println(c);
        System.out.println("hello");
    }
}

服务器启动后,会调用onStartupSet<Class<?>> c:会根据@HandlesTypes(HttpServlet.class)指定的类型,将应用中所有类型加载到c入参中ServletContext:Servlet上下文

  1. 在resources目录下创建如下结构
SpringMVC源码阅读-服务器启动
image.png
  1. javax.servlet.ServletContainerInitializer的文件内容就是自己的那个实现类
com.gao.MyServletContainerInitializer

Spring应用上下文设置到ServletContext

一切故事都从onRefresh()说起

 @Override
 protected void onRefresh() {
  super.onRefresh();
  try {
   createWebServer();
  }
  catch (Throwable ex) {
   throw new ApplicationContextException("Unable to start web server", ex);
  }
 }

直接进入createWebServer()

 private void createWebServer() {
  WebServer webServer = this.webServer;
  ServletContext servletContext = getServletContext();
  if (webServer == null && servletContext == null) {
   ServletWebServerFactory factory = getWebServerFactory();
   this.webServer = factory.getWebServer(getSelfInitializer());
   getBeanFactory().registerSingleton("webServerGracefulShutdown",
     new WebServerGracefulShutdownLifecycle(this.webServer));
   getBeanFactory().registerSingleton("webServerStartStop",
     new WebServerStartStopLifecycle(thisthis.webServer));
  }
  else if (servletContext != null) {
   try {
    getSelfInitializer().onStartup(servletContext);
   }
   catch (ServletException ex) {
    throw new ApplicationContextException("Cannot initialize servlet context", ex);
   }
  }
  initPropertySources();
 }
  • 如果存在ServletContext则直接调用getSelfInitializer().onStartup(servletContext)
  • 不存在ServletContext则会先获取工厂,再进行创建,入参还是getSelfInitializer()
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
    return this::selfInitialize;
}

private void selfInitialize(ServletContext servletContext) throws ServletException {
    prepareWebApplicationContext(servletContext);
    registerApplicationScope(servletContext);
    WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
    for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
        beans.onStartup(servletContext);
    }
}

返回了一个函数式接口,其方法实现是selfInitialize,工厂的获取咱们就不看了,下面我们进入this.webServer = factory.getWebServer(getSelfInitializer());通过工厂创建webServer

 @Override
 public WebServer getWebServer(ServletContextInitializer... initializers) {
  if (this.disableMBeanRegistry) {
   Registry.disableRegistry();
  }
  Tomcat tomcat = new Tomcat();
  File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
  tomcat.setBaseDir(baseDir.getAbsolutePath());
  for (LifecycleListener listener : this.serverLifecycleListeners) {
   tomcat.getServer().addLifecycleListener(listener);
  }
  Connector connector = new Connector(this.protocol);
  connector.setThrowOnFailure(true);
  tomcat.getService().addConnector(connector);
  customizeConnector(connector);
  tomcat.setConnector(connector);
  tomcat.getHost().setAutoDeploy(false);
  configureEngine(tomcat.getEngine());
  for (Connector additionalConnector : this.additionalTomcatConnectors) {
   tomcat.getService().addConnector(additionalConnector);
  }
  prepareContext(tomcat.getHost(), initializers);
  return getTomcatWebServer(tomcat);
 }

说实话不知道Tomcat架构根本看懂上面的代码,反正就是在构建一个tomcat,咱们看看我们的入参在哪被用,继续进入prepareContext

 protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
     ///......
  configureContext(context, initializersToUse);
  postProcessContext(context);
 }

不管实现,我们只关注我们入参合适被用到的。进入configureContext

protected void configureContext(Context context, ServletContextInitializer[] initializers) {
  TomcatStarter starter = new TomcatStarter(initializers);
     //...............
 }

后面代码咱们不管,它用入参创建了一个TomcatStarter。兄弟们我们看看这个类

class TomcatStarter implements ServletContainerInitializer {

 private static final Log logger = LogFactory.getLog(TomcatStarter.class);

 private final ServletContextInitializer[] initializers;

 private volatile Exception startUpException;

 TomcatStarter(ServletContextInitializer[] initializers) {
  this.initializers = initializers;
 }

 @Override
 public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException {
  try {
   for (ServletContextInitializer initializer : this.initializers) {
    initializer.onStartup(servletContext);
   }
  }
  catch (Exception ex) {
   this.startUpException = ex;
   // Prevent Tomcat from logging and re-throwing when we know we can
   // deal with it in the main thread, but log for information here.
   if (logger.isErrorEnabled()) {
    logger.error("Error starting Tomcat context. Exception: " + ex.getClass().getName() + ". Message: "
      + ex.getMessage());
   }
  }
 }

}

兄弟们看到了吧,知道我为啥在JavaWeb基础中点ServletContainerInitializer接口了吧,这个接口的实现类会在web容器创建后调用。而onStartup循环调用我们的入参ServletContextInitializer#onStartup

ServletContextInitializer#onStartup

 private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
  return this::selfInitialize;
 }

 private void selfInitialize(ServletContext servletContext) throws ServletException {
  prepareWebApplicationContext(servletContext);
  registerApplicationScope(servletContext);
  WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
  for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
   beans.onStartup(servletContext);
  }
 }

兄弟们咱们也不要看全部,就看prepareWebApplicationContext的实现

 protected void prepareWebApplicationContext(ServletContext servletContext) {
  Object rootContext = servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
     //........
  try {
   servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this);
   setServletContext(servletContext);
  }
  catch (RuntimeException | Error ex) {
   servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
   throw ex;
  }
 }

是不是,将this设置到了ServletContext的属性中,并且ServletContext也通过setServletContext被设置到应用上下文中。注意这段代码在ServletWebServerApplicationContext类中,它是AnnotationConfigServletWebServerApplicationContext的父类,this就是AnnotationConfigServletWebServerApplicationContext

DispatcherServlet如何获取Spring上下文

和非SpringBoot方式的web.xml配置的情况一样,这里就不细说了,SpringBoot方式下,DispatcherServlet取到ServletContext中的应用上下文进行使用。自己并没有另外再创建了。


原文始发于微信公众号(溪溪技术笔记):SpringMVC源码阅读-服务器启动

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/207176.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!