SpringMVC4-组件预览

《看透springMvc源代码分析与实践》学习笔记
SpringMVC 版本 4.1.5.RELEASE

组件预览

HandlerMapping

HandlerMapping的作用是:根据request找到相应的处理器Handler和Interceptors,

HandlerMapping接口只有一个方法:

//org.springframework.web.servlet.HandlerMapping
public interface HandlerMapping {
    //HandlerExecutionChain(包括 处理器Handler和Interceptors等)
    HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}

方法的实现非常灵活,只需使用request返回HandlerExecutionChain即可,我们也可以定义自己的HandlerMapping,只需要实现getHandler方法,并注册到spring容器即可 。

DispatcherServlet.doDispatch()方法中使用getHandler(),来匹配获取处理器:

	protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		for (HandlerMapping hm : this.handlerMappings) {
		    //logger 略.....
			HandlerExecutionChain handler = hm.getHandler(request);
			if (handler != null) {
				return handler;
			}
		}
		return null;
	}

,通过这段逻辑可以找到一个request对应的

如果一个request找到了与之对应的HandlerMapping,如何决定使用哪一个呢?

    
	
	

HandlerAdapter

HandlerAdapter可以理解为使用Handler干活的人。它一共有三个方法:

//org.springframework.web.servlet.HandlerAdapter

public interface HandlerAdapter {
    //判断当前HandlerAdapter是否支持使用给出的参数handler
    boolean supports(Object handler); 
    
    //使用具体Handler处理request请求
    ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
    
    //获取资源最后一次处理时间
    long getLastModified(HttpServletRequest request, Object handler);
}

之所以要使用HandlerAdapter,是因为Spring MVC没有对处理器有任何限制,它是Object类型的,这种模式给开发者提供了极大的便利。

HandlerMapping一样,HandlerAdapter也必须注册到spring容器中才能使用。
选择使用哪一个HandlerAdapter,它的逻辑定义在DispatcherServlet.getHandlerAdapter()中:

	protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
		for (HandlerAdapter ha : this.handlerAdapters) {
		    //logger 略.....
			if (ha.supports(handler)) {
				return ha;
			}
		}
		
		//未找到匹配HandlerAdapter,抛出异常...
		throw new ServletException("No adapter for handler [" + handler +
				"]: Does your handler implement a supported interface like Controller?");
	}

HandlerExceptionResolver

别的组件都是在正常情况下用来干活的,不过干活的过程中难免会出现问题,出现问题后就需要HandlerExceptionResolver对异常进行处理。
具体来说,此组件的作用是根据异常设置ModelAndView,之后再交给render方法进行渲染。
HandlerExceptionResolver的接口非常简单:

//org.springframework.web.servlet.HandlerExceptionResolver
public interface HandlerExceptionResolver {
    ModelAndView resolveException(
			HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex);
}

ViewResolver

ViewResolver用来将String类型的视图名和Locale解析为View类型视图的组件,ViewResolver接口非常简单:

//org.springframework.web.servlet.ViewResolver
public interface ViewResolver {
    View resolveViewName(String viewName, Locale locale) throws Exception;
}

从接口方法的定义可以看出解析视图所需的参数是viewName和Locale,一般情况下我们只需要根据viewName找到对应的视图,然后进行渲染就行了,并不需要对不同
的区域使用不同的视图进行显示。
如果需要支持国际化或不同主题时,可以使用此参数。
ResourceBundleViewResolver,它就需要同时使用viewName和locale来解析视图。
ResourceBundleViewResolver需要将每一个视图名和对应的视图类型配置到相应的properties文件中,默认使用classpath下的views为baseName的配置文件,
如:views.properties,views_zh_CN.properties等,配置文件中需要同时配置class和url两项内容:

hello.(class)=org.springframework.web.servlet.view.InternalResourceView
hello.url=/WEB-INF/go.jsp

View是用来渲染页面的,通俗来说就是将程序返回的参数填入模板里,生成html(也可以是其他类型)文件,这里有两个关键问题:

  • 使用哪个模板
  • 用什么技术填入参数

