SpringMVC源碼分析(三)

1.DispatcherServlet源碼分析

1.ViewResolver

先來看一下繼承關係

在這裏插入圖片描述

  • AbstractCachingViewResolver:AbstractCachingViewResolver是一個抽象類,這種視圖解析器會把它曾經解析過的視圖保存起來,然後每次要解析視圖的時候先從緩存裏面找,如果找到了對應的視圖就直接返回,如果沒有就創建一個新的視圖對象,然後把它放到一個用於緩存的map中,接着再把新建的視圖返回。使用這種視圖緩存的方式可以把解析視圖的性能問題降到最低。
  • ContentNegotiatingViewResolver:RESTful服務中很重要的一個特性即是同一資源,多種表述我們使用ContentNegotiatingViewResolver就可以做到,這個視圖解析器允許你用同樣的內容數據來呈現不同的view
  • StaticViewResolver:
  • BeanNameViewResolver:BeanNameViewResolver視圖解析器跟XmlViewResolver有點類似,也是通過把返回的邏輯視圖名稱去匹配定義好的視圖bean對象。
  • ViewResolverComposite:ViewResolverComposite簡單來說就是使用簡單的List來保存你配置使用的視圖解析器。

ViewResolverComposite實現了InitializingBean接口,也就是說當實例化對象的時候會調用afterPropertiesSet方法:我們就不說這個類了,實在是太簡單了

1.ContentNegotiatingViewResolver

​ ContentNegotiatingViewResolver解析器的作用是在別的解析器解析的結果上增加了對MediaType和後綴的支持, MediaType即媒體類型,有的地方也叫Content-Type, 比如,常用的texthtml, textjavascript以及表示上傳表單的multipartform-data等。對視圖的解析並不是自己完成的而是使用所封裝的ViewResolver來進行的。整個過程是這樣的:首先遍歷所封裝的ViewResolver具體解析視圖,可能會解析出多個視圖,然後再使用request獲取MediaType,也可能會有多個結果,最後對這兩個結果進行匹配查找出最優的視圖。

​ ContentNegotiating ViewResolver具體視圖解析是使用的所封裝的viewResolvers屬性裏的ViewResolver來解析的, viewResolvers有兩種初始化方法,一種方法是手動設置,另外一,種方法是如果沒有設置則自動獲取spring容器中除了它自己外的所有ViewResolver並設置到viewResolvers,如果是手動設置的,而且不在spring容器中(如果使用的是引用配置就會在容器中),會先對它進行初始化,代碼如下:

protected void initServletContext(ServletContext servletContext) {
    // 獲取容器中所有ViewResolver類型的bean,注意這裏是從整個spring容器而不只是SpringMvC容器中獲取的
    Collection<ViewResolver> matchingBeans =
            BeanFactoryUtils.
                beansOfTypeIncludingAncestors(obtainApplicationContext(),
                                                        ViewResolver.class).values();
    // 如果沒有手動註冊則將容器中找到的ViewResolver設置給viewResolvers
    if (this.viewResolvers == null) {
        this.viewResolvers = new ArrayList<>(matchingBeans.size());
        for (ViewResolver viewResolver : matchingBeans) {
            if (this != viewResolver) {
                this.viewResolvers.add(viewResolver);
            }
        }
    }
    else {
        // 如果是手動註冊的,並且在容器中不存在,則進行初始化
        for (int i = 0; i < this.viewResolvers.size(); i++) {
            ViewResolver vr = this.viewResolvers.get(i);
            if (matchingBeans.contains(vr)) {
                continue;
            }
            String name = vr.getClass().getName() + i;
            obtainApplicationContext().
                                    getAutowireCapableBeanFactory().
                                        						initializeBean(vr, name);
        }

    }
    if (this.viewResolvers.isEmpty()) {
        ...打印日誌
    }
    AnnotationAwareOrderComparator.sort(this.viewResolvers);
    this.cnmFactoryBean.setServletContext(servletContext);
}

1.解析視圖

public View resolveViewName(String viewName, Locale locale) throws Exception {
    // 使用RequestContextHolder獲取RequestAttributes,進而在下面獲取request
	RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
    // 獲取所有候選視圖,內部通過遍歷封裝的viewResolvers來解析
	List<MediaType> requestedMediaTypes = 
        				getMediaTypes(((ServletRequestAttributes) attrs).getRequest());
	if (requestedMediaTypes != null) {
		List<View> candidateViews = getCandidateViews(viewName, 
                                                      locale, 
                                                      requestedMediaTypes);
        // 從多個候選視圖中找出最優視圖
		View bestView = getBestView(candidateViews, requestedMediaTypes, attrs);
		if (bestView != null) {
			return bestView;
		}
	}
	if (this.useNotAcceptableStatusCode) {
        ...拋出異常
		return NOT_ACCEPTABLE_VIEW;
	}
	else {
		...
		return null;
	}
}

通過註釋大家可以看到,整個過程是這樣的:首先使用request獲取MediaType作爲需要滿足的條件,然後使用viewResolvers解析出多個候選視圖,最後將兩者進行匹配找出最優視圖。獲取request使用的是在前面分析FrameworkServlet時介紹過的RequestContextHolder,如果記不清楚了可以返回去再看一下。接下來看一下獲取候選視圖的getCandidateViews方法。

private List<View> getCandidateViews(String viewName, 
                                     Locale locale, 
                                    List<MediaType> requestedMediaTypes)throws Exception{

    List<View> candidateViews = new ArrayList<>();
    if (this.viewResolvers != null) {
        for (ViewResolver viewResolver : this.viewResolvers) {
            View view = viewResolver.resolveViewName(viewName, locale);
            if (view != null) {
                candidateViews.add(view);
            }
            for (MediaType requestedMediaType : requestedMediaTypes) {
                List<String> extensions =
                this.contentNegotiationManager.resolveFileExtensions(requestedMediaType);
                for (String extension : extensions) {
                    String viewNameWithExtension = viewName + '.' + extension;
                    view = viewResolver.resolveViewName(viewNameWithExtension, locale);
                    if (view != null) {
                        candidateViews.add(view);
                    }
                }
            }
        }
    }
    if (!CollectionUtils.isEmpty(this.defaultViews)) {
        candidateViews.addAll(this.defaultViews);
    }
    return candidateViews;
}

​ 這裏的邏輯也非常簡單,首先遍歷viewResolvers進行視圖解析,並將所有解析出的結果添加到候選視圖,然後判斷有沒有設置默認視圖,如果有則也將它添加到候選視圖。不過這裏使用viewResolvers進行視圖解析的過程稍微有點複雜,除了直接使用邏輯視圖進行解析,"還使用了通過遍歷requestedMediaTypes獲取到所對應的後綴,然後添加到邏輯視圖後面作爲一個新視圖名進行解析。解析出候選視圖後使用getBestView方法獲取最優視圖,代碼如下:

private View getBestView(List<View> candidateViews, 
                         List<MediaType> requestedMediaTypes, 
                         RequestAttributes attrs) {
    // 判斷解候選視圖中有沒有redirect視圖,如果有直接將其返回
    for (View candidateView : candidateViews) {
        if (candidateView instanceof SmartView) {
            SmartView smartView = (SmartView) candidateView;
            if (smartView.isRedirectView()) {
                ...日誌
                return candidateView;
            }
        }
    }
    
    for (MediaType mediaType : requestedMediaTypes) {
        for (View candidateView : candidateViews) {
            if (StringUtils.hasText(candidateView.getContentType())) {
                // 根據候選視圖獲取對應的MediaType
                MediaType candidateContentType =
                    			MediaType.parseMediaType(candidateView.getContentType());
                if (mediaType.isCompatibleWith(candidateContentType)) {
                    ...打印日誌
                    attrs.setAttribute(View.SELECTED_CONTENT_TYPE, 
                                       mediaType, 
                                       RequestAttributes.SCOPE_REQUEST);
                    return candidateView;
                }
            }
        }
    }
    return null;
}

