Loading

29-SpringMVC-5(Converter+拦截器+国际化+异常处理)

1. HttpMessageConverter

1.1 HMC 简述

HttpMessageConverter<T> 是 Spring3.0 新添加的一个接口,负责将请求信息转换为一个对象(类型为 T),将对象(类型为 T)输出为响应信息。

HttpMessageConverter 接口定义的方法:

Boolean canRead(Class<?> clazz, MediaType mediaType)
    指定转换器可以读取的对象类型,即转换器是否可将请求信息转换为 clazz 类
    型的对象,同时指定支持 MIME 类型(text/html,applaiction/json等)。
Boolean canWrite(Class<?> clazz, MediaType mediaType)
    指定转换器是否可将 clazz 类型的对象写到响应流中,响应流支持的媒体类型
    在 MediaType 中定义。
List<MediaType> getSupportMediaTypes()
    该转换器支持的媒体类型。
T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
    将请求信息流转换为 T 类型的对象 // 结合上图
void write(T t, MediaType contnetType, HttpOutputMessgae outputMessage)
    将 T 类型的对象写到响应流中,同时指定相应的媒体类型为 contentType // 结合上图

HttpInputMessage

public interface HttpInputMessage extends HttpMessage {

    InputStream getBody() throws IOException;

}

HttpOutputMessage

public interface HttpOutputMessage extends HttpMessage {

    OutputStream getBody() throws IOException;

}

1.2 HMC 实现类

每个 Converter 都有自己能处理的类型。

DispatcherServlet 默认装配 RequestMappingHandlerAdapter ,而 RequestMappingHandlerAdapter 默认装配的 HttpMessageConverter 如下:

加入 jackson jar 包后, RequestMappingHandlerAdapter 装配的 HttpMessageConverter 如下:

MappingJackson2HttpMessageConverter 的泛型是 Object。


使用 HttpMessageConverter<T> 将请求信息转化并绑定到处理方法的形参中或将响应结果转为对应类型的响应信息,Spring 提供了两种途径:

  • 使用 @RequestBody / @ResponseBody 对处理方法进行标注
  • 使用 HttpEntity<T> / ResponseEntity<T> 作为处理方法的形参或返回值

当控制器处理方法使用到 @RequestBody/@ResponseBodyHttpEntity<T>/ResponseEntity<T> 时,Spring 首先根据请求头或响应头的 Accept 属性选择匹配的 HttpMessageConverter, 进而根据参数类型或泛型类型的过滤得到匹配的 HttpMessageConverter,若找不到可用的 HttpMessageConverter 将报错

1.3 Json 类型转换

  1. 加入 jar 包
  2. 编写目标方法,使其返回/接收 JSON 对应的对象或集合
  3. 在方法上添加 @ResponseBody | @RequestBody 注解

1.2.1 @ResponseBody

  • @ResponseBody 将目标方法返回的数据放入响应体
    • 如果是对象,导入的 Jackson.jar 会自动将对象转为 json 格式
    • 如果是视图名,就在客户端浏览器打印视图名,不会再转发到对应视图了
  • json 还有很多可以加在 JavaBean 属性上的注解,如 :
    @JsonFormat(pattern = "yyyy-MM-dd")
    private Date birth = new Date();
    

1.2.2 @RequestBody

@RequestBody 可以获取一个(POST)请求的请求体。再者,既然 @ResponseBody 可以把对象转为 json 返回给浏览器;那么 @RequestBody 自然也就可以反向操作,接收 json 数据再封装成对象。

@RequestMapping("/testRequestBody")
public String testRequestBody(@RequestBody Employee reqBodyToEmp) {
    System.out.println(reqBodyToEmp);
    return "../../ajax";
}
$("#btn2").click(function() {
    // 发送 Ajax 请求
    var emp = {lastName:'张三', email:'aaa@aa.com', gender:1};
    var empStr = JSON.stringify(emp);
    // alert(typeof emp); // Object
    // alert(typeof empStr); // String
    $.ajax({
        url: '${pageContext.request.contextPath }/testRequestBody',
        type: "POST",
        data: empStr,
        contentType: "application/json",
        success:function(data) {
            alert(data);
        }
    });
    return false;
});

请求头一览

1.3 请求/响应实体

