前言
我們知道在使用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