SpringMVC

首先我们要了解下SPI机制

Service Provider Interface ,即服务提供者接口的意思。它通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类

SpringMVC原理.jpg

就是我们启动的时候会自动加载这个类。

@Override
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
      throws ServletException {

   List<WebApplicationInitializer> initializers = Collections.emptyList();

   if (webAppInitializerClasses != null) {
      initializers = new ArrayList<>(webAppInitializerClasses.size());
      for (Class<?> waiClass : webAppInitializerClasses) {
         // Be defensive: Some servlet containers provide us with invalid classes,
         // no matter what @HandlesTypes says... 所有的非接口非抽象的WebApplicationInitializer实现类
         if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
               WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
            try {
               initializers.add((WebApplicationInitializer) //集合负责保存满足上面条件的类
                     ReflectionUtils.accessibleConstructor(waiClass).newInstance());
            }
            catch (Throwable ex) {
               throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
            }
         }
      }
   }

   if (initializers.isEmpty()) {
      servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
      return;
   }
   //下面会遍历所有满足要求的WebApplicationInitializer,调用他们的onStartup
   servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
   AnnotationAwareOrderComparator.sort(initializers);
   for (WebApplicationInitializer initializer : initializers) {
      initializer.onStartup(servletContext); //所有的 WebApplicationInitializer 的 onStartup
   }
}

然后我们去看官方文档。https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-servlet

public class AppStarter  implements WebApplicationInitializer  {
//  @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        //1、创建ioc容器
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        context.register(SpringConfig.class); //2、传入一个配置类
        //以上截止,ioc容器都没有启动
        //3、配置了 DispatcherServlet,利用Servlet的初始化机制
        DispatcherServlet servlet = new DispatcherServlet(context);
        ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet);
        registration.setLoadOnStartup(1);
        registration.addMapping("/"); //映射路径

        //启动了容器?上面的Servlet添加到 servletContext 里面以后,Tomcat就会对 DispatcherServlet进行初始化
        //<servlet></servlet>
//      servletContext.addServlet("abc",XXXX.class)

    }
}

通过继承这个WebApplicationInitializer让tomcat启动的时候就加载。

上面的Servlet添加到 servletContext 里面以后,Tomcat就会对 DispatcherServlet进行初始化

接下来是HttpServletBean的init方法

@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.class, new ResourceEditor(resourceLoader, getEnvironment()));
         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();
}

然后是FrameworkServlet的initServletBean()

@Override
protected final void initServletBean() throws ServletException {
   getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
   if (logger.isInfoEnabled()) {
      logger.info("Initializing Servlet '" + getServletName() + "'");
   }
   long startTime = System.currentTimeMillis();

   try {
      this.webApplicationContext = initWebApplicationContext(); //初始化WebIOC容器
      initFrameworkServlet();
   }
   catch (ServletException | RuntimeException ex) {
      logger.error("Context initialization failed", ex);
      throw ex;
   }

   if (logger.isDebugEnabled()) {
      String value = this.enableLoggingRequestDetails ?
            "shown which may lead to unsafe logging of potentially sensitive data" :
            "masked to prevent unsafe logging of potentially sensitive data";
      logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
            "': request parameters and headers will be " + value);
   }

   if (logger.isInfoEnabled()) {
      logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
   }
}

初始化WebIOC容器

protected WebApplicationContext initWebApplicationContext() {
   WebApplicationContext rootContext =
         WebApplicationContextUtils.getWebApplicationContext(getServletContext()); //父容器
   WebApplicationContext wac = null; //先会获取之前的 WebApplicationContext(构建父子容器)

   if (this.webApplicationContext != null) {
      // A context instance was injected at construction time -> use it
      wac = this.webApplicationContext; //当前的web-ioc容器
      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) {
      // No context instance was injected at construction time -> see if one
      // has been registered in the servlet context. If one exists, it is assumed
      // that the parent context (if any) has already been set and that the
      // user has performed any initialization such as setting the context id
      wac = findWebApplicationContext();
   }
   if (wac == null) {
      // No context instance is defined for this servlet -> create a local one
      wac = createWebApplicationContext(rootContext);
   }

   if (!this.refreshEventReceived) {
      // Either the context is not a ConfigurableApplicationContext with refresh
      // support or the context injected at construction time had already been
      // refreshed -> trigger initial onRefresh manually here.
      synchronized (this.onRefreshMonitor) {
         onRefresh(wac);
      }
   }

   if (this.publishContext) {
      // Publish the context as a servlet context attribute.
      String attrName = getServletContextAttributeName();
      getServletContext().setAttribute(attrName, wac);
   }

   return wac;
}