1.3.1 HttpEntity

可以拿到所有请求头。

@RequestMapping("/test01")
public String test01(HttpEntity<String> str) {
    System.out.println(str);
    return "../../ajax";
}
·····························································
<,{accept=[image/gif, image/jpeg, image/pjpeg, application/x-ms-application
, application/xaml+xml, application/x-ms-xbap, */*], accept-language=[zh-Hans-CN
,zh-Hans;q=0.5], ua-cpu=[AMD64], accept-encoding=[gzip, deflate], user-agent
=[Mozilla/5.0 (Windows NT 6.2; Win64; x64; Trident/7.0; rv:11.0) like Gecko]
,host=[localhost:8080], connection=[Keep-Alive],cookie=[JSESSIONID
=917AF2A4399F13061B76B71299B7D7C1; Webstorm-d70cb02f
=56d06134-d193-4b9a-9add-38fde8c9e207]}>

1.3.2 ResponseEntity

自定义响应体、响应头、响应码。

@RequestMapping("/test02")
public ResponseEntity<String> test02() {
    MultiValueMap<String, String> headers = new HttpHeaders();
    String body = "<h1>SUCCESS</h1>";
    headers.add("Set-Cookie", "root=shaw");
    ResponseEntity<String> responseEntity = new
            ResponseEntity<String>(body, headers, HttpStatus.OK);
    return responseEntity;
}

文件下载示例:

@RequestMapping("/download")
public ResponseEntity<byte[]> download(HttpServletRequest request) throws Exception{
    // 1. 找到要下载的文件的真实路径
    ServletContext context = request.getServletContext();
    String realPath = context.getRealPath("/scripts/jquery-1.9.1.min.js");
    // 2. 得
    到要下载的文件的流;
    FileInputStream is = new FileInputStream(realPath);
    byte[] tmp = new byte[is.available()];
    is.read(tmp);
    is.close();
    // 3. 将要下载的文件流返回
    HttpHeaders httpHeaders = new HttpHeaders();
    httpHeaders.set("Content-Disposition", "attachment;filename="+"jquery-1.9.1.min.js");
    return new ResponseEntity<byte[]>(tmp, httpHeaders, HttpStatus.OK);
}

2. 文件上传

Spring MVC 为文件上传提供了直接的支持,这种支持是通过即插即用的 MultipartResolver(九大组件之一) 实现的。

Spring 用 Jakarta Commons FileUpload 技术实现了一个 MultipartResolver 实现类:CommonsMultipartResovler。但 Spring MVC 上下文中默认没有装配 MultipartResovler,因此默认情况下不能处理文件的上传工作,如果想使用 Spring 的文件上传功能,需现在上下文中配置 MultipartResolver。

<bean id="multipartResolver" 
        class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <!-- 最大文件上传大小 -->
    <property name="maxUploadSize" value="#{1024*1024*20}"></property>
    <!-- 设置默认的字符编码,必须和 JSP 的 pageEncoding 属性一致 -->
    <property name="defaultEncoding" value="utf-8"></property>
</bean>

为了让 CommonsMultipartResovler 正确工作,必须先将 Jakarta Commons FileUpload 及 Jakarta Commons io 的类包添加到类路径下。


文件上传示例

<form action="${pageContext.request.contextPath }/upload"
        method="post" enctype="multipart/form-data">
    用户头像:<input type="file" name="headerImg" /><br/>
    用户名:<input type="text" name="username" /><br/>
    <input type="submit" value="提交"/>
</form>
@Controller
public class FileUploadController {
    <!--
     @RequestParam("headerImg") MultipartFile file
     设置文件上传项在目标方法的形参为 ↑
     如果是多文件上传,将形参类型变成对应类型的数组即可
     -->
    @RequestMapping("/upload")
    public String upload(@RequestParam("username") String username
            , @RequestParam("headerImg") MultipartFile file) {
        System.out.println("文件项name:" + file.getName());
        System.out.println("原文件名: " + file.getOriginalFilename());

        // 文件保存
        try {
            file.transferTo(new File("U:\\" + file.getOriginalFilename()));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "forward:/upload.jsp";
    }
}

3. 拦截器

3.1 HandlerInterceptor

Spring MVC 也可以使用拦截器对请求进行拦截处理,用户可以自定义拦截器来实现特定的功能,自定义的拦截器必须实现 HandlerInterceptor 接口。

  • preHandle() 在业务处理器处理请求之前被调用,在该方法中对用户请求 request 进行处理。如果程序员决定该拦截器对请求进行拦截处理后还要调用其他的拦截器,或者是业务处理器去进行处理,则返回 true;如果程序员决定不需要再调用其他的组件去处理请求,则返回 false。
  • postHandle() 在业务处理器处理完请求后,但是 DispatcherServlet 向客户端返回响应前被调用,在该方法中对用户请求 request 进行处理。
  • afterCompletion() 在 DispatcherServlet 完全处理完请求(资源已响应) 后被调用,可以在该方法中进行一些资源清理的操作。

3.2 拦截器运行流程

3.2.1 单拦截器运行流程

Controller

@Controller
public class TestInterceptorController {

    @RequestMapping("/test01")
    public String test01() {
        System.out.println("Method test01");
        return "success";
    }
}

index.jsp

<a href="${pageContext.request.contextPath }/test01">test01</a>

success.jsp

<body>
<%System.out.println("SUCCESS JSP"); %>
</body>

FirstInterceptor(SecondInterceptor 同理)

public class FirstInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request
            , HttpServletResponse response, Object handler) throws Exception {
        System.out.println("[FirstInterceptor] preHandle");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response
            , Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("[FirstInterceptor] postHandle");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse
            response, Object handler, Exception ex) throws Exception {
        System.out.println("[FirstInterceptor] afterCompletion");
    }
}

springMVC.xml

<!-- 配置拦截器(谁靠前,谁优先) -->
<mvc:interceptors>
    <!-- 配置第一个拦截器,拦截所有请求 -->
    <bean class="cn.edu.nuist.component.FirstInterceptor"></bean>
    <!-- 配置第二个拦截器更详细的信息,拦截指定资源 -->
    <mvc:interceptor>
        <!-- 只拦截 test01 请求 -->
        <mvc:mapping path="/test01"/>
        <bean class="cn.edu.nuist.component.SecondInterceptor"></bean>
    </mvc:interceptor>
</mvc:interceptors>

  • 正常运行流程:preHandle → 目标方法 → postHandle → 响应资源 → afterCompletion
  • 其他流程
    • preHandle() 返回 false
    • 目标方法抛异常

3.2.2 多拦截器运行流程

正常流程

Second 不放行(已放行拦截器的 afterCompletion 总会执行)

3.3 查看源码

3.3.1 DispatcherServlet

doDispatcher() 部分源码:

try {
    // ...
    try {
        // ...

        // 【拦截器 preHandle 执行位置】只要有一个返false,直接跳到 afterCompletion
        if (!mappedHandler.applyPreHandle(processedRequest, response)) {
            return;
        }

        try {
            // Actually invoke the handler.
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
        } finally {
            if (asyncManager.isConcurrentHandlingStarted()) {
                return;
            }
        }

        applyDefaultViewName(request, mv);

        // 【拦截器 postHandle 执行位置】逆序执行每一个拦截器的 postHandle
        mappedHandler.applyPostHandle(processedRequest, response, mv);
    } catch (Exception ex) {
        dispatchException = ex;
    }

    // 【页面渲染】内部有调用 afterCompletion。若抛异常,也会被下面 catch 住
    processDispatchResult(processedRequest
        , response, mappedHandler, mv, dispatchException);

} catch (Exception ex) {
    // 【最外围 catch】抓住异常,依旧正常执行 afterCompletion
    triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
} catch (Error err) {
    triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
} finally {
    if (asyncManager.isConcurrentHandlingStarted()) {
        // Instead of postHandle and afterCompletion
        mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
        return;
    }
    // Clean up any resources used by a multipart request.
    if (multipartRequestParsed) {
        cleanupMultipart(processedRequest);
    }
}

processDispatchResult() 部分源码:

// Did the handler return a view to render?
if (mv != null && !mv.wasCleared()) {
    render(mv, request, response);
    if (errorView) {
        WebUtils.clearErrorRequestAttributes(request);
    }
}

// ...

// 页面渲染完成后,顺序执行到此处,执行 afterCompletion
// 即使渲染出错,没走到这,在外层 catch 块里还是会顺利执行
if (mappedHandler != null) {
    mappedHandler.triggerAfterCompletion(request, response, null);
}

3.3.2 preHandle

boolean applyPreHandle(HttpServletRequest request
        , HttpServletResponse response) throws Exception {
    if (getInterceptors() != null) {
        for (int i = 0; i < getInterceptors().length; i++) {
            HandlerInterceptor interceptor = getInterceptors()[i];
            // 若返回 true,继续循环遍历下一个 Interceptor
            // 若返回 false,除退出该方法外,触发调用放行拦截器的 afterCompletion
            if (!interceptor.preHandle(request, response, this.handler)) {
                triggerAfterCompletion(request, response, null);
                return false;
            }
            // 记录能走到这儿(放行)的拦截器的最大索引
            this.interceptorIndex = i;
        }
    }
    return true;
}

3.3.3 postHandle

void applyPostHandle(HttpServletRequest request, HttpServletResponse
         response, ModelAndView mv) throws Exception {
    if (getInterceptors() == null) {
        return;
    }

    for (int i = getInterceptors().length - 1; i >= 0; i--) {
        HandlerInterceptor interceptor = getInterceptors()[i];
        interceptor.postHandle(request, response, this.handler, mv);
    }
}

3.3.4 afterCompletion

void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse
        response, Exception ex) throws Exception {

    if (getInterceptors() == null) {
        return;
    }

    // i: 放行拦截器的索引
    for (int i = this.interceptorIndex; i >= 0; i--) {
        HandlerInterceptor interceptor = getInterceptors()[i];
        try {
            interceptor.afterCompletion(request, response, this.handler, ex);
        } catch (Throwable ex2) {
            logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
        }
    }
}

3.4 Filter 和 Interceptor

  • 如果某些功能,需要其他组件配合完成,就是用 Interceptor;其他情况可以写 Filter。
  • Interceptor 脱离 SpringMVC 就没用了,而 Filter 是 JavaWeb 三大组件。

4. 国际化

4.1 简单使用

  1. 写好国际化资源文件
  2. 让 Spring 的 ResourceBundleMessageSource 管理国际化资源文件
    <bean id="messageSource" 
            class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basename" value="/loginPage/login"></property>
    </bean>
    
  3. 直接去页面取值
    <h1><fmt:message key="welcomeInfo" /></h1>
    <form action="#">
        <fmt:message key="username" /><input type="text" name="username"/><br/>
        <fmt:message key="password" /><input type="password" name="password"/><br/>
        <input type="submit" value="<fmt:message key="loginBtn" />">
    </form>
    

4.2 AcceptHeaderLocaleResolver

  • 国际化的区域信息是决定国际化显示的因素。而 SpringMVC 中的区域信息是由区域信息解析器得到的:private LocaleResolver localeResolver;
  • 查看 DispatcherServlet.properties 可知:默认使用 AcceptHeaderLocaleResolver
    public class AcceptHeaderLocaleResolver implements LocaleResolver {
    
        @Override
        public Locale resolveLocale(HttpServletRequest request) {
            return request.getLocale(); // 使用请求头的区域信息
        }
    
        @Override
        public void setLocale(HttpServletRequest request
                , HttpServletResponse response, Locale locale) {
            throw new UnsupportedOperationException("Cannot change HTTP accept header"
                    + " - use a different locale resolution strategy");
        }
    }
    
  • 凡是用到区域信息的地方都是调用上述解析器的方法得到的;以渲染视图为例:
    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.resolveLocale(request);
        response.setLocale(locale);
    
        // ...
    }
    

4.3 程序中获取国际化信息

@Controller
public class TestI18nController {

    @Autowired // 程序中获取国际化信息
    private MessageSource messageSource;

    @RequestMapping("toLoginPage")
    public String toLoginPage(Locale locale) {
        System.out.println(locale); // zh_CN
        String welcomeInfo = messageSource.getMessage("welcomeInfo", null, locale);
        System.out.println(welcomeInfo);
        return "login";
    }
}

4.4 点击超链接切换国际化

<a href="toLoginPage?locale=zh_CN">中文</a>|
<a href="toLoginPage?locale=en_US">English</a>

4.4.1 自定义区域信息解析器

public class MyLocaleResolver implements LocaleResolver {

    @Override
    public Locale resolveLocale(HttpServletRequest request) {
        String localeStr = request.getParameter("locale");
        Locale l = null; // zh_CN
        if(localeStr != null && !"".equals(localeStr)) {
            l = new Locale(localeStr.split("_")[0], localeStr.split("_")[1]);
        } else {
            l = request.getLocale();
        }
        return l;
    }

    @Override
    public void setLocale(HttpServletRequest request
            , HttpServletResponse response, Locale locale) {
        throw new UnsupportedOperationException(
                "Cannot change HTTP accept header "
                + "- use a different locale resolution strategy");
    }
}

SessionLocaleResolver

4.4.2 SessionLocaleResolver

  • SessionLocaleResolver 的区域信息是从 session 中获取;也可以根据请求参数创建一个 Locale 对象,把他放在 session 中
    @Override
    public Locale resolveLocale(HttpServletRequest request) {
        Locale locale = (Locale) WebUtils.getSessionAttribute(
                request, LOCALE_SESSION_ATTRIBUTE_NAME);
        if (locale == null) {
            locale = determineDefaultLocale(request);
        }
        return locale;
    }
    
  • LocaleChangeInterceptor 部分源码
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response
            , Object handler) throws ServletException {
    
        String newLocale = request.getParameter(this.paramName);
        if (newLocale != null) {
            LocaleResolver localeResolver = RequestContextUtils.getLocaleResolver(request);
            if (localeResolver == null) {
                throw new IllegalStateException("No LocaleResolver found:"
                    + "not in a DispatcherServlet request?");
            }
            localeResolver.setLocale(request, response
                    , StringUtils.parseLocaleString(newLocale));
        }
        // Proceed in any case.
        return true;
    }
    
  • 配置到 springMVC.xml
  • 工作原理

5. 异常处理

5.1 HandlerExceptionResolver

Spring MVC 通过 HandlerExceptionResolver 处理程序的异常,包括 Handler 映射、数据绑定以及目标方法执行时发生的异常。

查看 DispatcherServlet.properties 文件:

HandlerExceptionResolver =
    AnnotationMethodHandlerExceptionResolver(被ExceptionHandlerExceptionResolver取代),
    ResponseStatusExceptionResolver,
    DefaultHandlerExceptionResolver

若使用了 <mvc:annotation-driven/> ,查看 AnnotationDrivenBeanDefinitionParser.parse() 方法:

RootBeanDefinition exceptionHandlerExceptionResolver =
        new RootBeanDefinition(ExceptionHandlerExceptionResolver.class);

5.2 三个默认异常解析器的使用

这 3 个异常解析器各自能处理什么样的异常?

5.2.1 ExceptionHandlerExceptionResolver

当 Handler 类中的处理方法发生异常,ExceptionHandlerExceptionResolver 会去调用处理器本类中用 @ExceptionHandler 定义的方法来处理当前异常。

@ExceptionHandler 注解定义的方法仅可添加一个 Exception 类型的方法形参,用来接收异常对象。那么,如何携带异常信息到页面 ? 设置异常处理方法的返回值类型为 ModelAndView(不能在形参上写 Model/Map,这个不是目标方法;而是个异常处理方法,只认异常类型参数)

示例:

@ExceptionHandler(value= {ArithmeticException.class, NullPointerException.class})
public ModelAndView handleException01(Exception e) {
    ModelAndView mav = new ModelAndView();
    mav.setViewName("myError");
    mav.addObject("ex", e);
    return mav;
}

@ExceptionHandler 定义的方法优先级问题:例如发生的是 NullPointerException,但是声明的异常有 RuntimeException 和 Exception,此时会根据异常的最近继承关系找到继承深度最浅的那个 @ExceptionHandler 注解方法,即标记了 RuntimeException 的方法。

写在 Controller 里,只对当前 Controller 有效;写在 @ControllerAdvice 标注的类里,全局有效;对某异常的处理若同时存在,本类优先,本类能处理的,自己处理;处理不了的,再交给全局。// 这个和继承关系无关,就算本类是 Exception,全局就是抛出的那个异常类型,也还是会使用本类的。

ExceptionHandlerMethodResolver 内部若找不到 @ExceptionHandler 注解的话,会找 @ControllerAdvice 标注的类(全局异常处理类) 中的 @ExceptionHandler 注解方法,看有没有哪个方法能处理该异常。

5.2.2 ResponseStatusExceptionResolver

当目标处理方法抛出“被 @ResponseStatus 标注的异常”

示例:定义一个 @ResponseStatus 修饰的异常类

@ResponseStatus(reason = "用户被拒绝登录", value=HttpStatus.NOT_ACCEPTABLE)
public class UserNameNotFoundException extends RuntimeException {}

若在处理器方法中抛出了上述异常:若 ExceptionHandlerExceptionResolver 不解析上述异常,而又由于触发的异常 UserNameNotFoundException 带有 @ResponseStatus 注解。因此会被 ResponseStatusExceptionResolver 解析到,然后使用该注解的属性进行处理:① 响应 value 属性对应的 HttpStatus 代码给客户端 ② 错误信息为 reason 属性值。

5.2.3 DefaultHandlerExceptionResolver

对 Spring 自定义异常进行处理。

NoSuchRequestHandlingMethodException、HttpRequestMethodNotSupportedException、
HttpMediaTypeNotSupportedException、HttpMediaTypeNotAcceptableException ...

doResolveException() 源码:

if (ex instanceof NoSuchRequestHandlingMethodException) {
    return handleNoSuchRequestHandlingMethod((NoSuchRequestHandlingMethodException) ex
            , request, response, handler);
}
else 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 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);
}

5.3 异常处理流程

DispatcherServlet

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response
        , HandlerExecutionChain mappedHandler, ModelAndView mv
        , Exception exception) throws Exception {

    boolean errorView = false;

    // 如果有异常
    if (exception != null) {
        if (exception instanceof ModelAndViewDefiningException) {
            logger.debug("ModelAndViewDefiningException encountered", exception);
            mv = ((ModelAndViewDefiningException) exception).getModelAndView();
        }
        else { // 处理异常!!!
            Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
            mv = processHandlerException(request, response, handler, exception);
            errorView = (mv != null);
        }
    }

    // Did the handler return a view to render?
    if (mv != null && !mv.wasCleared()) {
        render(mv, request, response);
        if (errorView) {
            WebUtils.clearErrorRequestAttributes(request);
        }
    }
    else {
        if (logger.isDebugEnabled()) {
            logger.debug("Null ModelAndView returned to DispatcherServlet with name '"
                    + getServletName() + "': assuming HandlerAdapter"
                        + "completed request handling");
        }
    }

    if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
        // Concurrent handling started during a forward
        return;
    }

    if (mappedHandler != null) {
        mappedHandler.triggerAfterCompletion(request, response, null);
    }
}

protected ModelAndView processHandlerException(HttpServletRequest request
        , HttpServletResponse response, Object handler, Exception ex) throws Exception {

    // Check registered HandlerExceptionResolvers...
    ModelAndView exMv = null;
    for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) {
        exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);
        if (exMv != null) {
            break;
        }
    }

    if (exMv != null) {
        if (exMv.isEmpty()) {
            return null;
        }
        // We might still need view name translation for a plain error model...
        if (!exMv.hasView()) {
            exMv.setViewName(getDefaultViewName(request));
        }
        if (logger.isDebugEnabled()) {
            logger.debug("Handler execution resulted in exception "
                 + "- forwarding to resolved error view: " + exMv, ex);
        }
        WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
        return exMv;
    }

    // 如果异常解析器都不能处理,就直接抛出去
    throw ex;
}

5.4 SimpleMappingExceptionResolver

通过配置的方式进行异常处理。

<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
    <!-- 配置哪些异常去哪些页面 -->
    <property name="exceptionMappings">
        <props>
            <!-- key: 异常全类名;value: 要去的页面视图名 -->
            <prop key="java.lang.ArrayIndexOutOfBoundsException">myError</prop>
        </props>
    </property>
    <!-- 指定错误信息取出时,用的 key 的名字(默认叫 exception) -->
    <property name="exceptionAttribute" value="ex"></property>
</bean>

posted @ 2020-09-15 18:16  tree6x7  阅读(225)  评论(0编辑  收藏  举报