这其实就是ViewResolver主要做的工作,ViewResolver需要找到渲染使用的模板和所用的技术(也就是视图类型)进行渲染,具体的渲染过程则交给不同的视图自己完成。
常用的ViewResolver如下图,默认使用(org.springframework.web.servlet.view.InternalResourceViewResolver)

SpringMVC4-组件预览_第1张图片


RequestToViewNameTranslator

ViewResolver是根据viewName查找View,但是有的Handler处理完之后并没有设置View也没有设置viewName,这时就需要从request获取viewName了,
如何从request中获取viewName,就是RequestToViewNameTranslator需要做的工作。它的接口定义如下,它仅有一个方法:

//org.springframework.web.servlet.RequestToViewNameTranslator
public interface RequestToViewNameTranslator {
    //根据request获取viewName
	String getViewName(HttpServletRequest request) throws Exception;

}

例:

public class MyRequest2ViewNameTranslater implements RequestToViewNameTranslator {
	@Override
	public String getViewName(HttpServletRequest request) throws Exception {
		if(request.getRequestURI().toString().startsWith("/oa") && request.getMethod().equalsIgnoreCase("GET")){
			return "oaIndexPage";
		}
		return "404";
	}
}

注意:RequestToViewNameTranslator在SpringMvc容器里只能配置一个,所有request到viewName的转换规则都要在一个RequestToViewNameTranslator中全部实现.


LocaleResolver

解析视图需要两个参数:一个视图名viewName,另一个local. 视图名是处理结果返回的(或者RequestToViewNameTranslator解析的默认视图名),Local从哪里来。
这个就是LocalResolver要做的工作。LocalResolver用于从request中解析出Local.它是i18n的基础。
LocaleResolver接口也很简单,定义了两个方法:

//org.springframework.web.servlet.LocaleResolver
public interface LocaleResolver {

    //从request中解析出Locale
	Locale resolveLocale(HttpServletRequest request);

    //将Locale设置给某个request
	void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale);
}

DispatcherServlet.doService()中定义了如下代码:

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //省略其他.....
        
		request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
		
		//将localeResolver,themeResolver等设置到request-attribute中
		request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
		request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
		request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
}

将localeResolver设置到request-attribute中,这样我们在需要使用Locale时,直接从request中取出localeResolver然后解析出Locale.
SpringMVC主要在如下两个地方使用到Locale:

  • ViewResolver解析视图时:上文已有说明
  • 使用国际化资源或者主题时:
    • 主要是使用RequestContext的getMessage()和getThemeMessage()方法时。
    • 以及我们通常在jsp文件中使用:
    <%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
    
    
    

手动修改Local
许多网站可以手动选择语言,这就需要提供手动修改Locale的接口。在SpringMVC中非常简单,只需要调用LocaleResolver.setLocale方法即可。

  • 我们可以提供一个Controller专门用来修改Locale. 麻烦、请求跳转后可能会丢失,不推荐
  • 通过配置Interceptor:SpringMVC提供一个org.springframework.web.servlet.i18n.LocaleChangeInterceptor,配置方式如下:
	
		
			
			
			
		
	

然后,我们可以通过在请求中增加参数locale来修改Locale了:如:http://localhost:8080?locale=zh_CN


ThemeResolver

ThemeResolver是用来解析主题的,它的接口定义如下:

//org.springframework.web.servlet.ThemeResolver
public interface ThemeResolver {
    //根据request获取themeName
	String resolveThemeName(HttpServletRequest request);
    
    //将themeName设置到指定的request
	void setThemeName(HttpServletRequest request, HttpServletResponse response, String themeName);
}

不同的主题其实就是:换了一套图片、显示效果以及样式文件等。
SpringMVC中一套主题对应一个.properties文件,里面存放着跟当前主题相关的所有资源,如图片,css文件等: 例如:

### theme.propertie
logo.pic=/images/default/logo.jpg
logo.word=excelib
style=/css/default/style.css

