SpringMVC异常处理
1、局部异常处理
在J2EE项目的开发中,不管是对底层的数据库操作过程,还是业务层的处理过程,还是控制层的处理过程,都不可避免会遇到各种可预知的、不可预知的异常需要处理。每个过程都单独处理异常,系统的代码耦合度高,工作量大且不好统一,维护的工作量也很大。 而SpringMVC提供了强大的异常处理机制,既保证了相关处理过程的功能较单一,也实现了异常信息的统一处理和维护。新建一个SpringMVC项目,基础示例代码为https://perfectcode.top/2020/12/20/SpringMVC%E8%BF%90%E8%A1%8C%E6%B5%81%E7%A8%8B/。
①新建一个ExceptionController,编写一个异常请求以及一个异常方法,并在异常方法上标注@ExceptionHandler注解,即告诉SpringMVC此方法专门用来处理ExceptionController类发生的异常。
- 异常方法只能携带异常信息,参数位置不能写model,但可以返回ModelAndView。
- 如果有多个@ExceptionHandler方法能处理同一个异常,则精确优先。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ExceptionController {
public String handler(){
int i=1/0;
return "success";
}
public ModelAndView handleException1(Exception exception){
System.out.println("异常为:" + exception);
ModelAndView modelAndView = new ModelAndView("error");
modelAndView.addObject("exception", exception);
return modelAndView;
}
}②在webapp/WEB-INF/pages下新建一个error.jsp的自定义错误信息页面,并取出错误信息并显示。
1
2
3
4
5
6
7
8
9
10<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>请求错误</h1>
错误信息为:${exception}
</body>
</html>③运行项目,访问/exception请求后来到错误页面,并能正确打印错误信息。
2、全局异常处理
虽然以上示例能处理异常,但是会发现每个异常方法都只能处理本类的异常,于是可以改成全局异常处理方式。
新建一个GlobalExceptionHandler类用来处理全局异常,并添加@ControllerAdvice注解,其作用是将处理异常的类加入到ioc容器中以及告诉SpringMVC这是一个全局异常处理类。如果Controller里有@ExceptionHandler方法能处理这个异常,则本类优先,就不会使用全局异常处理。
1
2
3
4
5
6
7
8
9
10
11
public class GlobalExceptionHandler {
public ModelAndView handleException1(Exception exception){
System.out.println("全局异常为:" + exception);
ModelAndView modelAndView = new ModelAndView("error");
modelAndView.addObject("exception", exception);
return modelAndView;
}
}
3、使用@ResponseStatus
@ResponseStatus可以加在自定义运行时异常类上,那么它将会以指定的HTTP状态码和指定的reson响应到浏览器,我们自定义异常的目的就是为了让它正确表述我们的思想,所以给其设置响应状态码和原因让其准确表达我们的目的。注意如果要使@ResponseStatus处理异常能够生效,需不使用@ExceptionHandler。
1
2
3
public class MyErrorException extends RuntimeException {
}1
2
3
4
5
6
7
8
public class ExceptionController {
public String handler() {
throw new MyErrorException();
}
}启动项目后请求该接口:
4、异常处理原理
Spring MVC 通过 HandlerExceptionResolver 处理程序的异常,包括 Handler 映射、数据绑定以及目标方法执行时发生的异常。
SpringMVC 提供的 HandlerExceptionResolver 的实现类:
HandlerExceptionResolver是SpringMVC九大组件之一,能在前端控制器DispatcherServlet类中找到其初始化信息如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35private void initHandlerExceptionResolvers(ApplicationContext context) {
this.handlerExceptionResolvers = null;
if (this.detectAllHandlerExceptionResolvers) {
// Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.
Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils
.beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerExceptionResolvers = new ArrayList<>(matchingBeans.values());
// We keep HandlerExceptionResolvers in sorted order.
AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);
}
}
else {
try {
HandlerExceptionResolver her =
context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class);
this.handlerExceptionResolvers = Collections.singletonList(her);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, no HandlerExceptionResolver is fine too.
}
}
// Ensure we have at least some HandlerExceptionResolvers, by registering
// default HandlerExceptionResolvers if no other resolvers are found.
//如果在容器中找不到异常处理器则使用默认的配置
if (this.handlerExceptionResolvers == null) {
this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);
if (logger.isTraceEnabled()) {
logger.trace("No HandlerExceptionResolvers declared in servlet '" + getServletName() +
"': using default strategies from DispatcherServlet.properties");
}
}
}对应的默认配置信息在源码的DispatcherServlet.properties中,其位置如下:
在此配置文件中成功找到默认的三个异常处理器:
1
2
3org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver其中ExceptionHandlerExceptionResolver是用来处理@ExceptionHandler注解的;ResponseStatusExceptionResolver是用来处理@ResponseStatus注解的;
DefaultHandlerExceptionResolver是用来处理SpringMVC自己的异常,其异常有:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59if (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);
}现在在DispatcherServlet类的doDispatch方法的processDispatchResult方法处打上断点,运行项目后访问有异常的接口后来到此方法,调用此方法时会传入不为null的Exception而导致会调用processHandlerException进入到处理异常的逻辑:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86private 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);
//处理异常后返回一个ModelAndView对象,其中包含了模型数据以及跳转的页面地址
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.isTraceEnabled()) {
logger.trace("No view rendering, null ModelAndView returned.");
}
}
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}
if (mappedHandler != null) {
// Exception (if any) is already handled..
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex)throws Exception {
// Success and error responses may use different content types
request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
// Check registered HandlerExceptionResolvers...
ModelAndView exMv = null;
//获取能够解析此异常的解析器
if (this.handlerExceptionResolvers != null) {
for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
exMv = resolver.resolveException(request, response, handler, ex);
if (exMv != null) {
break;
}
}
}
if (exMv != null) {
if (exMv.isEmpty()) {
request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
return null;
}
// We might still need view name translation for a plain error model...
if (!exMv.hasView()) {
String defaultViewName = getDefaultViewName(request);
if (defaultViewName != null) {
exMv.setViewName(defaultViewName);
}
}
if (logger.isTraceEnabled()) {
logger.trace("Using resolved error view: " + exMv, ex);
}
else if (logger.isDebugEnabled()) {
logger.debug("Using resolved error view: " + exMv);
}
WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
return exMv;
}
throw ex;
}其中的handlerExceptionResolvers即为初始化后的默认的三个异常处理器:
①如果是基于@ExceptionHandler注解的异常处理方式,则是使用ExceptionHandlerExceptionResolver来处理的。它会在doResolveException()–>doResolveHandlerMethodException()判断该异常是否符合执行@ExceptionHandler注解标注的方法,如果有则执行被@ExceptionHandler注解标注的自定义方法并返回一个ModelAndView对象,最后调用视图解析器将ModelAndView转换成View后调用其render()方法完成视图渲染从而来到错误界面。
②如果是基于@ResponseStatus注解的异常处理方式,则是使用ResponseStatusExceptionResolver来处理的,它会在doResolveException()中判断当前抛出的异常是否标了@ResponseStatus注解,如果是则通过接连调用resolveResponseStatus()–>applyStatusAndReason()–>response.sendError()使此次请求立即结束并转发出/error请求将错误信息发给tomcat来响应错误页面,最后创建一个空的ModelAndView对象后返回。
③如果出现了SpringMVC框架底层的异常(例如上面列举的MissingServletRequestParameterException异常),则是由DefaultHandlerExceptionResolver来处理的。它会在doResolveException()中判断当前抛出的异常是否是框架底层的异常之一,如果是则进入对应的处理异常的方式并返回一个ModelAndView对象。这里以出现了MissingServletRequestParameterException为例,会调用handleMissingServletRequestParameter()–>response.sendError()使此次请求立即结束并转发出/error请求将错误信息发给tomcat来响应错误页面,最后创建一个空的ModelAndView对象后返回。
④如果以上三个异常解析器都无法处理错误信息,则SpringMVC会抛出异常后底层会转发出/error请求将错误信息发给tomcat来响应错误页面。
自定义异常解析器只需要实现HandlerExceptionResolver接口并实现resolveException()方法后加入ioc容器即可。SpringMVC还有个简单映射异常处理器SimpleMappingExceptionResolver,可以配置哪些异常去哪些页面。在springmvc.xml中配置这个bean:
1
2
3
4
5
6
7
8
9
10
11<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<!--exceptionMappings配置哪些异常去哪些页面-->
<property name="exceptionMappings">
<props>
<!--key是异常全类名,value是指定的页面-->
<prop key="java.lang.ArithmeticException">error</prop>
</props>
</property>
<!--指定异常信息取出时使用的key,默认是exception-->
<property name="exceptionAttribute" value="exception"></property>
</bean>需要注意的是加入这个异常处理器后其优先级是最低的,即当前面的三个处理器无法处理时才会轮到它处理,但由于它也实现了Ordered接口,因此可以通过修改order值来改变优先级。