注意:为了突出源码主要代码逻辑,本章中贴出的源码,将删除例如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); } } }