将上面的文件命名为theme.properties,放到classpath下面就可以在页面中使用了。
例,在jsp页面中:就可以获得配置的excelib了。
现在所用的主题名就叫theme,主题名就是文件名,所以创建主题非常简单,只需要准备好资源,创建一个以主题名命名的properties文件即可。

获取主题资源的逻辑,依然在RequestContext中:

//org.springframework.web.servlet.support.RequestContext
    public String getThemeMessage(String code, String defaultMessage) {
		return getTheme().getMessageSource().getMessage(code, null, defaultMessage, this.locale);
	}
	
    public final 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;
    }

由此看出SpringMvc获取主题资源的大致逻辑 :

  1. ThemeResolver解析request获取themeName
  2. ThemeSource根据themeName找到具体的Theme
  3. 获取Theme获取具体的资源

ThemeResolver默认使用FixedThemeResolver,ThemeSource默认使用 WebApplicationContext,我们也可以自己来配置:






我们可以参照切换Locale方式切换主题:


    
        
        
        
    


MultipartResolver

MultipartResolver用于处理上传请求,处理方式是将普通的request包装成MultipartHttpServletRequest,

  • 可以直接调用getFile()方法,获取文件
  • 若上传多个文件,可以调用getFileMap得到{fileName -> file}结构的Map.

如果不使用MultipartResolver将request封装成MultipartHttpServletRequest,直接使用request,也是可以获取上传的文件的。

MultipartResolver接口定义如下:

//org.springframework.web.multipart.MultipartResolver
public interface MultipartResolver {
    //判断是否为上传请求
	boolean isMultipart(HttpServletRequest request);

    //将request封装为MultipartHttpServletRequest
	MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException;

    //处理完后,清理上传过程中产生的临时资源
	void cleanupMultipart(MultipartHttpServletRequest request);

}

判断是否为上传请求:可以简单的判断是不是multipart/form-data 类型。


FlashMapManager

FlashMap主要用于在redirect中传递参数。而FlashMapManager便是用来管理FlashMap的。
FlashMapManager的接口定义如下:

//org.springframework.web.servlet.FlashMapManager
public interface FlashMapManager {
    //接收从其他request传递过来的request-attributes的map , 只读(readonly)
    String INPUT_FLASH_MAP_ATTRIBUTE = FlashMapManager.class.getName() + ".INPUT_FLASH_MAP";
    
    //需要传递出去持有request-attributes的map
    String OUTPUT_FLASH_MAP_ATTRIBUTE = FlashMapManager.class.getName() + ".OUTPUT_FLASH_MAP";
    
    /**
     * a. 获取"input" FlashMap,并从底层缓存中将它移除。
     * b. 创建"output" FlashMap 
     * c. 清空过期的 FlashMap
     */
    void requestStarted(HttpServletRequest request);
    
    /**
     * a. 开始计算"output" FlashMap在底层缓存中的过期时间
     * 
     * 注: 如果"output" FlashMap或者不是由当前FlashMapManager创建的话,则不会保存到底层缓存中
     */
    void requestCompleted(HttpServletRequest request);
}

例:

@RequestMapping("/submit")
public String submit(RedirectAttributes attr) {
	//获取FlashMap方法1
	((FlashMap) ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
			.getRequest().getAttribute(FlashMapManager.OUTPUT_FLASH_MAP_ATTRIBUTE)).put("name", "特朗普传");

	//获取FlashMap方法2:RedirectAttributes.addFlashAttribute:: 同方法一效果
	attr.addFlashAttribute("orderId", "9901");

	//获取FlashMap方法3:RedirectAttributes.addAttribute ::参数会拼接到url后面。
	attr.addAttribute("price", "zh-cn");
	return "redirect:detail";
}

@RequestMapping("/detail")
public String detail(Model model) {
	//和普通方式获取attribute相同
	System.out.println(model.asMap());
	return "success";
}

你可能感兴趣的:(springMVC)