​ 首先判斷解候選視圖中有沒有redirect視圖,如果有直接將其返回,否則同時遍歷從request中獲取的MediaType,並使用當前的requestedMediaType對其進行判斷,如果支持則將所用的requestedMediaType添加到request的Attribute 中,以便在視圖渲染過程中使用,並將當前視圖返回。

2.AbstractCachingViewResolver系列

​ AbstractCachingViewResolver提供了統一的緩存功能,當視圖解析過一次就被緩存起來,直到緩存被刪除前視圖的解析都會自動從緩存中獲取。它的直接繼承類有三個:

  • ResourceBundleViewResolver:它是通過使用properties屬性配置文件解析視圖的;

  • XmlViewResolver:它是通過使用properties屬性配置文件解析視圖的;

  • UrlBasedViewResolver:第三個是所有直接將邏輯視圖作爲url查找模板文件的ViewResolver的基類,因爲它設置了統一的查找模板的規則,所以它的子類只需要確定渲染方式也就是視圖類型就可以了,它的每一個子類對應一種視圖類型。

    前兩種解析器的實現原理非常簡單,首先根據Locale將相應的配置文件初始化到BeanFactory,然後直接將邏輯視圖作爲beanName到factory裏查找就可以了。它們兩個的loadView的代碼是一樣的,如下:

    protected View loadView(String viewName, Locale locale) throws BeansException {
        BeanFactory factory = initFactory();
        try {
            return factory.getBean(viewName, View.class);
        }
        catch (NoSuchBeanDefinitionException ex) {
            // Allow for ViewResolver chaining...
            return null;
        }
    }
    
  1. 解析視圖
public View resolveViewName(String viewName, Locale locale) throws Exception {
    if (!isCache()) {
        return createView(viewName, locale);
    }
    else {
        Object cacheKey = getCacheKey(viewName, locale);
        View view = this.viewAccessCache.get(cacheKey);
        if (view == null) {
            synchronized (this.viewCreationCache) {
                view = this.viewCreationCache.get(cacheKey);
                if (view == null) {
                    // Ask the subclass to create the View object.
                    // 創建視圖
                    view = createView(viewName, locale);
                    if (view == null && this.cacheUnresolved) {
                        view = UNRESOLVED_VIEW;
                    }
                    if (view != null) {
                        this.viewAccessCache.put(cacheKey, view);
                        this.viewCreationCache.put(cacheKey, view);
                        if (logger.isTraceEnabled()) {
                            logger.trace("Cached view [" + cacheKey + "]");
                        }
                    }
                }
            }
        }
        return (view != UNRESOLVED_VIEW ? view : null);
    }
}

​ 邏輯非常簡單,首先判斷是否開啓了緩存功能,如果沒開啓則直接調用createView 創建視圖,否則檢查是否已經存在緩存中,如果存在則直接獲取並返回,否則使用create View創建一個,然後保存到緩存中並返回。createView內部直接調用了loadView方法,而loadView是一個模板方法,留給子類實際創建視圖,這也是子類解析視圖的人口方法。createView之所以調用了loadView而沒有直接作爲模板方法讓子類使用是因爲在loadView前可以統一做一些通用的解析,如果解析不到再交給loadView執行,這點在UrlBasedViewResolver中有具體的體現。

​ AbstractCachingViewResolver裏有個cacheLimit參數需要說一下,它是用來設置最大緩存數的,當設置爲0時不啓用緩存, isCache就是判斷它是否大於0,如果設置爲一個大於0的數則它表示最多可以緩存視圖的數量,如果往裏面添加視圖時超過了這個數那麼最前面緩存的值將會刪除。cacheLimit的默認值是1024,也就是最多可以緩存1024個視圖。

  1. 多知道點

LinkedHashMap中的自動刪除功能?

​ LinkedHashMap中保存的值是有順序的,不過除了這點還有一個功能,它可以自動冊除最前面保存的值,這個很多人並不知道。

​ LinkedHashMap中有一個removeEldestEntry方法,如果這個方法返回true, Map中最前面添加的內容將被刪除,它是在添加屬性的put或putAll方法被調用後自動調用的。這個功能主要是用在緩存中,用來限定緩存的最大數量,以防止緩存無限地增長。當新的值添加後,如果緩存達到了上限,最開頭的值就會被刪除,當然這需要設置,設置方法就是覆蓋removeEldestEntry方法,當這個方法返回true時就表示達到了上限,返回false就是沒達到上限,而size()方法可以返回現在所保存對象的數量,一般用它和設置的值做比較就可以了。AbstractCachingViewResolver中的viewCreationCache就是使用的這種方式,代碼如下:

private final Map<Object, View> viewCreationCache =
			new LinkedHashMap<Object, View>(DEFAULT_CACHE_LIMIT, 0.75f, true) {
				@Override
				protected boolean removeEldestEntry(Map.Entry<Object, View> eldest) {
					if (size() > getCacheLimit()) {
						viewAccessCache.remove(eldest.getKey());
						return true;
					}
					else {
						return false;
					}
				}
			};

​ 在AbstractCachingViewResolver中使用了兩個Map做緩存,它們分別是viewAccessCache和viewCreationCache。前者是ConcurrentHashMap類型,它內部使用了細粒度的鎖,支持併發訪問,效率非常高,而後者主要提供了限制緩存最大數的功能,效率不如前者高。使用的.最多的獲取緩存是從前者獲取的,而添加緩存會給兩者同時添加,後者如果發現緩存數量已達到上限時會在刪除自己最前面的緩存的同時也刪除前者對應的緩存。這種將兩種Map的優點結合起來的用法非常值得我們學習和借鑑。

1.UrlBasedViewResolver

​ UrlBasedViewResolver裏面重寫了父類的getCacheKey. createView和loadView三個方法。

​ getCachekey方法直接返回viewName,它用於父類AbstractCachingViewResolver中設置緩存的key,原來(AbstractCachingViewResolver中)使用的是viewName + “_” +locale,也就是說UrlBasedViewResolver的緩存中key沒有使用Locale只使用了viewName,從這裏可以看出UrlBasedViewResolver不支持Locale.

​ 在createView中首先檢查是否可以解析傳入的邏輯視圖,如果不可以則返回null讓別的ViewResolver解析,接着分別檢查是不是redirect視圖或者forward視圖,檢查的方法是看是不是以"redirect:"或"forward :"開頭,如果是則返回相應視圖,如果都不是則交給父類的createView,父類中又調用了loadView,代碼如下:

protected View createView(String viewName, Locale locale) throws Exception {
    // If this resolver is not supposed to handle the given view,
    // return null to pass on to the next resolver in the chain.
    // 檢查是否支持此邏輯視圖,可以配置支持的模板
    if (!canHandle(viewName, locale)) {
        return null;
    }

    // Check for special "redirect:" prefix.
    // 檢查是不是redirect視圖
    if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
        String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
        RedirectView view = new RedirectView(redirectUrl,
                                             isRedirectContextRelative(),
                                             isRedirectHttp10Compatible());
        String[] hosts = getRedirectHosts();
        if (hosts != null) {
            view.setHosts(hosts);
        }
        return applyLifecycleMethods(REDIRECT_URL_PREFIX, view);
    }

    // Check for special "forward:" prefix.
    // 查是不是forward視圖
    if (viewName.startsWith(FORWARD_URL_PREFIX)) {
        String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
        return new InternalResourceView(forwardUrl);
    }

    // Else fall back to superclass implementation: calling loadView.
    // 如果都不是則調用父類的createview,也就會調用loadview
    return super.createView(viewName, locale);
}

