[SpringMVC源码学习]DispatcherServlet初始化(一)

147 阅读6分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第10天,点击查看活动详情

1. DispatcherServlet初始化

我们将断点打在DispatcherServlet中的onRefresh方法上,然后启动项目。

img

从这个堆栈的图可以看出,最开始是HttpServletBeaninit方法执行,然后启动Spring容器,Spring容器初始化的最后一步finishRefresh里面进行了事件通知。

我们先看下HttpServletBean里面的init方法

@Override
public final void init() throws ServletException {
​
   ///将web.xml中的<param-value>classpath:spring-mvc.xml</param-value>读取解析,存入到this.requiredProperties中
   PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
   if (!pvs.isEmpty()) {
      try {
         //将需要包装的DispatcherServlet对象包装成一个BeanWrapper
         BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
         //将spring-mvc.xml加载成一个资源装载器
         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();
}

从web.xml中拿到paramValue属性之后,存放到一个叫requiredProperties的属性中。然后拿到一个包装类,其实bw这个包装类,就是包装了DispatcherServlet。然后将我们的spring-mvc.xml文件加载成一个资源装载器。下面就是给bw这个对象设置一些属性。最后会去调用initServletBean。这个方法在本类中其实也是一个空实现,交给FrameworkServlet这个子类去实现。

@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 {
      // 这里初始WebApplicationContext
      this.webApplicationContext = initWebApplicationContext(); 
      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");
   }
}

可以看到这里面没干什么事情。

在进行了一些日志的操作之后,记录一下开始的时间,我们会去进行初始化一个ApplicationContext

protected WebApplicationContext initWebApplicationContext() {
   // 获取web容器
   WebApplicationContext rootContext =
         WebApplicationContextUtils.getWebApplicationContext(getServletContext());
   WebApplicationContext wac = null;
​
   if (this.webApplicationContext != null) {
      // 在构造时注入上下文实例 -> 使用它
      wac = this.webApplicationContext;
      if (wac instanceof ConfigurableWebApplicationContext) {
         ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
         if (!cwac.isActive()) {
            // 上下文尚未刷新 -> 提供设置父上下文、设置应用上下文id等服务
            if (cwac.getParent() == null) {
               // 上下文实例是在没有显式父级的情况下注入的 -> 将根应用程序上下文(如果有的话;可能为空)设置为父级
               cwac.setParent(rootContext);
            }
            configureAndRefreshWebApplicationContext(cwac);
         }
      }
   }
   if (wac == null) {
      // 去父容器中找 web 容器
      wac = findWebApplicationContext();
   }
   if (wac == null) {
      // 父容器也没有就为此 servlet 定义上下文实例 -> 创建一个本地实例
      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;
}

首先去获取web容器,我们在之前是没有设置过的,所以获取到的是null,然后再去父容器中找,找到不到依然为null。那么接下来就要去创建一个WebApplicationContext了。

protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
   // 拿到 ApplicationContext 的类型
   Class<?> contextClass = getContextClass();
   if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
      throw new ApplicationContextException(
            "Fatal initialization error in servlet with name '" + getServletName() +
            "': custom WebApplicationContext class [" + contextClass.getName() +
            "] is not of type ConfigurableWebApplicationContext");
   }
   ConfigurableWebApplicationContext wac =
         (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
​
   wac.setEnvironment(getEnvironment());
   wac.setParent(parent);
   String configLocation = getContextConfigLocation();
   if (configLocation != null) {
      wac.setConfigLocation(configLocation);
   }
   configureAndRefreshWebApplicationContext(wac);
​
   return wac;
}

那么创建的这个ApplicationContext的类型到底是什么类型呢?

public Class<?> getContextClass() {
   return this.contextClass;
}

->

private Class<?> contextClass = DEFAULT_CONTEXT_CLASS;

->

public static final Class<?> DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class;

这里我们就很清楚的可以看到,其实它默认创建的是一个XmlWebApplicationContext。拿到这个类型之后,就会去通过反射去创建这个实例了。创建完成之后,去给他设置一些属性,有父容器设置父容器,再去调用configureAndRefreshWebApplicationContext这个方法。配置和刷新WebApplicationContext

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
   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
      if (this.contextId != null) {
         wac.setId(this.contextId);
      }
      else {
         // Generate default id...
         wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
               ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
      }
   }
​
   wac.setServletContext(getServletContext());
   wac.setServletConfig(getServletConfig());
   wac.setNamespace(getNamespace());
   wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
​
   // 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(getServletContext(), getServletConfig());
   }
​
   postProcessWebApplicationContext(wac);
   applyInitializers(wac);
   wac.refresh();
}

前面就没有什么好说的,设置一些参数,但是在最后,看到一个熟悉的方法,refresh。其实初始化DispatcherServlet的时候,是基于事件派发机制实现的。进入到refresh这个方法之后。

没错,这个就是我们学习Spring源码的时候的IOC中的refresh

public void refresh() throws BeansException, IllegalStateException {
        synchronized(this.startupShutdownMonitor) {
            //容器刷新之前初始化一些参数
            this.prepareRefresh();
            //创建beanFactory 
            ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
            //给beanFactory设置一些参数 
            this.prepareBeanFactory(beanFactory);
​
            try {
                //空方法,交给子类扩展
                this.postProcessBeanFactory(beanFactory);
                //调用BeanFactoryPostProcessor
                this.invokeBeanFactoryPostProcessors(beanFactory);
                //注册BeanPostProcessors
                this.registerBeanPostProcessors(beanFactory);
                //国际化的一些操作
                this.initMessageSource();
                //初始化一些时间多播器
                this.initApplicationEventMulticaster();
                空方法,交给子类扩展
                this.onRefresh();
                //注册监听器
                this.registerListeners();
                //初始化一些单实例非懒加载的bean实例
                this.finishBeanFactoryInitialization(beanFactory);
                // 发布相应的事件。
                this.finishRefresh();
            } catch (BeansException var9) {
                if (this.logger.isWarnEnabled()) {
                    this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
                }
​
                this.destroyBeans();
                this.cancelRefresh(var9);
                throw var9;
            } finally {
                this.resetCommonCaches();
            }
        }
    }
