DispatcherServlet初始化過程


前言

我們知道在使用SpringMVC的時候,我們會在web.xml中配置如下內容,DispatcherServlet會攔截住所有的請求然后處理。

    <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath*:application-context.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath*:spring-mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> 

首先我們先看下DispatcherServlet的繼承關系,然后我們再來逐步分析。

DispatcherServlet繼承關系

Servlet

public interface Servlet { /** * 容器啟動時被調用(當load-on-startup為負數或者不設置時,會在第一次被使用時才調用),只會調用一次 * 它有一個參數ServletConfig,是容器傳進來的,表示的是這個Servlet的一些配置,比如DispatcherServlet配置的<init-param> */ public void init(ServletConfig config) throws ServletException; /** * 獲取Servlet的配置 */ public ServletConfig getServletConfig(); /** * 最重要的一個方法,是具體處理請求的地方 */ public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException; /** * 獲取Servlet的一些信息,比如作者、版本、版權等,需要子類實現 */ public String getServletInfo(); /** * 用於Servlet銷毀(主要指關閉容器)時釋放一些資源,只會調用一次 */ public void destroy(); } 

ServletConfig

public interface ServletConfig { /** * 返回Servlet的名字,就是<servlet-name>中配置的名字 */ public String getServletName(); /** * 返回應用本身的一些配置 */ public ServletContext getServletContext(); /** * 返回<init-param>配置的參數 */ public String getInitParameter(String name); /** * 返回<init-param>配置的參數的名字 */ public Enumeration<String> getInitParameterNames(); } 

GenericServlet

GenericServlet是Servlet的默認實現,主要做了如下幾件事

  • 提供了無參的init方法,init()是一個模板方法,留給子類實現
    public void init(ServletConfig config) throws ServletException { this.config = config; this.init(); } public void init() throws ServletException { } 
  • 實現了ServletConfig的接口,可以直接調用ServletConfig中的方法,這樣做的好處是如果我們想獲取ServletConfig中的內容,不必先調用getServletConfig()了,比如獲取ServletContext的代碼
    public ServletContext getServletContext() { ServletConfig sc = getServletConfig(); if (sc == null) { throw new IllegalStateException( lStrings.getString("err.servlet_config_not_initialized")); } return sc.getServletContext(); } 

HttpServlet

HttpServlet是用HTTP協議實現的Servlet的基類,一般我們寫的Servlet就是繼承於它,我們注意到HttpServlet並沒有實現init方法

HttpServletBean

從HttpServletBean開始我們就進入Spring的范圍了,HttpServletBean重寫了init方法

