注意:爲了突出源碼主要代碼邏輯,本章中貼出的源碼,將刪除例如log,try等非主要的代碼。而知貼出重要的代碼塊
先看入口
private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";
private static final Properties defaultStrategies;
static {
// 將spring-webmvc-4.3.8.RELEASE.jar\org\springframework\web\servlet 目錄下的
// DispatcherServlet.properties文件中的內容放到properties對象中,下面各個init方法中的
// getDefaultStrategy方法裏,將會通過這個對象,來生成默認放到容器中的類
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}
@Override
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);
}
當我們啓動tomcat時,會通過onRefresh方法開始進入,然後出初始化一些內容。下面我們分別來看一下
- initMultipartResolver(context)方法
public static final String MULTIPART_RESOLVER_BEAN_NAME = "multipartResolver"; private void initMultipartResolver(ApplicationContext context) { // 從容器中獲取實現了MultipartResolver接口並且名字爲multipartResolver的bean this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class); }
這個方法主要是從容器中獲取名字爲multipartResolver的bean。這個bean主要是用來處理文件上傳用的,並且這個bean必須實現MultipartResolver接口。現在再來回想一下,我們spring-mvc.xml中是怎麼配置文件上傳的?是不是如下樣子:
注意看id,它與源碼中的常量字符串是一樣的。這也就是爲什麼這個id必須要這麼寫的原因了。不這麼寫,在這個init方法裏面就無法獲取到我們的這個bean了。
MultipartResolver的默認實現類有兩個,上圖便是使用其中一個: -
initLocaleResolver(context)方法
public static final String LOCALE_RESOLVER_BEAN_NAME = "localeResolver"; private void initLocaleResolver(ApplicationContext context) { try { this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class); if (logger.isDebugEnabled()) { logger.debug("Using LocaleResolver [" + this.localeResolver + "]"); } } catch (NoSuchBeanDefinitionException ex) { // 將DispatcherServlet.properties中名爲LocaleResolver對應的類放到容器中 this.localeResolver = getDefaultStrategy(context, LocaleResolver.class); } } DispatcherServlet.properties位於 spring-webmvc-4.3.8.RELEASE.jar\org\springframework\web\servlet目錄下,內容如下: org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver ...其他的省略....
這個方法是用來從容器中獲取實現本地化的bean,回想一下,我們如果要做本地化的話,在spring-mvc.xml中是怎麼寫的?
是不是一般會如下的寫法:注意看id,它與源碼中的常量字符串是一樣的。這也就是爲什麼這個id必須要這麼寫的原因了。不這麼寫,在這個init方法裏面就無法獲取到我們的這個bean了。
這個bean你既可以自己通過實現接口自定義一個,也可以使用springmvc自己的一些實現類,如下:
-
initThemeResolver(context)方法
public static final String THEME_RESOLVER_BEAN_NAME = "themeResolver"; private void initThemeResolver(ApplicationContext context) { try { this.themeResolver = context.getBean(THEME_RESOLVER_BEAN_NAME, ThemeResolver.class); }catch (NoSuchBeanDefinitionException ex) { // 將DispatcherServlet.properties中名爲LocaleResolver對應的類放到容器中 this.themeResolver = getDefaultStrategy(context, ThemeResolver.class); } } DispatcherServlet.properties內容如下: org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver ...其他的省略....
這個方法主要是從容器中獲取主題解析器(ThemeResolver)的,什麼是ThemeResolver呢?大家都見過通過在管理系統上選擇樣色樣式,然後系統的色調就都變了的效果吧。關於如何使用主題解析器,可以參考這篇文章:https://blog.csdn.net/qq924862077/article/details/53438710
下面來看一下如何通過spring-mvc.xml來聲明一個主題解析器相關的bean,如下:
注意看id,它與源碼中的常量字符串是一樣的。這也就是爲什麼這個id必須要寫themeResolver的原因了。不這麼寫,在這個init方法裏面就無法獲取到我們的這個bean了。
下面來看一下springmvc默認給我們帶了那些ThemeResolver的實現類,如下:
-
initHandlerMappings(context)方法
public static final String HANDLER_MAPPING_BEAN_NAME = "handlerMapping"; private void initHandlerMappings(ApplicationContext context) { this.handlerMappings = null; // 通過在xml中設置detectAllHandlerMappings這個屬性,來決定是加載所有的handlermapping還是隻加載我們自定義的那個handlermapping // 如果未true,從容器中獲取所有的handlermapping if (this.detectAllHandlerMappings) { // 從容器中獲取所有的handlerMapping,比如springmvc啓動時自動加載的和我們自定義的 Map<String, HandlerMapping> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false); if (!matchingBeans.isEmpty()) { this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values()); // 對這些handlermapping的順序進行排序 AnnotationAwareOrderComparator.sort(this.handlerMappings); } }else { // 爲false,那麼就只從容器中獲取名字爲handlerMapping的那個handlermapping HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class); this.handlerMappings = Collections.singletonList(hm); } //如果從容器找不到,就生成一個默認的 if (this.handlerMappings == null) { // 從DispatcherServlet.properties文件中找HandlerMapping對應的那些就是默認的handlermapping this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class); } }
這個方法主要就是從容器中獲取handlermapping的(什麼是handlermapping,請參考前面的文章)。springmvc默認給我們帶的handlermapping有哪些,如下圖:
到底哪些handlermapping在程序啓動時,會自動放到容器中,這就需要根絕一些觸發條件了,比如,如果我們少用了<mvc:annotation-driven/>標籤,那麼RequestMappingHandlerMapping就會自動放到容器中(原因不在此解釋)。
基本上來看,當detectAllHandlerMappings爲true時,下面三個handlermapping會在容器出現。
1) org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
2) org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping
3) org.springframework.web.servlet.handler.SimpleUrlHandlerMapping
那麼如果detectAllHandlerMappings爲false時,會從容器中獲取名字爲handlerMapping的那個bean,也就是說如果我們想讓springmvc只用我們指定的handlermapping時,通過兩步即可,如下:
第一步:在web.xml中設置detectAllHandlerMappings爲false(默認爲true)如下:第二步:在spring-mvc.xml中聲明要使用的handlermapping,如下:
<bean id="/myHanler" class="com.lhb.controller.BeanNameURLHandlerMappingController"/> <bean id="handlerMapping" class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
注意id必須爲handlerMapping,不這麼寫,在這個init方法裏面就無法獲取到我們的這個bean了。
如果此時此時在容器中找不到對應的名爲handlerMapping的bean則會調用
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
去獲取默認的handlerMapping,我們看看getDefaultStrategies是如何獲取的,源碼如下:private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties"; static { ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class); defaultStrategies = PropertiesLoaderUtils.loadProperties(resource); } protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) { // key就是接口的名字HandlerMapping String key = strategyInterface.getName(); // 這裏就是從springmvc的jar包中的\org\springframework\web\servlet 文件夾下的DispatcherServlet.properties文件中找到一個名爲HandlerMapping的key,它對應了springmvc默認放到容器中的handlerMapping類。下面會截圖 String value = defaultStrategies.getProperty(key); if (value != null) { // 默認的類有好多個,用逗號分隔的,所以這裏使用逗號進行分隔 String[] classNames = StringUtils.commaDelimitedListToStringArray(value); List<T> strategies = new ArrayList<T>(classNames.length); // 通過逗號分隔後,然後通過全類名來實例化 for (String className : classNames) { Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader()); Object strategy = createDefaultStrategy(context, clazz); strategies.add((T) strategy); } return strategies; } else { return new LinkedList<T>(); } }
DispatcherServlet.properties如下:
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\ org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping ...其他內容省略...
從上面源碼可以看出,獲取默認的handlermapping就是從DispatcherServlet.properties文件中找到HandlerMapping對應的這兩個類的全限定名,然後在程序中通過反射實例化放到容器的而已。這也就是爲什麼有時候我們沒聲明bean,程序未報錯的原因了。
-
initHandlerAdapters(context)方法
public static final String HANDLER_ADAPTER_BEAN_NAME = "handlerAdapter"; private void initHandlerAdapters(ApplicationContext context) { this.handlerAdapters = null; // detectAllHandlerAdapters爲true,表示從容器獲取所有的handlerAdpater if (this.detectAllHandlerAdapters) { Map<String, HandlerAdapter> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false); if (!matchingBeans.isEmpty()) { this.handlerAdapters = new ArrayList<HandlerAdapter>(matchingBeans.values()); AnnotationAwareOrderComparator.sort(this.handlerAdapters); } }else {//detectAllHandlerAdapters爲false,則只從容器中找名字爲handlerAdapter的bean HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class); this.handlerAdapters = Collections.singletonList(ha); } // 如果找不到一個handlerAdpater都沒有 if (this.handlerAdapters == null) { // 那麼就加載DispatcherServlet.properties中名爲HandlerAdapter的類作爲默認的handlerAdapter放到容器中 this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class); } }
從源碼可以看出initHandlerAdapters和initHandlerMapping如出一致,邏輯都是相同的,都是根據detectAllHandlerAdapters來決定是從容器中獲取所有adapter還是隻從容器中獲取名爲handlerAdapter的adapter。
同理,他的getDefaultStrategies方法,也是將DispatcherServlet.properties中名爲HandlerAdapter對應的類進行實例化,然後放到容器中來作爲默認的adatper的。這裏就不多做解釋了。
DispatcherServlet.properties如下:org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\ org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\ org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
-
initHandlerExceptionResolvers(context)方法
public static final String HANDLER_EXCEPTION_RESOLVER_BEAN_NAME = "handlerExceptionResolver"; private void initHandlerExceptionResolvers(ApplicationContext context) { this.handlerExceptionResolvers = null; if (this.detectAllHandlerExceptionResolvers) { // 從容器中獲取所有的HandlerExceptionResolver的實現類 Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils .beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false); if (!matchingBeans.isEmpty()) { this.handlerExceptionResolvers = new ArrayList<HandlerExceptionResolver>(matchingBeans.values()); // 對這些HandlerExceptionResolver的實現類進行排序 AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers); } } else { // 從容器中找的名爲handlerExceptionResolver的那個類,來處理controller拋出的異常 HandlerExceptionResolver her = context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class); this.handlerExceptionResolvers = Collections.singletonList(her); } // 如果從容器中一個對應的resolver都找不到,就生成DispatcherServlet.properties中指定的默認resolver。 if (this.handlerExceptionResolvers == null) { this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class); } } }
從源碼可以看出,同樣是通過detectAllHandlerExceptionResolvers這個屬性來決定是從容器中獲取素有的exceptionResolver還是隻從容器值獲取指定名字的exceptionResolver。如果從容器中一個exceptionResolver都獲取不到,那麼就通過getDefaultStrategies方法,將DispatcherServlet.properties中寫的exceptionResolver進行實例化,來使用。
DispatcherServlet.properties如下: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
-
initRequestToViewNameTranslator(context)方法
什麼是RequestToViewNameTranslator?作用是什麼?可以看我的文章spring篇:【RequestToViewNameTranslator】裏面有詳細說明。下面我們來看源碼:public static final String REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME = "viewNameTranslator"; private void initRequestToViewNameTranslator(ApplicationContext context) { try { this.viewNameTranslator =context.getBean(REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME, RequestToViewNameTranslator.class); }catch (NoSuchBeanDefinitionException ex) { // We need to use the default. this.viewNameTranslator = getDefaultStrategy(context, RequestToViewNameTranslator.class); } }
源碼很簡單,就是從容器中去獲取名爲viewNameTranslator的translator,如果容器中沒有,那麼就把DispatcherServlet.properties中寫的那個translator進行實例化來使用。DispatcherServlet.properties如下:
org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
-
initViewResolvers(context)方法
什麼是RequestToViewNameTranslator?作用是什麼?可以看我的文章spring篇:【RequestToViewNameTranslator】裏面有詳細說明。下面我們來看這個方法的源碼:
public static final String VIEW_RESOLVER_BEAN_NAME = "viewResolver"; private void initViewResolvers(ApplicationContext context) { this.viewResolvers = null; //是否從容器中檢測所有的ViewResolver,detectAllViewResolvers默認爲true,可通過web.xml來修改這個值 if (this.detectAllViewResolvers) { // 從容器中檢測所有的ViewResolver的實現類 Map<String, ViewResolver> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.class, true, false); if (!matchingBeans.isEmpty()) { this.viewResolvers = new ArrayList<ViewResolver>(matchingBeans.values()); // 將從容器中檢索到的所有ViewResolver進行排序 AnnotationAwareOrderComparator.sort(this.viewResolvers); } } else { //否則只從容器中檢測名字爲viewResolver的ViewResolver ViewResolver vr = context.getBean(VIEW_RESOLVER_BEAN_NAME, ViewResolver.class); this.viewResolvers = Collections.singletonList(vr); } // 如果一個ViewResolver也沒得到,就根據DispatcherServlet.properties中指定的來創建一個默認的ViewResolver if (this.viewResolvers == null) { this.viewResolvers = getDefaultStrategies(context, ViewResolver.class); } } }