SpringMVC源代码学习(四)九大组件

以下内容基于书:《看透SpringMVC-源代码分析与实践》基本照搬。。。用于自己查阅备忘。

1. HandlerMapping

在dispatcherServlet,doDispatch方法中有调用getHandler,代码如下: 
其中List< HandlerMapping> handlerMappings是dispatcherServlet的内部变量。 
那该方法的内容就是遍历handlerMappings,获得符合条件的HandlerMapping,调用其getHandler方法,返回获得的HandlerExecutionChain

    protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        for (HandlerMapping hm : this.handlerMappings) {
            if (logger.isTraceEnabled()) {
                logger.trace(
                        "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
            }
            HandlerExecutionChain handler = hm.getHandler(request);
            if (handler != null) {
                return handler;
            }
        }
        return null;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

HandlerMapping是一个接口,内部只有一个方法和诺干变量,它的作用是根据request找到对应的Handler。方法如下: 
HandlerExecutionChain getHandler(HttpSevletRequest request) throws Exception 
接下来看看一个该方法的实现,SimpleControllerHandlerAdapter,代码如下:

    public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        Object handler = getHandlerInternal(request);
        if (handler == null) {
            handler = getDefaultHandler();
        }
        if (handler == null) {
            return null;
        }
        // Bean name or resolved handler?
        if (handler instanceof String) {
            String handlerName = (String) handler;
            handler = getApplicationContext().getBean(handlerName);
        }

        HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
        if (CorsUtils.isCorsRequest(request)) {
            CorsConfiguration globalConfig = this.corsConfigSource.getCorsConfiguration(request);
            CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
            CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
            executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
        }
        return executionChain;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

另外一个要讨论的就是顺序问题,不同hander负责映射的条件可能有重复的,这时候就需要定义不同的HandlerMapping执行的顺序,这里的顺序可以通过实现Order接口,通过Order属性定义。order越小越先使用。如:

<bean class="com.excelib.TudouHandlerMapping"
    p:order="1"/>
<bean class="com.excelib.TudoupianHandlerMapping"
    p:order="0"/>
  • 1
  • 2
  • 3
  • 4

2.HandlerAdapter

在dispatcherServlet通过如下方法获得HandlerAdapter,其中List< HandlerAdapter> handlerAdapters是dispatcherServlet的成员变量,可以看到它的逻辑是遍历所有的Adapter,然后检查哪个可以处理当前的Handler,找到第一个可以处理Handler的Adapter后停止查找,返回。这里的顺序同样是通过Order属性设置的。

    protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
        for (HandlerAdapter ha : this.handlerAdapters) {
            if (logger.isTraceEnabled()) {
                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");
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

上篇文章有介绍,HandlerAdapter的角色是使用工具(handler)的人。因为handler是Object类型,需要HandlerAdapter使用它来完成一定格式要求的任务。 
它是一个接口,代码如下:

    public interface HandlerAdapter {   
        boolean supports(Object handler);   
        ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
       long getLastModified(HttpServletRequest request, Object handler);
  • 1
  • 2
  • 3
  • 4

接下来看一个spring自己实现的示例,代码如下:

public class SimpleControllerHandlerAdapter implements HandlerAdapter {

    @Override
    public boolean supports(Object handler) {
        return (handler instanceof Controller);
    }

    @Override
    public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {

        return ((Controller) handler).handleRequest(request, response);
    }

    @Override
    public long getLastModified(HttpServletRequest request, Object handler) {
        if (handler instanceof LastModified) {
            return ((LastModified) handler).getLastModified(request);
        }
        return -1L;
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

可以看到这个Adapter比较简单,它要求handler实现了Controller接口,方法的实现是通过处理器的handleRequest方法。

3. HandlerExceptionResolver

HandlerExceptionResolver是SpringMVC中专门负责处理异常的类。它负责: 
根据异常设置ModelAndView。 
之后交给render方法进行渲染。render只负责将Model渲染成页面。具体ModelAndView的来源render并不关心。

public interface HandlerExceptionResolver {
    ModelAndView resolveException(
            HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex);

}
  • 1
  • 2
  • 3
  • 4
  • 5

它的结构很简单,只有一个方法,只需要从异常解析出ModelAndView就可以了。具体实现可以维护一个异常为Key、View为value的Map,解析时直接从Map里获取View。

4.ViewResolver

ViewResolver用来将String类型的视图名(也叫逻辑视图)和Locale解析为View类型的视图,ViewResolver接口也很简单,代码如下

public interface ViewResolver {
    View resolveViewName(String viewName, Locale locale) throws Exception;

}
  • 1
  • 2
  • 3
  • 4

这里可以看到参数是viewName和locale,不过一般我们只要根据视图名找到视图,然后渲染就可以,如果需要国际化支持也只要将显示的内容或者主题使用国际化支持。 
View是用来渲染页面的,也就是将程序返回的参数填入模板中,生成html或其他格式的文件。所以说它会解决两个问题: 
1. 填入哪个模板? 
2. 如何填入? 
它会找到渲染所用的模板和所用的技术(也就是视图的类型)进行渲染,具体的渲染过程则交给不同的视图自己完成。 
我们最常用的UrlBasedViewResolver系列的解析器都是针对单一视图类型进行解析的。比如InternalResourceViewResolver只针对jsp类型的视图,FreeMarkerViewResolver只针对FreeMarker,VelocityViewResolver只针对Velocity。而ResourceBundleViewResolver、XmlViewResolver、BeanNameViewResolver等解析器可以同时解析多种类型的视图。 
ResourceBundleViewResolver使用properties配置文件来进行配置解析的文件类和url、XmlViewResolver使用xml配置。BeanNameViewResolver是根据ViewName从ApplicationContext容器中查找相应的bean做View的,它比较简单,源码如下:

public class BeanNameViewResolver extends WebApplicationObjectSupport implements ViewResolver, Ordered {
    private int order = Integer.MAX_VALUE;  // default: same as non-Ordered
    public void setOrder(int order) {
        this.order = order;
    }
    @Override
    public int getOrder() {
        return this.order;
    }
    @Override
    public View resolveViewName(String viewName, Locale locale) throws BeansException {
        ApplicationContext context = getApplicationContext();
        if (!context.containsBean(viewName)) {
            if (logger.isDebugEnabled()) {
                logger.debug("No matching bean found for view name '" + viewName + "'");
            }
            // Allow for ViewResolver chaining...
            return null;
        }
        if (!context.isTypeMatch(viewName, View.class)) {
            if (logger.isDebugEnabled()) {
                logger.debug("Found matching bean for view name '" + viewName +
                        "' - to be ignored since it does not implement View");
            }
            // Since we're looking into the general ApplicationContext here,
            // let's accept this as a non-match and allow for chaining as well...
            return null;
        }
        return context.getBean(viewName, View.class);
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

可以看出原理就是根据viewName从spring容器中查找Bean,if(找不到||不是view类型) return null; else return context.getBean(viewName, View.class); 

ViewResolver的使用需要注册到Spring MVC容器中,默认使用的是org.springframework.web.servlet.view.InternalResourceViewResolver。

5. RequestToViewNameTranslator

ViewResolver根据ViewName查找View,但有的Handler处理完并没有设置View,也没有设置viewName,这时就需要从request中获取viewName。也就是RequestToViewNameTranslator的任务。它是个接口,代码如下:

public interface RequestToViewNameTranslator {
    String getViewName(HttpServletRequest request) throws Exception;
}
  • 1
  • 2
  • 3

就像之前说的,他只有一个getViewName方法,能够从request获取到viewName就可以了。下面是一个简单实现的示例:

if(request.getRequestURI().toString().startsWith("/todou")&&request.getMethod().equalsIgnoreCase("GET"))
    return "maidigua";
else
    return "404";
  • 1
  • 2
  • 3
  • 4

RequestToViewNameTranslator在Spring MVC容器中只能配置一个,所以所有request到ViewName的转换规则都要在一个Translator里面实现。

6. LocaleResolver

还记得ViewResolver有两个参数,viewname和Locale,viewname来自Handler或RequestToViewNameTranslator。locale变量就来自LocaleResolver。 
LocaleResolver用于从request解析出Locale。

Locale就是zh-cn之类的,表示一个区域。有了这个就可以对不同区域的用户显示不同的结果,这就是il18n(国际化)的基本原理。

LocaleResolver是个接口,代码如下:

public interface LocaleResolver {
    Locale resolveLocale(HttpServletRequest request);
    void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale);

}
  • 1
  • 2
  • 3
  • 4
  • 5

一共只有两个方法,第一个方法就是起到获取Locale的作用,在介绍doService方法时说过,容器会将localeResolver设置到request的attribute中。 
代码如下:

request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE,this.localeResolver);

切换Locale: 
第二个方法可以将Locale设置到request中。 
SpringMVC提供了统一修改request中Locale的机制,就是我们在分析doDispatch时见过的Interceptor。SpringMVC已经写好现成的了,配置一下就可以,也就是org.springframework.web.servlet.i18n.LocaleChangeInterceptor. 
配置方法如:

<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/*"/>
        <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"/>
        //这里也可以自定义参数的名称,如:
        //<bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor" p:paramName="lang"/>
    mvc:interceptor>
mvc:interceptors>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

这样通过request的locale参数就可以修改Locale了,如:http://localhost:8080?locale=zh_CN 
http://localhost:8080?locale=en 
用到Locale的地方有两处,1、ViewResolver解析视图的时候。2、使用到国际化资源或者主题的时候。国际化资源或主题主要使用RequestContext的getMessage和getThemeMessage方法。

7.ThemeResolver

它是个接口,源码如下:

public interface ThemeResolver {
    String resolveThemeName(HttpServletRequest request);
    void setThemeName(HttpServletRequest request, HttpServletResponse response, String themeName);
}
  • 1
  • 2
  • 3
  • 4

SpringMVC中一套主题对应一个properties文件,里面存放着跟当前主题相关的所有资源,如图片、css样式表等,如:

#theme.properties
logo.pic=/images/default/logo.jpg
logo.word=excelib
style=/css/default/style.css
  • 1
  • 2
  • 3
  • 4

SpringMVC跟主题有关的类主要有ThemeResolver、ThemeSource和Theme。 
1. ThemeResolver的作用是从request解析出主题名。 
2. ThemeSource的作用是根据主题名找到具体的主题。 
3. 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;
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

从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"/>
  • 1
  • 2

这里配置了themeResolver,themeSource默认主题名为default,配置文件在comexcelib.themes包下。 
切换Theme:同切换Locale相同,依然是使用Interceptor。配置如下:

<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/" />
        <bean class="org.springframework.web.servlet.theme.ThemeChangeInterceptor" p:paramName="theme"/>
    mvc:interceptor>
mvc:interceptors>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

可以通过paramName设置修改主题的参数名,默认使用”theme”。下面的请求可以切换为summer主题。

8.MultipartResolver

用于处理上传请求,处理方法时将普通的request包装成MultipartHttpServletRequest,后者可以直接调用getFile方法获取File,如果上传多个文件,还可以调用getFileMap得到FileName->File结构的Map,这样就使得上传请求的处理变得非常简单。 
然后,其实不包装直接用request也可以。所以SpringMVC中此组件没有提供默认值。MultipartResolver代码如下:

public interface MultipartResolver {
    boolean isMultipart(HttpServletRequest request);//判断是不是上传请求
    MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException;//将request包装成MultipartHttpServletRequest
    void cleanupMultipart(MultipartHttpServletRequest request);//清除上传过程中产生的临时资源
}
  • 1
  • 2
  • 3
  • 4
  • 5

9.FlashMapManager

FlashMap在之前的文章1中提到过了,主要用在redirect中传递参数。而FlashMapManager是用来管理FlashMap的,定义如下:

public interface FlashMapManager {
    FlashMap retrieveAndUpdate(HttpServletRequest request, HttpServletResponse response);//恢复参数,将恢复过的和超时的参数从保存介质中删除;
    void saveOutputFlashMap(FlashMap flashMap, HttpServletRequest request, HttpServletResponse response);//用于将参数保存起来。
}
  • 1
  • 2
  • 3
  • 4

默认实现是org.springframework.web.servlet.support.SessionFlashMapManager。它将参数保存在session中,实现原理就是利用session作为中转站保存request中的参数,达到redirect传递参数的。


  1. http://blog.csdn.net/xia4820723/article/details/51345559 SpringMVC源代码学习外传(二)如何在重定向时传递参数&FlashMap。 ↩

你可能感兴趣的:(Spring-MVC)