​ 其實這裏是爲所有UrlBasedViewResolver子類解析器統一添加了檢查是否支持傳入的邏輯視圖和傳入的邏輯視圖是不是redirect或者forward視圖的功能。檢查是否支持是調用的canHandle方法,它是通過可以配置的viewNames屬性檢查的,如果沒有配置則可以解析所有邏輯視圖,如果配置了則按配置的模式檢查,配置的方法可以直接將所有可以解析的邏輯視圖配置進去,也可以配置邏輯視圖需要滿足的模板,如"report"goto" "from*"等,代碼如下:

protected boolean canHandle(String viewName, Locale locale) {
    String[] viewNames = getViewNames();
    return (viewNames == null || PatternMatchUtils.simpleMatch(viewNames, viewName));
}
protected View loadView(String viewName, Locale locale) throws Exception {
    AbstractUrlBasedView view = buildView(viewName);
    View result = applyLifecycleMethods(viewName, view);
    return (view.checkResource(locale) ? result : null);
}

loadView一共執行了三句代碼:

①使用buildView方法創建View ;

②使用applyLifecycle. Methods方法對創建的View初始化;

③檢查view對應的模板是否存在,如果存在則將初始化的視圖返回,否則返回null交給下一個ViewResolver處理。

protected View applyLifecycleMethods(String viewName, AbstractUrlBasedView view) {
    ApplicationContext context = getApplicationContext();
    if (context != null) {
        Object initialized = context.getAutowireCapableBeanFactory().initializeBean(view,
                                                                               viewName);
        if (initialized instanceof View) {
            return (View) initialized;
        }
    }
    return view;
}

下面來看一下buildView方法,它用於具體創建View,理解了這個方法就知道AbstractUrBasedView系列中View是怎麼創建的了,它的子類只是在這裏創建出來的視圖的基礎上設置了一些屬性。所以這是AbstractUrlBasedView中最重要的方法,代碼如下:

protected AbstractUrlBasedView buildView(String viewName) throws Exception {
    Class<?> viewClass = getViewClass();
    Assert.state(viewClass != null, "No view class");

    AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.
        													instantiateClass(viewClass);
    view.setUrl(getPrefix() + viewName + getSuffix());
	// 如果contentType不爲null,將其值設置給view,可以在ViewResolver中配置
    String contentType = getContentType();
    if (contentType != null) {
        view.setContentType(contentType);
    }

    view.setRequestContextAttribute(getRequestContextAttribute());
    view.setAttributesMap(getAttributesMap());
	// 如果exposePathVariables不爲null,將其值設置給view,它用於標示是否讓view使用PathVariables,可	   // 以在viewResolver中配置。PathVariables就是處理器中@Pathvariables註釋的參數
    Boolean exposePathVariables = getExposePathVariables();
    if (exposePathVariables != null) {
        view.setExposePathVariables(exposePathVariables);
    }
    //如果exposecontextBeansAsAttributes不爲null,將其值設置給view,
    //它用於標示是否可以讓view使用容器中註冊的bean,此參數可以在ViewResolver中配置
    Boolean exposeContextBeansAsAttributes = getExposeContextBeansAsAttributes();
    if (exposeContextBeansAsAttributes != null) {
        view.setExposeContextBeansAsAttributes(exposeContextBeansAsAttributes);
    }
    //如果exposedcontextBeanNames不爲nul1,將其值設置給view,
    //它用於配置view可以使用容器中的哪些bean,可以在ViewResolver中配置
    String[] exposedContextBeanNames = getExposedContextBeanNames();
    if (exposedContextBeanNames != null) {
        view.setExposedContextBeanNames(exposedContextBeanNames);
    }

    return view;
}

View的創建過程也非常簡單,首先根據使用BeanUtils根據getViewClass方法的返回值創建出view,然後將viewName加上前綴、後綴設置爲url,前綴和後綴可以在配置ViewResolver時進行設置,這樣View就創建完了,接下來根據配置給View設置一些參數,具體內容已經註釋到代碼上了。這裏的getViewClass返回其中的viewClass屬性,代表View的視圖類型,可以在子類通過setViewClass方法進行設置。另外還有一個requiredViewClass方法,它用於在設置視圖時判斷所設置的類型是否支持,在UrIBasedViewResolver中默認返回AbstractUrlBasedView類型, requiredViewClass使用在設置視圖的setViewClass方法中,代碼如下:

public void setViewClass(@Nullable Class<?> viewClass) {
    if (viewClass != null && !requiredViewClass().isAssignableFrom(viewClass)) {
        ...拋出異常
    }
    this.viewClass = viewClass;
}

​ UrIBasedViewResolver的代碼就分析完了,通過前面的分析可知,只需要給它設置Abstract-UrIlBasedView類型的viewClass就可以直接使用了,我們可以直接註冊配置了viewClass的UrlBasedViewResolver來使用,不過最好還是使用相應的子類。

​ UrlBasedViewResolver的子類主要做三件事: 0通過重寫requiredViewClass方法修改了必須符合的視圖類型的值;②使用setViewClass方法設置了所用的視圖類型;3給創建出來的視圖設置一些屬性。下面來看一下使用得非常多的InternalResourceViewResolver和FreeMarkerViewResolver,前者用於解析jsp視圖後者用於解析FreeMarker視圖,其他實現類也都差不多。

​ InternalResourceViewResolver直接繼承自UrlBasedViewResolver,它在構造方法中設置了viewClass,在buildView中對父類創建的View設置了一些屬性, requiredViewClass方法返回InternalResourceView類型,代碼如下:

public InternalResourceViewResolver() {
    Class<?> viewClass = requiredViewClass();
    if (InternalResourceView.class == viewClass && jstlPresent) {
        viewClass = JstlView.class;
    }
    setViewClass(viewClass);
}
protected Class<?> requiredViewClass() {
    return InternalResourceView.class;
}
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
    InternalResourceView view = (InternalResourceView) super.buildView(viewName);
    if (this.alwaysInclude != null) {
        view.setAlwaysInclude(this.alwaysInclude);
    }
    view.setPreventDispatchLoop(true);
    return view;
}

​ buildView方法中給創建出來的View設置的alwaysInclude用於標示是否在可以使用forward的情況下也強制使用include,默認爲false,可以在註冊解析器時配置。setPreventDispatchLoop(true)用於阻止循環調用,也就是請求處理完成後又轉發回了原來使用的處理器的情況。

​ FreeMarkerViewResolver繼承自UrlBasedViewResolver的子類AbstractTemplate ViewResolver, AbstractTemplateViewResolver是所用模板類型ViewResolver的父類,它裏面主要對創建的View設置了一些屬性,並將requiredViewClass的返回值設置爲AbstractTemplateView類型。代碼如下:

protected Class<?> requiredViewClass() {
    return AbstractTemplateView.class;
}
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
    AbstractTemplateView view = (AbstractTemplateView) super.buildView(viewName);
    view.setExposeRequestAttributes(this.exposeRequestAttributes);
    view.setAllowRequestOverride(this.allowRequestOverride);
    view.setExposeSessionAttributes(this.exposeSessionAttributes);
    view.setAllowSessionOverride(this.allowSessionOverride);
    view.setExposeSpringMacroHelpers(this.exposeSpringMacroHelpers);
    return view;
}

  • exposeRequestAttributes :是否將所有RequestAttributes暴露給view使用,默認爲false
  • allowRequestOverride :當RequestAttributes中存在Model中同名的參數時,是否允許使用RequestAttributes中的值將Model中的值覆蓋,默認爲false
  • exposeSessionAttributes :是否將所有SessionAttributes暴露給view使用,默認爲false
  • allowSessionOverride :當SessionAttributes中存在Model中同名的參數時,是否允許使用RequestAttributes中的值將Model中的值覆蓋,默認爲false.
  • exposeSpringMacroHelpers :是否將RequestContext暴露給view爲spring的宏使用,默認爲true.

​ FreeMarkerViewResolver的代碼就非常簡單了,只是覆蓋requiredViewClass方法返回FreeMarkerView類型,並在構造方法中調用setViewClass方法設置了viewClass.

3.使用原理解析

我們通常使用的都是InternalResourceViewResolver

我們註冊視圖解析器有幾種方式:

  • 第一種通過直接註冊它,使用Bean

    @Bean
    public ViewResolver viewResolver() {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/pages/");
        resolver.setSuffix(".jsp");
        return resolver;
    }
    
    
  • 第二種通過父接口的支持

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/pages/");
        resolver.setSuffix(".jsp");
        registry.viewResolver(resolver);
    }
    
    

什麼時候執行的呢?

他是在DispatcherServlet中的

doDispatcher(HttpServletRequest request, HttpServletResponse response)方法裏面執行的

// 就是這個方法
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

render(mv, request, response);

protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
    // Determine locale for request and apply it to the response.
    Locale locale =
                (this.localeResolver != null ? 
                 this.localeResolver.resolveLocale(request) : 
                 request.getLocale());
    response.setLocale(locale);

    View view;
    // 得到視圖名
    String viewName = mv.getViewName();
    if (viewName != null) {
        // We need to resolve the view name.
        // 這裏進去看看就是解析試圖
        view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
        if (view == null) {
            ...拋出異常
        }
    }
    else {
        // No need to lookup: the ModelAndView object contains the actual View object.
        view = mv.getView();
        if (view == null) {
            ...拋出異常
        }
    }

    // Delegate to the View object for rendering.
    ...打印日誌
    try {
        if (mv.getStatus() != null) {
            response.setStatus(mv.getStatus().value());
        }
        view.render(mv.getModelInternal(), request, response);
    }
    ...異常處理和打印日誌
}

protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
                               Locale locale, HttpServletRequest request) throws Exception {

    if (this.viewResolvers != null) {
        for (ViewResolver viewResolver : this.viewResolvers) {
            // 這個時候不同的註冊方式就有了區別了,但是原理都是一樣的,
            // 如果我們通過自己直接註冊的@Bean類型的InternalResourceViewResolver
            // 那麼就會直接進行執行父類的resolveViewName方法
            // 但是當我們通過父類支持提供的註冊方法就會先遍歷集合然後執行
            View view = viewResolver.resolveViewName(viewName, locale);
            if (view != null) {
                return view;
            }
        }
    }
    return null;
}

2.RequestToViewNameTranslator

​ RequestToViewNameTranslator可以在處理器返回的view爲空時使用它根據request獲取viewName, Spring MVC提供的實現類只有一個DefaultRequestToViewNameTranslator,這個類也非常簡單,只是因爲有一些getter/setter方法,所以看起來代碼比較多,實際執行解析的只有兩個,代碼如下:

public String getViewName(HttpServletRequest request) {
    String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
    return (this.prefix + transformPath(lookupPath) + this.suffix);
}

protected String transformPath(String lookupPath) {
    String path = lookupPath;
    if (this.stripLeadingSlash && path.startsWith(SLASH)) {
        path = path.substring(1);
    }
    if (this.stripTrailingSlash && path.endsWith(SLASH)) {
        path = path.substring(0, path.length() - 1);
    }
    if (this.stripExtension) {
        path = StringUtils.stripFilenameExtension(path);
    }
    if (!SLASH.equals(this.separator)) {
        path = StringUtils.replace(path, SLASH, this.separator);
    }
    return path;
}

getViewName是接口定義的方法,實際解析時就調用它。在getViewName中首先從request獲得lookupPath,然後使用transformPath方法對其進行處理後加上前綴後綴返回。transformPath方法的作用簡單來說就是根據配置對lookupPath “指頭去尾換分隔符",它是根據其中的四個屬性的設置來處理的,下面分別解釋一下這四個屬性,其中用到的Slash是一個靜態常量,表示“1"。

  • stripLeadingsSlash:如果最前面的字符爲Slash是否將其去掉。
  • stripTrailingSlash:如果最後一個字符爲Slash是否將其去掉。
  • stripExtension:是否需要去掉擴展名。
  • separator:如果其值與Slash不同則用於替換原來的分隔符Slash

可以配置。可以配置的參數除了這6個外還有4個: urlDecode, removeSemicolonContent.alwaysUseFullPath和urlPathHelper,前三個參數都是用在urlPathHelper中的, urlDecode用於設置url是否需要編解碼,一般默認就行; removeSemicolonContent在前面已經說過了,用於設置是否刪除url中與分號相關的內容; alwaysUseFullPath用於設置是否總使用完整路徑;urlPathHelper是用於處理url的工具,一般使用spring默認提供的就可以了。

3.HandlerExceptionResolver

HandlerExceptionResolver用於解析請求處理過程中所產生的異常,繼承結構如圖所示。

在這裏插入圖片描述

其中HandlerExceptionResolverComposite作爲容器使用,可以封裝別的Resolver,前面已經多次介紹過,這裏就不再敘述了。

HandlerExceptionResolver的主要實現都繼承自抽象類AbstractHandlerExceptionResolver,它有四個子類:

  • AbstractHandlerMethodExceptionResolver和其子類ExceptionHandlerExceptionResolver一起完成使用

    @ExceptionHnadler註釋的方法進行異常解析的功能

  • DefaultHandlerExceptionResolver:按不同類型分別對異常進行解析。

  • ResponseStatusExceptionResolver:解析有@ResponseStatus註釋類型的異常。

  • SimpleMappingExceptionResolver:通過配置的異常類和view的對應關係來解析異常。

異常解析過程主要包含兩部分內容:給ModelAndView設置相應內容、設置response的相關屬性。當然還可能有一些輔助功能,如記錄日誌等,在自定義的ExceptionHander裏還可以做更多的事情。

1.AbstractHandlerExceptionResolver