public final void init() throws ServletException { if (logger.isDebugEnabled()) { logger.debug("Initializing servlet '" + getServletName() + "'"); } // 從ServletConfig中獲取初始配置,比如contextConfigLocation PropertyValues pvs = new HttpServletBean.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())); // 模板方法,做一些初始化的工作,bw代表DispatcherServlet,但是沒有子類重寫 initBeanWrapper(bw); // 把初始配置設置給DispatcherServlet,比如contextConfigLocation bw.setPropertyValues(pvs, true); } catch (BeansException ex) { if (logger.isErrorEnabled()) { logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex); } throw ex; } } // 模板方法,子類重寫,做進一步初始化的工作 initServletBean(); if (logger.isDebugEnabled()) { logger.debug("Servlet '" + getServletName() + "' configured successfully"); } } 

FrameworkServlet

FrameworkServlet實現了initServletBean方法,其簡化代碼如下

    protected final void initServletBean() throws ServletException { try { // 初始化WebApplicationContext this.webApplicationContext = initWebApplicationContext(); // 初始化FrameworkServlet,模板方法,並沒有子類實現 initFrameworkServlet(); } catch (ServletException ex) { this.logger.error("Context initialization failed", ex); throw ex; } catch (RuntimeException ex) { this.logger.error("Context initialization failed", ex); throw ex; } } 

我們可以看到最重要的代碼只有兩句,而這兩句之中最主要的是initWebApplicationContext(),下面我們就來看下initWebApplicationContext的簡化代碼

    protected WebApplicationContext initWebApplicationContext() { // 獲取父WebApplicationContext,如果我們在web.xml中配置了ContextLoaderListener,那么它加載的就是父WebApplicationContext WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext()); WebApplicationContext wac = null; // 如果通過構造方法傳入了webApplicationContext,就使用它 if (this.webApplicationContext != null) { wac = this.webApplicationContext; if (wac instanceof ConfigurableWebApplicationContext) { ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac; if (!cwac.isActive()) { if (cwac.getParent() == null) { cwac.setParent(rootContext); } configureAndRefreshWebApplicationContext(cwac); } } } if (wac == null) { // 從ServletContext中獲取webApplicationContext,一般情況下是沒有的 wac = findWebApplicationContext(); } if (wac == null) { // 自己創建一個webApplicationContext wac = createWebApplicationContext(rootContext); } if (!this.refreshEventReceived) { // 當ContextRefreshedEvent事件沒有觸發時調用此方法,模板方法,子類實現,是DispatcherServlet中重要的方法 onRefresh(wac); } if (this.publishContext) { // 把webApplicationContext保存到ServletContext中 String attrName = getServletContextAttributeName(); getServletContext().setAttribute(attrName, wac); } return wac; } 

正常情況下都是自己創建一個webApplicationContext,我們看下創建的過程

    protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) { // 獲取創建類型 Class<?> contextClass = getContextClass(); // 具體創建 ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); wac.setEnvironment(getEnvironment()); wac.setParent(parent); // 將設置的contextConfigLocation參數傳給wac,默認傳入WEB-INFO/[ServletName]-Servlet.xml wac.setConfigLocation(getContextConfigLocation()); // 配置和刷新wac configureAndRefreshWebApplicationContext(wac); return wac; } protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) { if (ObjectUtils.identityToString(wac).equals(wac.getId())) { if (this.contextId != null) { wac.setId(this.contextId); } else { 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 FrameworkServlet.ContextRefreshListener())); ConfigurableEnvironment env = wac.getEnvironment(); if (env instanceof ConfigurableWebEnvironment) { ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig()); } postProcessWebApplicationContext(wac); applyInitializers(wac); // 根據contextConfigLocation的值刷新webApplicationContext wac.refresh(); } 

DispatcherServlet

從上面的分析可以知道DispactcherServlet初始化的入口方法是onRefresh(wac),下面我們來具體看下

    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); initViewResolvers(context); initFlashMapManager(context); } 

我們可以看到onRefresh方法只是調用了initStrategies方法,而initStrategies方法內部調用了九個初始化SpringMVC組件的方法,這九個組件的初始化過程類似,我們就以initHandlerMappings為例分析下

    private void initHandlerMappings(ApplicationContext context) { this.handlerMappings = null; // detectAllHandlerMappings默認是true if (this.detectAllHandlerMappings) { // 從ApplicationContext中找到所有的HandlerMapping Map<String, HandlerMapping> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false); if (!matchingBeans.isEmpty()) { this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values()); AnnotationAwareOrderComparator.sort(this.handlerMappings); } } else { try { // 從ApplicationContext中獲取名稱為handlerMapping的Bean HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class); this.handlerMappings = Collections.singletonList(hm); } catch (NoSuchBeanDefinitionException ex) { } } // 如果沒有HandlerMapping,則使用默認策略 if (this.handlerMappings == null) { this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class); } } 

默認策略其實就是根據DispatcherServlet.properties中的配置加載對應的組件,下面就是文件中具體的內容,我要說的是默認配置並不是SpringMVC的推薦配置,比如DefaultAnnotationHandlerMapping現在已經是廢棄狀態。

org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver

org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
    org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
    org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
    org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter

org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\
    org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
    org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator

org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager

總結

下面用表格的形式簡要總結下DispatcherServlet初始化過程

類或接口 初始化入口方法 具體作用
Servlet init(ServletConfig config) 接口定義,由Web容器調用
GenericServlet init(ServletConfig config) 保存ServletConfig,內部調用無參的init方法
HttpServlet - -
HttpServletBean init() 設置contextConfigLocation的值,內部調用initServletBean()
FrameworkServlet initServletBean() 初始化webApplicationContext,內部調用onRefresh(ApplicationContext context)
DispatcherServlet onRefresh(ApplicationContext context) 初始化九大組件



文章轉載自:https://www.jianshu.com/p/be981b92f1d2


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM