SpringMVC源码分析--DispatcherServlet初始化的九大组件的总体概述(三)

在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中删除。

猜你喜欢

转载自blog.csdn.net/weixin_40792878/article/details/81676489