public ModelAndView resolveException(
    HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {

    if (shouldApplyTo(request, handler)) {
        prepareResponse(ex, response);
        // 模板方法等待子類實現
        ModelAndView result = doResolveException(request, response, handler, ex);
        if (result != null) {
            // Print debug message when warn logger is not enabled.
            ...打印日誌
            // Explicitly configured warn logger in logException method.
            logException(ex, request);
        }
        return result;
    }
    else {
        return null;
    }
}

​ 首先使用shouldApplyTo方法判斷當前ExceptionResolver是否可以解析所傳入處理器所拋出的異常(可以指定只能處理指定的處理器拋出的異常),如果不可以則返回null,交給下一個ExceptionResolver解析,如果可以則調用logException方法打印日誌,接着調用prepareResponse設置response,最後調用doResolveException實際解析異常,doResolveException是個模板方法,留給子類實現。

protected boolean shouldApplyTo(HttpServletRequest request, @Nullable Object handler) {
    if (handler != null) {
        if (this.mappedHandlers != null && this.mappedHandlers.contains(handler)) {
            return true;
        }
        if (this.mappedHandlerClasses != null) {
            for (Class<?> handlerClass : this.mappedHandlerClasses) {
                if (handlerClass.isInstance(handler)) {
                    return true;
                }
            }
        }
    }
    // Else only apply if there are no explicit handler mappings.
    return (this.mappedHandlers == null && this.mappedHandlerClasses == null);
}

​ 這裏使用了兩個屬性: mappedHandlers和mappedHandlerClasses這兩個屬性可以在定義HandlerExceptionResolver的時候進行配置,用於指定可以解析處理器拋出的哪些異常,也就是如果設置了這兩個值中的一個,那麼這個ExceptionResolver就只能解析所設置的處理器拋·出的異常。mappedHandlers用於配置處理器的集合, mappedHandlerClasses用於配置處理器類型的集合。檢查方法非常簡單,在此就不細說了,如果兩個屬性都沒配置則將處理所有異常。

logException是默認記錄日誌的方法,代碼如下:

protected void logException(Exception ex, HttpServletRequest request) {
    if (this.warnLogger != null && this.warnLogger.isWarnEnabled()) {
        this.warnLogger.warn(buildLogMessage(ex, request));
    }
}

logException方法首先調用buildLogMessage創建了日誌消息,然後使用warnLogger將其記錄下來。

prepareResponse方法根據preventResponseCaching標示判斷是否給response設置禁用緩存的屬性, preventResponseCaching默認爲false,代碼如下:

protected void prepareResponse(Exception ex, HttpServletResponse response) {
    if (this.preventResponseCaching) {
        preventCaching(response);
    }
}
protected void preventCaching(HttpServletResponse response) {
    response.addHeader(HEADER_CACHE_CONTROL, "no-store");
}

最後的doResolveException方法是模板方法,子類使用它具體完成異常的解析工作

2.ExceptionHandlerExceptionResolver

ExceptionHandlerExceptionResolver繼承自AbstractHandlerMethodExceptionResolver,後者繼承自AbstractHandlerExceptionResolver, AbstractHandlerMethodExceptionResolver重寫了shouldApplyTo方法,並在處理請求的doResolveException方法中將實際處理請求的過程交給了模板方法doResolveHandlerMethodException。代碼如下:

protected boolean shouldApplyTo(HttpServletRequest request, @Nullable Object handler) {
    if (handler == null) {
        return super.shouldApplyTo(request, null);
    }
    else if (handler instanceof HandlerMethod) {
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        handler = handlerMethod.getBean();
        return super.shouldApplyTo(request, handler);
    }
    else {
        return false;
    }
}

@Override
@Nullable
protected final ModelAndView doResolveException(HttpServletRequest request, 
    											HttpServletResponse response, 
    											@Nullable Object handler, 
    											Exception ex) {

    return doResolveHandlerMethodException(request, 
                                           response, 
                                           (HandlerMethod) handler, 
                                           ex);
}

​ AbstractHandlerMethodExceptionResolver的作用其實相當於一個適配器。一般的處理器是類的形式,但HandlerMethod其實是將方法作爲處理器來使用的,所以需要進行適配。首先在shouldApplyTo中判斷如果處理器是HandlerMethod類型則將處理器設置爲其所在的類,然後再交給父類判斷,如果爲空則直接交給父類判斷,如果既不爲空也不是HandlerMethod類型則返回false不處理。

​ doResolveException將處理傳遞給doResolveHandlerMethodException方法具體處理,這樣做主要是爲了層次更加合理,而且這樣設計後如果有多個子類還可以在doResolveException中統一做一些事情。

下面來看ExceptionHandlerExceptionResolver,它其實就是一個簡化版的RequestMappingHandlerAdapter,它的執行也是使用的ServletinvocableHandlerMethod,首先根據handlerMethod和exception將其創建出來(大致過程是在處理器類裏找出所有註釋了@Exception, Handler的方法,然後再根據其配置中的異常和需要解析的異常進行匹配),然後設置了argumentResolvers和return ValueHandlers,接着調用其invokeAndHandle方法執行處理,最後將處理結果封裝成ModelAndView返回。如果RequestMappingHandlerAdapter理解了,再來看它就會覺得非常簡單。代碼如下:

protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
                                                       HttpServletResponse response,
                                                       @Nullable 
                                                       HandlerMethod handlerMethod, 
                                                       Exception exception) {
	// 找到處理異常的方法
    ServletInvocableHandlerMethod exceptionHandlerMethod =
        							getExceptionHandlerMethod(handlerMethod, exception);
    if (exceptionHandlerMethod == null) {
        return null;
    }
	// 設置arqumentResolvers和returnValueHandlers
    if (this.argumentResolvers != null) {
        exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
    }
    if (this.returnValueHandlers != null) {
	exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
    }

    ServletWebRequest webRequest = new ServletWebRequest(request, response);
    ModelAndViewContainer mavContainer = new ModelAndViewContainer();

    try {
        
        Throwable cause = exception.getCause();
        if (cause != null) {
            // Expose cause as provided argument as well
            // 執行ExceptionHandler方法解析異常
            exceptionHandlerMethod.invokeAndHandle(webRequest, 
                                                   mavContainer, 
                                                   exception, 
                                                   cause, 
                                                   handlerMethod);
        }
        else {
            // Otherwise, just the given exception as-is
            exceptionHandlerMethod.invokeAndHandle(webRequest, 
                                                   mavContainer, 
                                                   exception, 
                                                   handlerMethod);
        }
    }
    catch (Throwable invocationEx) {
        // Any other than the original exception is unintended here,
        // probably an accident (e.g. failed assertion or the like).
        if (invocationEx != exception && logger.isWarnEnabled()) {
            。。。打印日誌
        }
        // Continue with default processing of the original exception...
        return null;
    }

    if (mavContainer.isRequestHandled()) {
        return new ModelAndView();
    }
    else {
        ModelMap model = mavContainer.getModel();
        HttpStatus status = mavContainer.getStatus();
        ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, status);
        mav.setViewName(mavContainer.getViewName());
        if (!mavContainer.isViewReference()) {
            mav.setView((View) mavContainer.getView());
        }
        if (model instanceof RedirectAttributes) {
            Map<String, ?> flashAttributes = 
                					((RedirectAttributes)model).getFlashAttributes();
            RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
        }
        return mav;
    }
}

這裏只是返回了ModelAndView,並沒有對response進行設置,如果需要可以自己在異常·處理器中設置。

3.DefaultHandlerExceptionResolver

DefaultHandlerExceptionResolver的解析過程是根據異常類型的不同,使用不同的方法進行處理, doResolveException代碼如下:

