概述
Spring MVC 通过 HandlerExceptionResolver 处理程序的异常,包括
Handler 映射、数据绑定以及目标方法执行时发生的异常。
SpringMVC 提供的 HandlerExceptionResolver 的实现类:
DispatcherServlet 默认装配
自定义错误页面信息
如果按SpringMVC或者Tomcat的默认错误页面,你不喜欢的话,可以自定义错误信息。
eg:数学运算异常
1、ExceptionHandlerExceptionResolver
作用
主要处理 Handler 中用 @ExceptionHandler 注解定义的方法。
@ExceptionHandler 注解定义的方法优先级问题:例如发生的是NullPointerException,
但是声明的异常有 RuntimeException 和 Exception,
此时会根据异常的最近继承关系找到继承深度最浅的那个 @ExceptionHandler 注解方法,
即标记了 RuntimeException 的方法
ExceptionHandlerMethodResolver 内部若找不到@ExceptionHandler 注解的话,
会找 @ControllerAdvice 中的@ExceptionHandler 注解方法
controller
@Controller
public class ErrorController {
@RequestMapping("/errorTest")
public String errorhandle01(ModelAndView modelAndView){
int i = 10/0;
return "success";
}
@ExceptionHandler(ArithmeticException.class)
public ModelAndView handleException01(Exception e) {
System.out.println("数学运算异常---");
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("myerror");
modelAndView.addObject("ex",e);
return modelAndView;
}
-------------------
//两个都能处理数学运算异常,按精度优先匹配。所有异常处理都是精度优先
----------------------
@ExceptionHandler(Exception.class)
public ModelAndView handleException02(Exception e) {
System.out.println("Exception---");
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("myerror");
modelAndView.addObject("ex",e);
return modelAndView;
}
}
jsp
<body>
错误信息:${ex}
</body>
结果
补充:异常对象不能通过Map集合方式传递给成功页面。(Map不可以),即之前说的入参直接放model,map,modelandview三个都只有Model旗下的可以,所以想将异常信息返回给页面,可以使用new ModelAndView的方式或者具体按ExceptionHandlerExceptionResolver支持的参数解析器的条件来操作携带信息
返回ModelAndView可以携带信息
else {
--
//在异常处理方法执行的时候,会new一个ModelAndViewContainer,执行后,方法内的信息会携带在里
//然后会在下面进行赋值处理,统一返回ModelAndView给页面进行渲染
--
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());
}
抽取公共的处理异常@ControllerAdvice
以上的写法是写在一个控制器的内部,只能处理当前控制器发生的异常,
这是显然是不符合可重用原则的,所以,可以对某些不是比较特殊的异常出里进行抽取
@ControllerAdvice注解
eg:
@ControllerAdvice
public class ExceptionAdviceHandler {
@ExceptionHandler(value={java.lang.ArithmeticException.class})
public ModelAndView handleException(Exception ex){
System.out.println("公共的处理异常ArithmeticException:"+ex);
ModelAndView mv = new ModelAndView("myerror");
mv.addObject("ex", ex);
return mv;
}
@ExceptionHandler(value={java.lang.Exception.class})
public ModelAndView handleException2(Exception ex){
System.out.println("公共的处理异常Exception:"+ex);
ModelAndView mv = new ModelAndView("myerror");
mv.addObject("ex", ex);
return mv;
}
}
内部和公共的处理优先级问题
只要内部的异常处理能处理发生的异常,就轮不到公共异常处理类,另:无论是内部的还是公共的,异常处理都按精度优先
2、ResponseStatusExceptionResolver
作用
若在处理器方法中抛出异常:若ExceptionHandlerExceptionResolver 解析不了异常。
由于触发的异常类型带有@ResponseStatus 注解。因此会被
ResponseStatusExceptionResolver 解析到。
最后响应状态代码和信息给客户端。
eg:@ResponseStatus标志的异常处理类
@ResponseStatus(value= HttpStatus.NOT_ACCEPTABLE,reason="数学运算异常")
public class RuntimeExceptionTest extends RuntimeException{
}
try {
int i = 10/0;
}catch (Exception e) {
--
//抛出一个自定义的异常,这个异常若ExceptionHandlerExceptionResolver解析不了
//但又有@ResponseStatus 注解
--
throw new RuntimeExceptionTest();
}
结果:
@ResponseStatus标在方法上
@ResponseStatus(value= HttpStatus.NOT_ACCEPTABLE,reason="数学运算异常")
@RequestMapping("/responseStatus")
public String errorhandle02(){
System.out.println("responseStatus");
return "success";
}
返回结果:
控制台输出:
原理
标在方法上一样作为方法执行,只是如果
@ResponseStatus(value= HttpStatus.NOT_ACCEPTABLE,reason="数学运算异常")中有reason的值,
会直接返回。mavContainer也标记此请求已被完全处理
3、DefaultHandlerExceptionResolver
对一些特殊的异常进行处理,比如:
NoSuchRequestHandlingMethodException、
HttpRequestMethodNotSupportedException、
HttpMediaTypeNotSupportedException、
HttpMediaTypeNotAcceptableException等。
eg:
<a href="/errorhandle03">errorhandle03</a>
@RequestMapping(value = "/errorhandle03" ,method = RequestMethod.POST)
public String errorhandle03(){
System.out.println("errorhandle03");
return "success";
}
结果
另:SimpleMappingExceptionResolver
作用
如果希望对所有异常进行统一处理,可以使用 SimpleMappingExceptionResolver,它将异常类名映射为视图名,即发生异常时使用对应的视图报告异常
web.xml
<bean id="mappingExceptionResolver" class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
--
//private Properties exceptionMappings;
//SimpleMappingExceptionResolver的exceptionMappings指定处理哪些异常
--
<property name="exceptionMappings">
<props>
--
//指定响应的错误页面
--
<prop key="java.lang.ArithmeticException">myerror</prop>
</props>
</property>
--
//private String exceptionAttribute = DEFAULT_EXCEPTION_ATTRIBUTE;
//指定返回携带错误信息的属性名,默认是exception
--
<property name="exceptionAttribute" value="ex"></property>
</bean>
原理:
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);
}
--
//最终会将错误信息放在ModelAndView中并返回,
//所以错误信息在页面可以使用${requestScope.exception}获取
--
return getModelAndView(viewName, ex, request);
}
else {
return null;
}
}
总结
SpringMVC的异常处理就是:从容器中找异常解析器,这个异常解析器作为九大组件之一,
如果你配了,就用你配的,如果你没配,就用默认的实现策略。
处理的流程就是:容器中的解析器逐个遍历,能处理的就返回
if (this.handlerExceptionResolvers != null) {
for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
exMv = resolver.resolveException(request, response, handler, ex);
if (exMv != null) {
break;
}
}
}
三个默认的解析器中;DefaultHandlerExceptionResolver 优先级最低
所以前面两个不能处理的错误它一般都能处理,如果它都不能处理,那就会扔给Tomcat