​

由于侧重点不同,我们需要关注的是finishRefresh

protected void finishRefresh() {
   // Clear context-level resource caches (such as ASM metadata from scanning).
   clearResourceCaches();
​
   // Initialize lifecycle processor for this context.
   initLifecycleProcessor();
​
   // Propagate refresh to lifecycle processor first.
   getLifecycleProcessor().onRefresh();
​
   // Publish the final event.
   publishEvent(new ContextRefreshedEvent(this));
​
   // Participate in LiveBeansView MBean, if active.
   if (!NativeDetector.inNativeImage()) {
      LiveBeansView.registerApplicationContext(this);
   }
}

在这个方法中,注册了ContextRefreshedEvent这样一个事件,我们进入到publishEvent这里。

protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
   Assert.notNull(event, "Event must not be null");
​
   // Decorate event as an ApplicationEvent if necessary
   ApplicationEvent applicationEvent;
   if (event instanceof ApplicationEvent) {
      applicationEvent = (ApplicationEvent) event;
   }
   else {
      applicationEvent = new PayloadApplicationEvent<>(this, event);
      if (eventType == null) {
         eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();
      }
   }
​
   // Multicast right now if possible - or lazily once the multicaster is initialized
   if (this.earlyApplicationEvents != null) {
      this.earlyApplicationEvents.add(applicationEvent);
   }
   else {
      getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
   }
​
   // Publish event via parent context as well...
   if (this.parent != null) {
      if (this.parent instanceof AbstractApplicationContext) {
         ((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
      }
      else {
         this.parent.publishEvent(event);
      }
   }
}

在进行了一些赋值的操作之后,判断一下一些早期事件是否为null,因为我们此前并没有对此进行操作,所以肯定是为null。然后拿到事件的多播器之后,就会进行事件的发布了。

image-20220806112652311

multicastEvent这个方法中,会调用invokeListener来回调监听方法。

protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
   ErrorHandler errorHandler = getErrorHandler();
   if (errorHandler != null) {
      try {
         doInvokeListener(listener, event);
      }
      catch (Throwable err) {
         errorHandler.handleError(err);
      }
   }
   else {
      doInvokeListener(listener, event);
   }
}

doInvokeListener回调onApplicationEvent方法。

private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
   try {
      listener.onApplicationEvent(event); // <-----这里回调
   }
   catch (ClassCastException ex) {
      String msg = ex.getMessage();
      if (msg == null || matchesClassCastMessage(msg, event.getClass()) ||
            (event instanceof PayloadApplicationEvent &&
                  matchesClassCastMessage(msg, ((PayloadApplicationEvent) event).getPayload().getClass()))) {
         // Possibly a lambda-defined listener which we could not resolve the generic event type for
         // -> let's suppress the exception.
         Log loggerToUse = this.lazyLogger;
         if (loggerToUse == null) {
            loggerToUse = LogFactory.getLog(getClass());
            this.lazyLogger = loggerToUse;
         }
         if (loggerToUse.isTraceEnabled()) {
            loggerToUse.trace("Non-matching event type for listener: " + listener, ex);
         }
      }
      else {
         throw ex;
      }
   }
}

但是,ApplicationListener接口中的方法,别很多子类所重写,那么到底是哪个呢,我们上面提到,我们注册了ContextRefreshedEvent这样一个事件,那自然需要用ContextRefreshListener去监听。

我们DeBug后发现,确实会进入到这样的一个方法。值得注意的是,FrameworkServlet里有个内部类ContextRefreshListener,实现了ApplicationListener接口,接收到了上面的事件通知,执行onApplicationEvent方法。

private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {
​
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        FrameworkServlet.this.onApplicationEvent(event);
    }
    ...
}

继续跟踪onApplicationEvent方法

public void onApplicationEvent(ContextRefreshedEvent event) {
    this.refreshEventReceived = true;
    synchronized (this.onRefreshMonitor) {
        onRefresh(event.getApplicationContext());
    }
}

发现最终是由onRefresh方法来进行具体处理的,并且这个onRefresh方法在FrameworkServlet里面是个空方法,是由它的子类DispatcherServlet来实现的。

我们来看DispatcherServlet里的onRefresh方法

protected void onRefresh(ApplicationContext context) {
    initStrategies(context);
}

进去

protected void initStrategies(ApplicationContext context) {
    initMultipartResolver(context); // 初始化文件上传解析器
    initLocaleResolver(context); // 本地化解析
    initThemeResolver(context); // 主题解析器
    initHandlerMappings(context);  // 初始化处理器映射器
    initHandlerAdapters(context); // 初始化处理器适配器
    initHandlerExceptionResolvers(context); // 异常解析器
    initRequestToViewNameTranslator(context); // 视图提取器,从request中获取viemName
    initViewResolvers(context); // 初始化视图解析器
    initFlashMapManager(context); // 参数解析器
}

这样就把SpringMVC的九大组件给初始化了。