protected ModelAndView doResolveException(	HttpServletRequest request, 
                                            HttpServletResponse response, 
                                            @Nullable Object handler, 
                                            Exception ex) {

    try {
        if (ex instanceof HttpRequestMethodNotSupportedException) {
            return handleHttpRequestMethodNotSupported(
                (HttpRequestMethodNotSupportedException) ex, request, response, handler);
        }
        else if (ex instanceof HttpMediaTypeNotSupportedException) {
            return handleHttpMediaTypeNotSupported(
                (HttpMediaTypeNotSupportedException) ex, request, response, handler);
        }
        else if (ex instanceof HttpMediaTypeNotAcceptableException) {
            return handleHttpMediaTypeNotAcceptable(
                (HttpMediaTypeNotAcceptableException) ex, request, response, handler);
        }
        else if (ex instanceof MissingPathVariableException) {
            return handleMissingPathVariable(
                (MissingPathVariableException) ex, request, response, handler);
        }
        else if (ex instanceof MissingServletRequestParameterException) {
            return handleMissingServletRequestParameter(
                (MissingServletRequestParameterException) ex, 
                										request, 
                										response, 
                										handler);
        }
        else if (ex instanceof ServletRequestBindingException) {
            return handleServletRequestBindingException(
                (ServletRequestBindingException) ex, request, response, handler);
        }
        else if (ex instanceof ConversionNotSupportedException) {
            return handleConversionNotSupported(
                (ConversionNotSupportedException) ex, request, response, handler);
        }
        else if (ex instanceof TypeMismatchException) {
            return handleTypeMismatch(
                (TypeMismatchException) ex, request, response, handler);
        }
        else if (ex instanceof HttpMessageNotReadableException) {
            return handleHttpMessageNotReadable(
                (HttpMessageNotReadableException) ex, request, response, handler);
        }
        else if (ex instanceof HttpMessageNotWritableException) {
            return handleHttpMessageNotWritable(
                (HttpMessageNotWritableException) ex, request, response, handler);
        }
        else if (ex instanceof MethodArgumentNotValidException) {
            return handleMethodArgumentNotValidException(
                (MethodArgumentNotValidException) ex, request, response, handler);
        }
        else if (ex instanceof MissingServletRequestPartException) {
            return handleMissingServletRequestPartException(
                (MissingServletRequestPartException) ex, request, response, handler);
        }
        else if (ex instanceof BindException) {
            return handleBindException((BindException) ex, request, response, handler);
        }
        else if (ex instanceof NoHandlerFoundException) {
            return handleNoHandlerFoundException(
                (NoHandlerFoundException) ex, request, response, handler);
        }
        else if (ex instanceof AsyncRequestTimeoutException) {
            return handleAsyncRequestTimeoutException(
                (AsyncRequestTimeoutException) ex, request, response, handler);
        }
    }
    ...捕獲異常並打印日誌
    return null;
}

具體的解析方法也非常簡單,主要是設置response的相關屬性,下面介紹前兩個異常的處理方法,也就是沒找到處理器執行方法和request的Method類型不支持的異常處理,代碼如下:

protected ModelAndView handleNoHandlerFoundException(NoHandlerFoundException ex,
                                                     HttpServletRequest request,
                                                     HttpServletResponse response,
                                                     @Nullable 
                                                     Object handler) throws IOException {

    pageNotFoundLogger.warn(ex.getMessage());
    response.sendError(HttpServletResponse.SC_NOT_FOUND);
    return new ModelAndView();
}

3.ResponseStatusExceptionResolver

ResponseStatusExceptionResolver用來解析註釋了@ResponseStatus的異常(如自定義的註釋了@ResponseStatus的異常),代碼如下:

protected ModelAndView doResolveException(HttpServletRequest request, 
                                          HttpServletResponse response, 
                                          @Nullable Object handler, 
                                          Exception ex) {

    try {
        if (ex instanceof ResponseStatusException) {
            return resolveResponseStatusException((ResponseStatusException) ex, 
                                                  request, 
                                                  response, 
                                                  handler);
        }

        ResponseStatus status = AnnotatedElementUtils.findMergedAnnotation(ex.getClass(),
                                                                   ResponseStatus.class);
        if (status != null) {
            return resolveResponseStatus(status, request, response, handler, ex);
        }

        if (ex.getCause() instanceof Exception) {
            return doResolveException(request, 
                                      response, 
                                      handler, 
                                      (Exception) ex.getCause());
        }
    }
    ...異常處理
    return null;
}

doResolveException方法中首先使用AnnotationUtils找到Responsestatus註釋,然後調用resolveResponsestatus方法進行解析,後者使用註釋裏的value和reason作爲參數調用了response的sendError方法。

4.SimpleMappingExceptionResolver

SimpleMappingExceptionResolver需要提前配置異常類和view的對應關係然後才能使用,」doResolveException代碼如下:

protected ModelAndView doResolveException(	HttpServletRequest request, 
                                            HttpServletResponse response, 
                                            @Nullable Object handler, 
                                            Exception ex) {

    // Expose ModelAndView for chosen error view.
    String viewName = determineViewName(ex, request);
    if (viewName != null) {
        // Apply HTTP status code for error views, if specified.
        // Only apply it if we're processing a top-level request.
        Integer statusCode = determineStatusCode(request, viewName);
        if (statusCode != null) {
            applyStatusCodeIfPossible(request, response, statusCode);
        }
        return getModelAndView(viewName, ex, request);
    }
    else {
        return null;
    }
}

這裏首先調用determineViewName方法根據異常找到顯示異常的邏輯視圖,然後調用determineStatusCode方法判斷邏輯視圖是否有對應的statusCode,如果有則調用applyStatusCodelfPossible方法設置到response,最後調用getModelAndView將異常和解析出的viewName封裝成ModelAndView並返回。

protected String determineViewName(Exception ex, HttpServletRequest request) {
    String viewName = null;
    // 如果異常在設置的excludedExceptions中所包含則返回null
    if (this.excludedExceptions != null) {
        for (Class<?> excludedEx : this.excludedExceptions) {
            if (excludedEx.equals(ex.getClass())) {
                return null;
            }
        }
    }
    // Check for specific exception mappings.
    // 調用findMatchingViewame方法實際查找
    if (this.exceptionMappings != null) {
        viewName = findMatchingViewName(this.exceptionMappings, ex);
    }
    // Return default error view else, if defined.
    // 如果沒找到viewName並且配置了defaultErrorView,則使用defaultErrorView
    if (viewName == null && this.defaultErrorView != null) {
        ...打印日誌
        viewName = this.defaultErrorView;
    }
    return viewName;
}

這裏首先檢查異常是不是配置在excludedExceptions中( excludedExceptions用於配置不處理的異常),如果是則返回null,否則調用findMatchingViewName實際查找viewName,如果沒找到而且配置了defaultErrorView,則使用defaultErrorView, findMatchingViewName從傳入的參數就可以看出來它是根據配置的exceptionMappings參數匹配當前異常的,不過並不是直接完全匹配的,而是隻要配置異常的字符在當前處理的異常或其父類中存在就可以了,如配置"BindingException"可以匹配"xxx.USserBindingException" “xxxDeptBindingException"等,而"java.lang.Exception"可以匹配所有它的子類,即所有"CheckedExceptions”,其代碼如下:

protected String findMatchingViewName(Properties exceptionMappings, Exception ex) {
    String viewName = null;
    String dominantMapping = null;
    int deepest = Integer.MAX_VALUE;
    for (Enumeration<?> names = exceptionMappings.propertyNames();
         													names.hasMoreElements();) {
        String exceptionMapping = (String) names.nextElement();
        int depth = getDepth(exceptionMapping, ex);
        if (depth >= 0 && 
            (depth < deepest || 
             (depth == deepest &&
			 	dominantMapping != null && 
              exceptionMapping.length() > dominantMapping.length()))) {
            deepest = depth;
            dominantMapping = exceptionMapping;
            viewName = exceptionMappings.getProperty(exceptionMapping);
        }
    }
    ...打印日誌
    return viewName;
}

大致過程就是遍歷配置文件,然後調用getDepth查找,如果返回值大於等於0則說明可以匹配,而且如果有多個匹配項則選擇最優的,選擇方法是判斷兩項內容:①匹配的深度;②匹配的配置項文本的長度。深度越淺越好,配置的文本越長越好。深度是指如果匹配的是異常的父類而不是異常本身,那麼深度就是異常本身到被匹配的父類之間的繼承層數。getDepth方法的代碼如下:

private int getDepth(String exceptionMapping, Class<?> exceptionClass, int depth) {
    if (exceptionClass.getName().contains(exceptionMapping)) {
        // Found it!
        return depth;
    }
    // If we've gone as far as we can go and haven't found it...
    if (exceptionClass == Throwable.class) {
        return -1;
    }
    return getDepth(exceptionMapping, exceptionClass.getSuperclass(), depth + 1);
}

4.MultipartResolver