父子容器

这里有个父子容器的体现cwac.setParent(rootContext); //父子容器的体现,

[图片上传失败...(image-8fc8b8-1668562028847)]

好了想要搞这个父子容器,我们要用一个新的启动类

/**
 * 最快速的整合注解版SpringMVC和Spring的
 */
public class QuickAppStarter  extends AbstractAnnotationConfigDispatcherServletInitializer {

   @Override //根容器的配置(Spring的配置文件===Spring的配置类)
   protected Class<?>[] getRootConfigClasses() {
      return new Class<?>[]{SpringConfig.class};
   }

   @Override //web容器的配置(SpringMVC的配置文件===SpringMVC的配置类)
   protected Class<?>[] getServletConfigClasses() {
      return new Class<?>[]{SpringMVCConfig.class};
   }

   @Override //Servlet的映射,DispatcherServlet的映射路径
   protected String[] getServletMappings() {
      return new String[]{"/"};
   }

   @Override
   protected void customizeRegistration(ServletRegistration.Dynamic registration) {
//    super.customizeRegistration(registration);

//    registration.addMapping("");//
   }
}
@ComponentScan(value = "com.demo.web",excludeFilters = {
      @ComponentScan.Filter(type= FilterType.ANNOTATION,value = Controller.class)
})
@Configuration
public class SpringConfig {
   //Spring的父容器

}
@ComponentScan(value = "com.demo.web",includeFilters = {
      @ComponentScan.Filter(type= FilterType.ANNOTATION,value = Controller.class)
},useDefaultFilters = false)
public class SpringMVCConfig {
   //SpringMVC的子容器,能扫描的Spring容器中的组件


}

我们现在从新启动断点重新从onStartup进来进入AbstractDispatcherServletInitializer的onStartup

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

首先 super.onStartup(servletContext); 中启动方法中调用的是父类的方法

@Override //注册ContextLoaderListener;contextInitialized
public void onStartup(ServletContext servletContext) throws ServletException {
   registerContextLoaderListener(servletContext);
}

/**
 * Register a {@link ContextLoaderListener} against the given servlet context. The
 * {@code ContextLoaderListener} is initialized with the application context returned
 * from the {@link #createRootApplicationContext()} template method.
 * @param servletContext the servlet context to register the listener against
 */
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");
   }
}

WebApplicationContext rootAppContext = createRootApplicationContext(); //创建一个根容器

这边是模版方法给我们注册父组件配置

@Override
@Nullable //重写了爷爷类的创建根容器方法
protected WebApplicationContext createRootApplicationContext() {
   Class<?>[] configClasses = getRootConfigClasses(); //获取根配置
   if (!ObjectUtils.isEmpty(configClasses)) {
      AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
      context.register(configClasses); //创建了一个IOC容器并把配置类注册进来
      return context;
   }
   else {
      return null;
   }
}

接下来再次回到 registerDispatcherServlet(servletContext);

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

   WebApplicationContext servletAppContext = createServletApplicationContext(); //创建Servlet容器
   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()); //根据我们指定的DispatcherServlet的路径进行注册
   registration.setAsyncSupported(isAsyncSupported());

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

   customizeRegistration(registration);
}

WebApplicationContext servletAppContext = createServletApplicationContext(); //创建Servlet容器

@Override
protected WebApplicationContext createServletApplicationContext() {
   AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); //这里又创建了一个容器
   Class<?>[] configClasses = getServletConfigClasses(); //获取web应用的配置
   if (!ObjectUtils.isEmpty(configClasses)) {
      context.register(configClasses);
   }
   return context;
}

registration.addMapping(getServletMappings()); //根据我们指定的DispatcherServlet的路径进行注册

到此为止创建好了2个父子容器,那什么时候初始化。

