先来看一下继承关系
ViewResolverComposite实现了InitializingBean接口,也就是说当实例化对象的时候会调用afterPropertiesSet方法:我们就不说这个类了,实在是太简单了
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);
}
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 中,以便在视图渲染过程中使用,并将当前视图返回。
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;
}
}
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个视图。
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的优点结合起来的用法非常值得我们学习和借鉴。
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;
}
FreeMarkerViewResolver的代码就非常简单了,只是覆盖requiredViewClass方法返回FreeMarkerView类型,并在构造方法中调用setViewClass方法设置了viewClass.
我们通常使用的都是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;
}
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"。
可以配置。可以配置的参数除了这6个外还有4个: urlDecode, removeSemicolonContent.alwaysUseFullPath和urlPathHelper,前三个参数都是用在urlPathHelper中的, urlDecode用于设置url是否需要编解码,一般默认就行; removeSemicolonContent在前面已经说过了,用于设置是否删除url中与分号相关的内容; alwaysUseFullPath用于设置是否总使用完整路径;urlPathHelper是用于处理url的工具,一般使用spring默认提供的就可以了。
HandlerExceptionResolver用于解析请求处理过程中所产生的异常,继承结构如图所示。
其中HandlerExceptionResolverComposite作为容器使用,可以封装别的Resolver,前面已经多次介绍过,这里就不再叙述了。
HandlerExceptionResolver的主要实现都继承自抽象类AbstractHandlerExceptionResolver,它有四个子类:
AbstractHandlerMethodExceptionResolver和其子类ExceptionHandlerExceptionResolver一起完成使用
@ExceptionHnadler注释的方法进行异常解析的功能
DefaultHandlerExceptionResolver:按不同类型分别对异常进行解析。
ResponseStatusExceptionResolver:解析有@ResponseStatus注释类型的异常。
SimpleMappingExceptionResolver:通过配置的异常类和view的对应关系来解析异常。
异常解析过程主要包含两部分内容:给ModelAndView设置相应内容、设置response的相关属性。当然还可能有一些辅助功能,如记录日志等,在自定义的ExceptionHander里还可以做更多的事情。
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方法是模板方法,子类使用它具体完成异常的解析工作
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进行设置,如果需要可以自己在异常·处理器中设置。
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();
}
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方法。
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);
}
MultipartResolver用于处理上传请求,有两个实现类:
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,如下所示:
xml方式
<servlet>
<servlet-name >myWebservlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
servlet-class>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath:springmvc/servlet.xmlparam-value>
init-param >
<load-on-startup>1load-on-startup>
<multipart-config>
<max-file-size>5242880 max-file-size >
<max-request-size>20971520max-request-size>
< file-size-threshold >0 file-size-threshold >
multipart-config >
servlet >
注解方式:
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;
}
}
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并保存到对应的属性,以便在处理器中可以直接调用。
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);
}
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");
}