MultipartResolver用於處理上傳請求,有兩個實現類:

  • StandardServletMultipartResolver:使用了Servlet3.0標準的上傳方式
  • CommonsMultipartResolver。使用了Apache的commons-fileupload

1.StandardServletMultipartResolver

StandardServletMultipartResolver使用了Servlet3.0標準的上傳方式,在Servle3.0中上傳文件非常簡單,只需要調用request的getParts方法就可以獲取所有上傳的文件。如果想單獨獲取某個文件可以使用request.getPart (fileName),獲取到Part後直接調用它到write(saveFileName)方法就可以將文件保存爲以saveFileName爲文件名的文件,也可以調用getInputStream獲取InputStream。如果想要使用這種上傳方式還需要在配置上傳文件的Servlet 時添h multipart-config屬性,例如,我們使用的Spring MVC中所有的請求都在DispatcherServlet這個 Servlet中,所以可以給它配置上multipart-confg,如下所示:

  1. xml方式

    <servlet> 
         <servlet-name >myWeb</servlet-name> 
          <servlet-class>
    			org.springframework.web.servlet.DispatcherServlet
          </servlet-class> 
          <init-param> 
           		<param-name>contextConfigLocation</param-name> 
           		<param-value>classpath:springmvc/servlet.xml</param-value> 
          </ init-param > 
      	  <load-on-startup>1</load-on-startup> 
          <multipart-config> 
           <!-- <location>/</location> -->
           <!--單個文件最大大小:5MB-->
           <max-file-size>5242880</ max-file-size >  
           <!--所有文件最大大小:20MB-->
           <max-request-size>20971520</max-request-size>  
           <!-- 超過這個大小直接存硬盤,而不是內存 -->
           < file-size-threshold >0</ file-size-threshold >  
          </ multipart-config > 
    </ servlet > 
    
    
  2. 註解方式:

    public class MyWebApplicationInitializer implements WebApplicationInitializer {
    	
        // 將存儲文件的臨時位置
    	private static final String LOCATION = "C:/temp/"; 
    	 // 5MB : Max file size.
    	private static final long MAX_FILE_SIZE = 5242880;
    	// 20MB : Total request size containing Multi part.
    	private static final long MAX_REQUEST_SIZE = 20971520; 
    	// Size threshold after which files will be written to disk
        // 文件寫入磁盤後的大小閾值
    	private static final int FILE_SIZE_THRESHOLD = 0; 
    
    	@Override
    	public void onStartup(ServletContext servletContext) {
    		//先註冊RootConfig.class
    		AnnotationConfigWebApplicationContext rootAppContext = new 																AnnotationConfigWebApplicationContext();
    		//我們也可以直接讓spring接在mvc的配置文件,但是這樣的話邏輯比較混亂了
    		rootAppContext.register(RootConfig.class);
    		//註冊spring的監聽器
    		ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
    		//listener.setContextInitializers(null);
    		servletContext.addListener(listener);
    		// 開始註冊springmvc
    		// 其實這樣寫我是爲了更加明確什麼時候執行什麼操作,
            // 其實我們都用之前創建的一個spring容器就可以了,
    		// spring就會進行所有的掃描了,
    		// 其實對於spring來說,你先要配置springmvc主要就是配置spring的監聽器,
            // 配置完監聽器之後,所有的功能都好事了
    		AnnotationConfigWebApplicationContext ac = new 																			AnnotationConfigWebApplicationContext();
    		ac.register(AppConfig.class);
    		DispatcherServlet servlet = new DispatcherServlet(ac);
    		//servlet.setContextInitializers(null);
    		//添加根servlet
    		ServletRegistration.Dynamic registration = servletContext.addServlet("app",
                                                                                 servlet);
    		registration.setLoadOnStartup(1);
    		registration.addMapping("/");
    		//配置文件上傳
    		// 這種方式是使用servlet3.0特性的文件上傳
    		// 當你想使用這種方式的時候你就必須要配置multipartResolver
    		registration.setMultipartConfig(getMultipartConfigElement());
    
    
    		// 添加pso編碼過濾器
    		// 這個裏面跟蹤源碼會發現其實 兩個boolen值就是爲request和response設置編碼的
    		CharacterEncodingFilter filter = new CharacterEncodingFilter("UTF-8", 
                                                                         true, 
                                                                         true);
    		FilterRegistration.Dynamic encodingFilter = servletContext.addFilter(
                    													"encodingFilter", 
                                                                                 filter);
    		encodingFilter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST),
                                                    true, "/*");
    
    	}
    
    	// 配置文件上傳
    	private MultipartConfigElement getMultipartConfigElement() {
    		MultipartConfigElement multipartConfigElement = new MultipartConfigElement(
                																LOCATION, 
    																		MAX_FILE_SIZE, 
                														MAX_REQUEST_SIZE, 
                                                                     FILE_SIZE_THRESHOLD);
    		return multipartConfigElement;
    	}
    }
    
    
  3. springmvc配置

    當我們需要使用servlet3.0的文件上傳的時候我們就需要使用當前的文件上傳處理器了

    /*
    這個bean的id必須是multipartResolver,因爲springmvc獲得文件長傳處理器的時候通過了beanName進行了獲取
    */
    @Bean
    public MultipartResolver multipartResolver(){
        StandardServletMultipartResolver multipartResolver = new
            										StandardServletMultipartResolver();
        return multipartResolver;
    }
    
    

    下面看一下StandardServletMultipartResolver,它的代碼非常簡單。

public class StandardServletMultipartResolver implements MultipartResolver {

	private boolean resolveLazily = false;
    
	public void setResolveLazily(boolean resolveLazily) {
		this.resolveLazily = resolveLazily;
	}

	// 這個方法很簡單,就是判斷是不是post請求
	@Override
	public boolean isMultipart(HttpServletRequest request) {
		// Same check as in Commons FileUpload...
		if (!"post".equalsIgnoreCase(request.getMethod())) {
			return false;
		}
		return StringUtils.startsWithIgnoreCase(request.getContentType(), "multipart/");
	}

	@Override
	public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) 
        													throws MultipartException {
		return new StandardMultipartHttpServletRequest(request, this.resolveLazily);
	}

	@Override
	public void cleanupMultipart(MultipartHttpServletRequest request) {
		if (!(request instanceof AbstractMultipartHttpServletRequest) ||
				((AbstractMultipartHttpServletRequest) request).isResolved()) {
			// To be on the safe side: explicitly delete the parts,
			// but only actual file parts (for Resin compatibility)
			try {
				for (Part part : request.getParts()) {
					if (request.getFile(part.getName()) != null) {
						part.delete();
					}
				}
			}
            ...捕捉異常
		}
	}

}

如何判斷是不是上傳請求呢?在isMultipart方法中首先判斷是不是post請求,如果是則再檢查contentType是不是以"multipart/"開頭,如果也是則認爲是上傳請求。

resolveMultipart方法直接將當前請求封裝成StandardMultipartHttpServletRequest並返回。cleanupMultipart方法刪除了緩存。下面來看一下StandardMultipartHttpServletRequest

private void parseRequest(HttpServletRequest request) {
    try {
        Collection<Part> parts = request.getParts();
        this.multipartParameterNames = new LinkedHashSet<>(parts.size());
        MultiValueMap<String, MultipartFile> files = new LinkedMultiValueMap<>(parts.size());
        for (Part part : parts) {
            String headerValue = part.getHeader(HttpHeaders.CONTENT_DISPOSITION);
            ContentDisposition disposition = ContentDisposition.parse(headerValue);
            String filename = disposition.getFilename();
            if (filename != null) {
                if (filename.startsWith("=?") && filename.endsWith("?=")) {
                    filename = MimeDelegate.decode(filename);
                }
                files.add(part.getName(), new StandardMultipartFile(part, filename));
            }
            else {
                this.multipartParameterNames.add(part.getName());
            }
        }
        setMultipartFiles(files);
    }
    catch (Throwable ex) {
        handleParseFailure(ex);
    }
}

