在DispatcherServlet同一个目录下的DispatchServlet.properties文件中默认的九大组件:
# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.//这里是定制开发,不可对外更改的
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
// 带"/"的是多个默认配置Handler类
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
一、HandlerMapping
他的作用是根据request找到相应的处理器Handler和Interceptors,并且这个HandlerMapping接口只有一个方法。
源码:
public interface HandlerMapping {
String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + ".pathWithinHandlerMapping";
String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingPattern";
String INTROSPECT_TYPE_LEVEL_MAPPING = HandlerMapping.class.getName() + ".introspectTypeLevelMapping";
String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables";
String MATRIX_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".matrixVariables";
String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes";
@Nullable
HandlerExecutionChain getHandler(HttpServletRequest var1) throws Exception;
}
这里通过request返回一个HandlerExecutionChain(包含handler和Interceptor)就可以了,另外HandlerMapping可以通过order属性来定义HandlerMapping的顺序,因为查找Handler是按照顺序遍历的HandlerMapping,当找到一个HandlerMapping后就立即停止查找并返回。
DispatcherServlet中方法调用的源码如下:
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
Iterator var2 = this.handlerMappings.iterator();
while(var2.hasNext()) {
HandlerMapping hm = (HandlerMapping)var2.next();
if (this.logger.isTraceEnabled()) {
this.logger.trace("Testing handler map [" + hm + "] in DispatcherServlet with name '" + this.getServletName() + "'");
}
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
二、HandlerAdapter适配器
这个就是使用Handler的,这个接口提供三个方法源码如下:
public interface HandlerAdapter {
//判断是否可以使用某个Handler
boolean supports(Object var1);
//用来具体使用Handler干活的
@Nullable
ModelAndView handle(HttpServletRequest var1, HttpServletResponse var2, Object var3) throws Exception;
//来获取资源的Last-Modified.LastModified是资源最后一次修改的时间
long getLastModified(HttpServletRequest var1, Object var2);
}
在DispatherServlet中调用的getHandlerAdapter源码如下,这里的Adapter也是采用遍历来获取的,一旦找到合适的就返回,所以Adapter也是可以通过Order属性来设置他们的排序的。同理HandlerAdapter需要注册到SpringMVC的容器中,注册方法和HandlerMapping相同,只需要配置第一个Bean就可以了。
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
Iterator var2 = this.handlerAdapters.iterator();
while(var2.hasNext()) {
HandlerAdapter ha = (HandlerAdapter)var2.next();
if (this.logger.isTraceEnabled()) {
this.logger.trace("Testing handler adapter [" + ha + "]");
}
if (ha.supports(handler)) {
return ha;
}
}
}
throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
三、HandlerExceptionResolver
当其他组件出现异常时,需要通过这个组件根据异常设置ModelAndView,之后再交给render方法进行渲染,在这里render方法只负责渲染页面,不需要管ModelAndView的由来,分工明确。通过DispatcherServlet中的doDispatch方法知道-----HandlerExceptionResolver只是用来解析对于请求做处理的过程中产生的异常,而渲染环节产生的异常就管不到了。这样在后续我们分析问题的时候就能够知道一些原因了。
接口定义源码如下:
public interface HandlerExceptionResolver {
@Nullable
ModelAndView resolveException(HttpServletRequest var1, HttpServletResponse var2,
@Nullable Object var3, Exception var4);
}
它只需要一个方法那就是解析出ModelAndView就可以了,具体实现可以维护一个异常为key,View为value的Map,这样解析的时候就可以通过异常获取到View了,如果在Map中没有对应的异常就返回默认值就可以了。
四、ViewResolver
ViewResolver是用来将Stirng类型的视图名(也可以叫逻辑视图)和Locale解析为View类型的视图,ViewResolver的接口也比较简单,源码如下:
public interface ViewResolver {
@Nullable
View resolveViewName(String var1, Locale var2) throws Exception;
}
这个方法只需要根据视图名和Locale解析出视图就可以了。但是一般我们不需要对不同区域使用不同的视图进行显示,如果需要国际化支持也只是将显示的内容或者主题使用国际化支持。不过SpringMVC确实有一个功能可以让不同的区域使用的视图进行显示。ResourceBundleViewResolver就需要同时使用视图名和对应的视图类型配置到相应的properties中个,使用classpath下的view为baseName的配置文件,例如:views.properties,views_zh_CN.properties等,baseName和文件位置都可以设置。
View
他是用来渲染页面的,通俗来说就是程序将返回的参数填入模板里生产html(或者其他类型)文件。
问题所在:
使用什么模板?
用什么技术填入参数?
那么这里ViewResolver就是用来找到渲染需要的模板和所用的技术(也就是视图的类型)进行渲染,具体的渲染过程就交给不同的视图了。例如:
- UrlBaseViewResolver系列的解析器都是针对单一视图类型进行解析的,只需要找到使用的模板就可以了。
- InternalResourceViewResolver只针对jsp类型的视图
- FreeMarkerViewResolver只针对FreeMarker类型的视图
- VelocityViewResolver只针对Velocity类型的视图
- ResourceBundleViewResolver、XMLViewResolver、BeanNameViewResolver等解析器都可以同时解析多种类型的视图
(ResourceBundleViewResolver通过配置文件properties配置文件来解析,XMLViewResolver通过XML文件进行解析,BeanNameViewResolver通过ViewName从ApplicationContext容器中查找相应的bean做View的)
BeanNameViewResolver源码如下:
public class BeanNameViewResolver extends WebApplicationObjectSupport implements ViewResolver, Ordered {
private int order = 2147483647;
public BeanNameViewResolver() {
}
public void setOrder(int order) {
this.order = order;
}
public int getOrder() {
return this.order;
}
@Nullable
public View resolveViewName(String viewName, Locale locale) throws BeansException {
ApplicationContext context = this.obtainApplicationContext();
if (!context.containsBean(viewName)) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("No matching bean found for view name '" + viewName + "'");
}
return null;
} else if (!context.isTypeMatch(viewName, View.class)) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Found matching bean for view name '" + viewName + "' - to be ignored since it does not implement View");
}
return null;
} else {
return (View)context.getBean(viewName, View.class);
}
}
}
总结:ViewResolver使用需要注册到SpringMVC的容器里,默认是使用的org.springframerwork.web.servlet.view.InternalResourceViewResolver(解析jsp模板的)
五、RequestToViewNameTranslator
ViewResolver是根据ViewName查找的View,但是有的Handler处理完后并没有设置View也没有设置viewName,这时候就需要从Request中获取到viewName了,而如何获取就需要RequestToViewNameTranslator来实现了。
接口源码:
public interface RequestToViewNameTranslator {
@Nullable
String getViewName(HttpServletRequest var1) throws Exception;
}
注意点:RequestToViewNameTranslator在SpringMVC中只能配置一个,所以所有request到ViewName的转换规则都是在一个Translator中全部实现的。
他有一个默认实现类DefaultRequestToViewNameTranslator源码如下:
public class DefaultRequestToViewNameTranslator implements RequestToViewNameTranslator {
private static final String SLASH = "/";
private String prefix = "";
private String suffix = "";
private String separator = "/";
private boolean stripLeadingSlash = true;
private boolean stripTrailingSlash = true;
private boolean stripExtension = true;
private UrlPathHelper urlPathHelper = new UrlPathHelper();
public DefaultRequestToViewNameTranslator() {
}
public void setPrefix(@Nullable String prefix) {
this.prefix = prefix != null ? prefix : "";
}
public void setSuffix(@Nullable String suffix) {
this.suffix = suffix != null ? suffix : "";
}
public void setSeparator(String separator) {
this.separator = separator;
}
public void setStripLeadingSlash(boolean stripLeadingSlash) {
this.stripLeadingSlash = stripLeadingSlash;
}
public void setStripTrailingSlash(boolean stripTrailingSlash) {
this.stripTrailingSlash = stripTrailingSlash;
}
public void setStripExtension(boolean stripExtension) {
this.stripExtension = stripExtension;
}
public void setAlwaysUseFullPath(boolean alwaysUseFullPath) {
this.urlPathHelper.setAlwaysUseFullPath(alwaysUseFullPath);
}
public void setUrlDecode(boolean urlDecode) {
this.urlPathHelper.setUrlDecode(urlDecode);
}
public void setRemoveSemicolonContent(boolean removeSemicolonContent) {
this.urlPathHelper.setRemoveSemicolonContent(removeSemicolonContent);
}
public void setUrlPathHelper(UrlPathHelper urlPathHelper) {
Assert.notNull(urlPathHelper, "UrlPathHelper must not be null");
this.urlPathHelper = urlPathHelper;
}
//重写的方法
public String getViewName(HttpServletRequest request) {
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
return this.prefix + this.transformPath(lookupPath) + this.suffix;
}
@Nullable
protected String transformPath(String lookupPath) {
String path = lookupPath;
if (this.stripLeadingSlash && lookupPath.startsWith("/")) {
path = lookupPath.substring(1);
}
if (this.stripTrailingSlash && path.endsWith("/")) {
path = path.substring(0, path.length() - 1);
}
if (this.stripExtension) {
path = StringUtils.stripFilenameExtension(path);
}
if (!"/".equals(this.separator)) {
path = StringUtils.replace(path, "/", this.separator);
}
return path;
}
}
六、LocaleResolver
在解析视图的时候需要两个参数:一个是视图名,一个是Locale,视图名是处理器(Handler)返回的(或者使用RequestToViewNameTranslator解析出来的默认视图名),而Locale这就是需要LocaleResolver来解析了。
LocaleResolver用于从request解析出Locale,它接口定义如下:
public interface LocaleResolver {
Locale resolveLocale(HttpServletRequest var1);
void setLocale(HttpServletRequest var1, @Nullable HttpServletResponse var2, @Nullable Locale var3);
}
SpringMVC中使用到Locale的地方只有两处:1)ViewResolver解析视图的时候;2)使用到国际化资源或者主题的时候。
那么在SpringMVC中如何提供人为修改Locale的机制呢,这个中间提供了一个Interceptor--org.springframework.web.servlet.i18n.LocaleChangeInterceptor,配置方法如下:
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/*" />
<bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
这样就可以通过locale参数来修改Locale了,比如
http://localhost:8080?locale=zh_CN
http://localhost:8080?locale=en
当然这里url中的“locale”也可以通过paramName设置为别的名称,如下设置为lang
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/*" />
<bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"
p:paramName="lang"/>
</mvc:interceptor>
</mvc:interceptors>
就可这样访问: http://localhost:8080?lang=zh_CN
七、ThemeResolver
这个主要是用来解主题的,源码如下:
public interface ThemeResolver {
String resolveThemeName(HttpServletRequest request);
void setThemeName(HttpServletRequest request, HttpServletResponse response, String themeName);
}
在SpringMVC中一套主题对应一个properties文件,里面存放着跟当前主题相关的所有资源,如图片、css样式表等
#theme.properties
logo.pic=/images/default/logo.jpg
logo.word=excelib
style=/css/default/style.css
将上面的theme.properties文件放到classpath中,如果在jsp页面中就可以使用<spring:theme code ="logo.word"/>了,(需要引入标签库<%talibprefix="spring" uri="http://www.springframework.org/tags"%>),另外这个SpringMVC也支持国际化,就是不同区域显示不同的冯科,可以定义这样的文件theme.properties;theme_zh_CN.properties,theme_en_US.properties.SpringMVC跟
主题的相关的类有:ThemeResolver、ThemeSource和Theme,ThemeResolver作用是从reques中解析出主题名,ThemeSource是根据主题名找到具体的主题,Theme是ThemeSource找到具体的主题,通过他获取到主题里面的资源。
代码:
//org.springframework.web.servlet.support.RequestContext;
public String getThemeMessage(String code, Object[] args, String defaultMessage) {
return getTheme().getMessageSource().getMessage(code, args, defaultMessage, this.locale);
}
public Theme getTheme() {
if (this.theme == null) {
// Lazily determine theme to use for this RequestContext.
this.theme = RequestContextUtils.getTheme(this.request);
if (this.theme == null) {
// No ThemeResolver and ThemeSource available -> try fallback.
this.theme = getFallbackTheme();
}
}
return this.theme;
}
//org.springframework.web.servlet.support.RequestContextUtils
public static Theme getTheme(HttpServletRequest request) {
ThemeResolver themeResolver = getThemeResolver(request);
ThemeSource themeSource = getThemeSource(request);
if (themeResolver != null && themeSource != null) {
String themeName = themeResolver.resolveThemeName(request);
return themeSource.getTheme(themeName);
}
else {
return null;
}
}
从RequestContextUtils的代码中就可以看到ThemeResolver和ThemeSource的作用。ThemeResolver的默认实现是org.springframework.web.servlet.theme.FixedThemeResolver
在讲SpringMVC容器创建时介绍过WebApplicationContext是在FrameworkServlet中创建的,默认使用的是XmlWebApplicationContext,它的父类是AbstractRefreshableWebApplicationContext,这个类实现了ThemeSource接口,实现方式是在内部封装了一个ThemeSource属性,然后将具体工作交给它。
这里可以把ThemeSource理解成一个配置文件,默认使用的是WebApplicationContext。ThemeResolver默认使用的是FixedThemeResolver。这些我们可以自己配置,例如:
<bean id="themeSource" class="org.springframework.ui.context.support.ResourceBundleThemeSource"
p:basenamePrefix="com.excelib.themes."/>
<bean id="themeResolver" class="org.springframework.web.servlet.theme.CookieThemeResolver"
p:defaultThemeName="default"/>
这里不仅配置了themeResolverhe和themeSource,而且还配置了默认主题名“default”,以及配置文件的位置在com.excelib.themes包下(注意配置后面有“.”)。
怎么切换主题呢?同理和Locale相似,使用interceptor来实现,如下所示:
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/*"/>
<bean class="org.springframework.web.servlet.theme.ThemeChangeInterceptor"
p:paramName="theme"/>
<mvc:interceptor/>
<mvc:interceptors/>
可以通过paramName修改主题的参数名,默认使用“theme”,下面的请求就可以切换主题http://localhost:8080?theme=summer
八、MultipartResolver
用于处理上传请求,处理方法时将普通的request包装成MultipartHttpServletRequest,后者可以直接调用getFile方法获取File,如果上传多个文件,还可以调用getFileMap得到FileName->File结构的Map,这样就使得上传请求的处理变得非常简单。
然后,其实不包装直接用request也可以,
因为并不是每一个请求都需要上传文件,所以SpringMVC中此组件没有提供默认值。MultipartResolver代码如下:
public interface MultipartResolver {
boolean isMultipart(HttpServletRequest request);//判断是不是上传请求,判断是不是multipart/form-data类型
//将request包装成MultipartHttpServletRequest
MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException;
void cleanupMultipart(MultipartHttpServletRequest request);//清除上传过程中产生的临时资源
}
九、FlashMapManager
FlashMap主要用在redirect中传递参数。而FlashMapManager是用来管理FlashMap的,定义如下:
public interface FlashMapManager {
//恢复参数,将恢复过的和超时的参数从保存介质中删除;
FlashMap retrieveAndUpdate(HttpServletRequest request, HttpServletResponse response);
//用于将参数保存起来。
void saveOutputFlashMap(FlashMap flashMap, HttpServletRequest request,
HttpServletResponse response);
}
默认实现是org.springframework.web.servlet.support.SessionFlashMapManager。它将参数保存在session中,实现原理就是利用session作为中转站保存request中的参数,达到redirect传递参数的。
整个redirect的参数通过FlashMap传递的过程分三步:
1)在处理器中将需要传递的参数设置到outputFlashMap中,设置办法有:
@RequestMapping(value = "/submit",method = RequestMethod.POST)
public String submit(RedirectAttributes attributes){
//方式一存值
((FlashMap)((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().
getAttribute(DispatcherServlet.OUTPUT_FLASH_MAP_ATTRIBUTE)).put("name","vison");
//方式二存值
attributes.addFlashAttribute("ordersId","XXX");
return "redirect:showorders";
}
2)在RedirectView的renderMergedOutputModel方法中调用FlashMapManager的saveOutputFlashMap方法将outputFlashMap的参数设置到Session中
3)请求redirect后DispatcherServlet的doService会调用FlashMapManager的retrieveAndUpdate方法从Session中获取inputFlashMap并设置到Request的属性中备用,同时从Session中删除。