父容器初始化

我们在做父容器的时候有注册过一个监听器,监听器在tomcat启动后会回调。

ContextLoaderListener下的

@Override
public void contextInitialized(ServletContextEvent event) { //根容器初始化
   initWebApplicationContext(event.getServletContext()); //初始化webioc容器
}
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!");
   }

   servletContext.log("Initializing Spring root WebApplicationContext");
   Log logger = LogFactory.getLog(ContextLoader.class);
   if (logger.isInfoEnabled()) {
      logger.info("Root WebApplicationContext: initialization started");
   }
   long startTime = System.currentTimeMillis();

   try {
      // Store context in local instance variable, to guarantee that
      // it is available on ServletContext shutdown.
      if (this.context == null) {
         this.context = createWebApplicationContext(servletContext);
      }
      if (this.context instanceof ConfigurableWebApplicationContext) {
         ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
         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 ->
               // determine parent for root web application context, if any.
               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);
      }

      if (logger.isInfoEnabled()) {
         long elapsedTime = System.currentTimeMillis() - startTime;
         logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
      }

      return this.context;
   }
   catch (RuntimeException | Error ex) {
      logger.error("Context initialization failed", ex);
      servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
      throw ex;
   }
}

configureAndRefreshWebApplicationContext(cwac, servletContext);//配置和刷新容器

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
   if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
      // The application context id is still set to its original default value
      // -> assign a more useful id based on available information
      String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
      if (idParam != null) {
         wac.setId(idParam);
      }
      else {
         // Generate default id...
         wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
               ObjectUtils.getDisplayString(sc.getContextPath()));
      }
   }

   wac.setServletContext(sc);
   String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
   if (configLocationParam != null) {
      wac.setConfigLocation(configLocationParam);
   }

   // The wac environment's #initPropertySources will be called in any case when the context
   // is refreshed; do it eagerly here to ensure servlet property sources are in place for
   // use in any post-processing or initialization that occurs below prior to #refresh
   ConfigurableEnvironment env = wac.getEnvironment();
   if (env instanceof ConfigurableWebEnvironment) {
      ((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
   }

   customizeContext(sc, wac);
   wac.refresh(); //容器初始化
}

wac.refresh(); //容器初始化这里就是我们最熟悉的容器刷新12大步了。

到这边我们的父容器AOP,事务,IOC,自动装配组件功能都装载进来了。

子容器初始化

我们在创建子容器的时候我们有创建了一个DispatcherServlet,在tomcat启动后会调用init方法。这样又回到我们最最开始的流程。

protected WebApplicationContext initWebApplicationContext() {
   WebApplicationContext rootContext =
         WebApplicationContextUtils.getWebApplicationContext(getServletContext()); //父容器
   WebApplicationContext wac = null; //先会获取之前的 WebApplicationContext(构建父子容器)

   if (this.webApplicationContext != null) {
      // A context instance was injected at construction time -> use it
      wac = this.webApplicationContext; //当前的web-ioc容器
      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) {
      // No context instance was injected at construction time -> see if one
      // has been registered in the servlet context. If one exists, it is assumed
      // that the parent context (if any) has already been set and that the
      // user has performed any initialization such as setting the context id
      wac = findWebApplicationContext();
   }
   if (wac == null) {
      // No context instance is defined for this servlet -> create a local one
      wac = createWebApplicationContext(rootContext);
   }

   if (!this.refreshEventReceived) {
      // Either the context is not a ConfigurableApplicationContext with refresh
      // support or the context injected at construction time had already been
      // refreshed -> trigger initial onRefresh manually here.
      synchronized (this.onRefreshMonitor) {
         onRefresh(wac); //容器刷新
      }
   }

   if (this.publishContext) {
      // Publish the context as a servlet context attribute.
      String attrName = getServletContextAttributeName();
      getServletContext().setAttribute(attrName, wac);
   }

   return wac;
}

获取父容器的时候就能找到父容器。

onRefresh(wac); //容器刷新 然后后面也执行了容器刷新

到这里就是把MVC的原理源码梳理了一遍,这里挖个坑.现在我们知道一个controller被注册到容器中了。那我们一个请求进来/test/a. 他是怎么找到对应的接口呢。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容