可以看到,它的大概思路就是通過request的getParts方法獲取所有Part,然後使用它們創建出File並保存到對應的屬性,以便在處理器中可以直接調用。

2.CommonsMultipartResolver

CommonsMultipartResolver使用了 commons-fleupload來完成具體的上傳操作。

在CommonsMultiparResolver中,判斷是不是上傳請求的isMultipart,這將交給commonsfileupload的 ServletFileUpload類完成,代碼如下:

public boolean isMultipart(HttpServletRequest request) {
    return ServletFileUpload.isMultipartContent(request);
}
public static final boolean isMultipartContent(
    HttpServletRequest request) {
    if (!POST_METHOD.equalsIgnoreCase(request.getMethod())) {
        return false;
    }
    return FileUploadBase.isMultipartContent(new ServletRequestContext(request));
}

CommonsMultipartResolver中實際處理request的方法是resolveMultipart,代碼如下:

public MultipartHttpServletRequest resolveMultipart(final HttpServletRequest request) 																throws MultipartException {
    Assert.notNull(request, "Request must not be null");
    if (this.resolveLazily) {
        return new DefaultMultipartHttpServletRequest(request) {
            @Override
            protected void initializeMultipart() {
                MultipartParsingResult parsingResult = parseRequest(request);
                setMultipartFiles(parsingResult.getMultipartFiles());
                setMultipartParameters(parsingResult.getMultipartParameters());
    			setMultipartParameterContentTypes(
     											parsingResult.
                                   					getMultipartParameterContentTypes());
            }
        };
    }
    else {
        MultipartParsingResult parsingResult = parseRequest(request);
        return new DefaultMultipartHttpServletRequest(request,
                                                      parsingResult.getMultipartFiles(),
												parsingResult.getMultipartParameters(), 									parsingResult.getMultipartParameterContentTypes());
    }
}

​ 它根據不同的resolveLazily配置使用了兩種不同的方法,不過都是將Request轉換爲DefaultMultipartHtpSservletRequest類型,而且都使用parseRequest方法進行處理。

​ 如果resolveLazily爲true,則會將parsingResult方法放在DefaultMultipartHttpSservlet-Request,類重寫的initializeMultipart方法中, initializeMultipart方法只有在調用相應的get方法(getMultipartFiles, getMultipartParameters或getMultipartParameterContentTypes)時纔會被調用。

​ 如果resolvelazily爲false,則將會先調用parseRequest方法來處理request,然後將處理的結果傳入DefaultMultipartHttpServletRequest.

​ parseRequest方法是使用commons-fileupload中的FileUpload組件解析出fileltems,然後再調用parseFileltems方法將fileltems分爲參數和文件兩類,並設置到三個Map中,三個Map分別用於保存參數、參數的ContentType和上傳的文件,代碼如下:

protected MultipartParsingResult parseRequest(HttpServletRequest request) throws MultipartException {
    String encoding = determineEncoding(request);
    FileUpload fileUpload = prepareFileUpload(encoding);
    try {
        List<FileItem> fileItems = ((ServletFileUpload) fileUpload).parseRequest(request);
        return parseFileItems(fileItems, encoding);
    }
    catch (FileUploadBase.SizeLimitExceededException ex) {
        throw new MaxUploadSizeExceededException(fileUpload.getSizeMax(), ex);
    }
    catch (FileUploadBase.FileSizeLimitExceededException ex) {
        throw new MaxUploadSizeExceededException(fileUpload.getFileSizeMax(), ex);
    }
    catch (FileUploadException ex) {
        throw new MultipartException("Failed to parse multipart servlet request", ex);
    }
}
protected MultipartParsingResult parseFileItems(List<FileItem> fileItems,
                                                String encoding) {
    MultiValueMap<String, MultipartFile> multipartFiles = new LinkedMultiValueMap<>();
    Map<String, String[]> multipartParameters = new HashMap<>();
    Map<String, String> multipartParameterContentTypes = new HashMap<>();

    // Extract multipart files and multipart parameters.
    for (FileItem fileItem : fileItems) {
        if (fileItem.isFormField()) {
            String value;
            String partEncoding = determineEncoding(fileItem.getContentType(), encoding);
            try {
                value = fileItem.getString(partEncoding);
            }
            catch (UnsupportedEncodingException ex) {
                ...打印日誌
                value = fileItem.getString();
            }
            String[] curParam = multipartParameters.get(fileItem.getFieldName());
            if (curParam == null) {
                // simple form field
                multipartParameters.put(fileItem.getFieldName(), new String[] {value});
            }
            else {
                // array of simple form fields
                String[] newParam = StringUtils.addStringToArray(curParam, value);
                multipartParameters.put(fileItem.getFieldName(), newParam);
            }
            multipartParameterContentTypes.put(fileItem.getFieldName(),
                                               fileItem.getContentType());
        }
        else {
            // multipart file field
            CommonsMultipartFile file = createMultipartFile(fileItem);
            multipartFiles.add(file.getName(), file);
            
        }
    }
    return new MultipartParsingResult(multipartFiles, 
                                      multipartParameters,
                                      multipartParameterContentTypes);
}

5.LocaleResolver

LocaleResolver的作用是使用request解析出Locale,它的繼承結構如圖所示。

在這裏插入圖片描述

​ 雖然LocaleResolver的實現類結構看起來比較複雜,但是實現卻非常簡單。在LocaleResolver的實現類中, AcceptHeaderLocaleResolver直接使用了Header裏的"acceptlanguage"值,不可以在程序中修改;FixedLocaleResolver用於解析出固定的Locale,也就是說在創建時就設置好確定的Locale,之後無法修改;SessionLocaleResolver用於將Locale保存到Session中,可以修改;CookieLocaleResolver用於將Locale保存到Cookie中,可以修改。

​ 另外,從Spring MVC4.0開始, LocaleResolver添加了一個子接口LocaleContextResolver,其中增加了獲取和設置LocaleContext的能力,並添加了抽象類AbstractLocaleContextResolver抽象類添加了對TimeZone也就是時區的支持。LocaleContextResolver接口定義如下:

public interface LocaleContextResolver extends LocaleResolver {
    LocaleContext resolveLocaleContext(HttpServletRequest request);
    void setLocaleContext(HttpServletRequest request, 
                          @Nullable HttpServletResponse response,
                          @Nullable LocaleContext localeContext);

}

下面分別看一下這些實現類,先來看AcceptHeaderLocaleResolver,這個類直接實現的LocaleResolver接口,代碼非常簡單,如下所示:

public Locale resolveLocale(HttpServletRequest request) {
    Locale defaultLocale = getDefaultLocale();
    if (defaultLocale != null && request.getHeader("Accept-Language") == null) {
        return defaultLocale;
    }
    Locale requestLocale = request.getLocale();
    List<Locale> supportedLocales = getSupportedLocales();
    if (supportedLocales.isEmpty() || supportedLocales.contains(requestLocale)) {
        return requestLocale;
    }
    Locale supportedLocale = findSupportedLocale(request, supportedLocales);
    if (supportedLocale != null) {
        return supportedLocale;
    }
    return (defaultLocale != null ? defaultLocale : requestLocale);
}

public void setLocale(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable Locale locale) {
    throw new UnsupportedOperationException(
        "Cannot change HTTP accept header - use a different locale resolution strategy");
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章