一、SpringMVC 异常处理

二、HandlerExceptionResolver
1、DispatcherServlet 默认装配的 HandlerExceptionResolver
当自己没有配置异常处理器,也没有使用 <mvc:annotation-driven /> 配置,DispatcherServlet 会默认加载 DispatcherServlet.properties 中的 HandlerExceptionResolver:
DispatcherServlet 加载异常处理器:
private 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<HandlerExceptionResolver>(matchingBeans.values());
// We keep HandlerExceptionResolvers in sorted order.
OrderComparator.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.isDebugEnabled()) {
logger.debug("No HandlerExceptionResolvers found in servlet '" + getServletName() + "': using default");
}
}
}
DispatcherServlet.properties 中的异常处理器:
1 2 3 | org.springframework.web.servlet.HandlerExceptionResolver= org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\ org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\ org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver |
2、使用了 <mvc:annotation-driven /> 配置
当使用了 <mvc:annotation-driven /> 标签后,会自动注入更多的组件,其中关于异常处理器组件如下:
parserContext.registerComponent(new BeanComponentDefinition(exceptionHandlerExceptionResolver, methodExceptionResolverName));
parserContext.registerComponent(new BeanComponentDefinition(responseStatusExceptionResolver, responseStatusExceptionResolverName));
parserContext.registerComponent(new BeanComponentDefinition(defaultExceptionResolver, defaultExceptionResolverName));
最终生效的异常处理器:
1 2 3 | ExceptionHandlerExceptionResolver ResponseStatusExceptionResolver DefaultHandlerExceptionResolver |
三、SpringMVC 异常处理流程
从页面发起一个请求,通过传入的参数来触发运行时异常:
页面请求:(可以从地址栏动态传参)
<a href="${ctp}/handle01?i=10">test01</a>
控制器方法:(如果参数为0,就会有异常)
@RequestMapping(value = "/handle01")
public String handle01(Integer i) {
System.out.println("handle01");
System.out.println(10 / i);
return "success";
}
开启SpringMVC高级功能:
<mvc:annotation-driven/>
异常处理流程:
来到 DispatcherServlet 中 dispatch() 方法:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
// ① 执行目标方法,如果没有异常正常运行;
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(request, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
// ② 处理结果(可能会有异常)
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
// ⑦ 异常解析器不能处理异常,执行完 afterCompletion后,抛出异常,抛给Tomcat
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Error err) {
triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
processDispatchResult() 方法:
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);
}
}
processHandlerException() 方法:
所有的异常解析器尝试解析,解析完成进行后续,解析失败下一个解析器
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) throws Exception {
// Check registered HandlerExceptionResolvers...
ModelAndView exMv = null;
//⑤ 遍历所有的异常解析器
//如果异常解析器能够处理,返回 exMV,然后到 ⑥ 页面渲染
//如果所有异常解析器不能够处理,直接抛出异常 ex,然后到 catch ⑦
for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) {
exMv = handlerExceptionResolver.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()) {
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;
}
如果异常解析器都不能处理就直接抛出去,抛给Tomcat了,显示错误页面。
SpringMVC 中装配的异常解析器:
1 2 3 | ExceptionHandlerExceptionResolver:处理标注 @ExceptionHandler ResponseStatusExceptionResolver:处理标注 @ResponseStatus DefaultHandlerExceptionResolver:判断是否SpringMVC自带的异常 |
四、ExceptionHandlerExceptionResolver 异常解析器
1、定义异常处理方法
可以在控制器中定义处理异常方法,告诉SpringMVC这个方法专门处理这个类发生的异常,可以使用 value 属性来指定要处理的异常类型。
(1)给方法上随便写一个 Exception,用来接收发生的异常;
(2)要携带异常信息不能给参数位置写 Model;
(3)直接返回 ModelAndView,可以放进异常信息;
示例:
@ExceptionHandler(value = {ArithmeticException.class, NullPointerException.class})
public ModelAndView handleException01(Exception exception) {
System.out.println("本类的:handleException01..." + exception);
ModelAndView mv = new ModelAndView("MyError");
//获取异常信息
mv.addObject("ex", exception);
//视图解析器拼串,自定义错误页面
return mv;
}
可以写多个处理异常的方法:
@ExceptionHandler(value = {Exception.class})
public ModelAndView handleException02(Exception exception) {
System.out.println("本类的:handleException02..." + exception);
ModelAndView mv = new ModelAndView("MyError");
//获取异常信息
mv.addObject("ex", exception);
//视图解析器拼串
return mv;
}
如果有多个 @ExceptionHandler 都能处理异常,精确匹配优先处理;
2、全局处理异常
可以单独定义一个类来处理全部的异常情况:
/** * 1、集中处理所有异常的类加入到 ioc 容器中 * https://blog.csdn.net/qq_40726812/article/details/115521502 * 2、@ControllerAdvice 专门处理异常的类 */ @ControllerAdvice public class MyCenterException { @ExceptionHandler(value = {ArithmeticException.class}) public ModelAndView handleException01(Exception exception) { System.out.println("全局的:handleException01..." + exception); ModelAndView mv = new ModelAndView("MyError"); //获取异常信息 mv.addObject("ex", exception); //视图解析器拼串 return mv; } @ExceptionHandler(value = {Exception.class}) public ModelAndView handleException02(Exception exception) { System.out.println("全局的:handleException02..." + exception); ModelAndView mv = new ModelAndView("MyError"); //获取异常信息 mv.addObject("ex", exception); //视图解析器拼串 return mv; } }
配置文件中开启 SpringMVC 高级功能:
<mvc:annotation-driven/>
当同时配置了全局异常和本类异常处理同时存在,本类处理优先,本类中精确匹配优先。
3、ExceptionHandlerExceptionResolver 异常解析器
主要处理 Handler 中用 @ExceptionHandler 注解定义的方法;
@ExceptionHandler 注解定义的方法优先级问题:例如发生的是NullPointerException,但是声明的异常有 RuntimeException 和 Exception,此候会根据异常的最近继承关系找到继承深度最浅的那个 @ExceptionHandler 注解方法,即标记了 RuntimeException 的方法;
ExceptionHandlerMethodResolver 内部若找不到 @ExceptionHandler 注解的话,会找 @ControllerAdvice 中的 @ExceptionHandler 注解方法;
五、ResponseStatusExceptionResolver 异常解析器
1、使用 @ResponseStatus
这个注解标注在自定义异常类上。
自定义异常类:
@ResponseStatus(reason = "用户被拒绝登录", value = HttpStatus.NOT_ACCEPTABLE)
public class UserNameNotFoundException extends RuntimeException{}
页面请求:
<a href="${ctp}/handle02?username=admin111">handle02</a>
控制器方法:
@RequestMapping(value = "/handle02")
public String handle02(@RequestParam("username") String username) {
if (!"admin".equals(username)) {
System.out.println("登录失败!");
throw new UserNameNotFoundException();
}
System.out.println("登录成功!");
return "success";
}
展现效果:
2、ResponseStatusExceptionResolver 异常解析器
在异常及异常父类中找到 @ResponseStatus 注解,然后使用这个注解的属性进行处理。
定义一个 @ResponseStatus 注解修饰的异常类
若在处理器方法中抛出了上述异常:若ExceptionHandlerExceptionResolver 不解析述异常。
由于触发的异常 UnauthorizedException 带有@ResponseStatus 注解。因此会被 ResponseStatusExceptionResolver 解析到。最后响应HttpStatus.UNAUTHORIZED 代码给客户端。HttpStatus.UNAUTHORIZED 代表响应码401,无权限。
关于其他的响应码请参考 HttpStatus 枚举类型源码。
六、DefaultHandlerExceptionResolver 异常解析器
DefaultHandlerExceptionResolver:判断是否 SpringMVC 自带的异常。
SpringMVC 自带的异常,如:HttpRequestMethodNotSupportedException ,如果没人处理,就使用 DefaultHandlerExceptionResolver 处理。
SpringMVC 自带的异常:
NoSuchRequestHandlingMethodException、
HttpRequestMethodNotSupportedException、
HttpMediaTypeNotSupportedException、
HttpMediaTypeNotAcceptableException等。
示例代码:
页面请求:(get请求)
<a href="${ctp}/handle03">handle03</a>
控制器方法:(post方式接收)
@RequestMapping(value = "/handle03", method = RequestMethod.POST)
public String handle03() {
return "success";
}
会抛出异常信息
然后用异常解析器来尝试解析,前两个都解析不了,使用 DefaultHandlerExceptionResolver 来解析。
DefaultHandlerExceptionResolver 解析器:
@Override
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) {
try {
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);
}
}
catch (Exception handlerException) {
logger.warn("Handling of [" + ex.getClass().getName() + "] resulted in Exception", handlerException);
}
return null;
}
DefaultHandlerExceptionResolver 用来解析 SpringMVC 的自定义异常,这个解析器是对这些已经定义好的异常进行处理的,解析完毕后,来到错误页面。
例如:
protected ModelAndView handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex,
HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
pageNotFoundLogger.warn(ex.getMessage());
String[] supportedMethods = ex.getSupportedMethods();
if (supportedMethods != null) {
response.setHeader("Allow", StringUtils.arrayToDelimitedString(supportedMethods, ", "));
}
response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, ex.getMessage());
return new ModelAndView();
}
错误页面:
七、简单映射异常解析器 SimpleMappingExceptionResolver
如果希望对所有的异常或自定义的异常进行统一处理,可以使用 SimpleMappingExceptionResolver,它将异常类名映射为视图名,即发生异常时使用对应的视图报告异常。
配置 SimpleMappingExceptionResolver:
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<!-- exceptionMappings:配置哪些异常去哪些页面 属性是 Properties类型-->
<property name="exceptionMappings">
<!--自定义的异常与跳转的视图名-->
<props>
<!--key:异常全类名, value:视图页面视图名称 -->
<prop key="java.lang.NullPointerException">error</prop>
<prop key="java.lang.ArithmeticException">error</prop>
</props>
</property>
<!--指定错误信息取出使用的 key-->
<property name="exceptionAttribute" value="ex"></property>
</bean>
控制器方法:模拟空指针异常
@RequestMapping(value = "/handle04")
public String handle04() {
System.out.println("handle04 空指针异常");
String str = null;
System.out.println(str.charAt(0));
return "success";
}
异常页面:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h2>空指针异常!!!</h2>
<h2>出错信息:${ex } --- ${exception}</h2>
</body>
</html>
分析:
当配置了 SimpleMappingExceptionResolver,会有四个异常解析器:
发生异常后会根据异常解析器顺序进行解析,SimpleMappingExceptionResolver:
解析完毕后会到错误页面进行渲染。
异常解析器:自动将异常对象信息,存放到 request 范围内
<!-- 配置SimpleMappingExceptionResolver异常解析器 --
<bean id="simpleMappingExceptionResolver" class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<!-- exceptionAttribute 默认值(通过ModelAndView传递给页面):
exception -> ${requestScope.exception}
public static final String DEFAULT_EXCEPTION_ATTRIBUTE = "exception";
-->
<property name="exceptionAttribute" value="ex"></property>
<property name="exceptionMappings">
<props>
<prop key="java.lang.NullPointerException">error</prop>
<prop key="java.lang.ArithmeticException">error</prop>
</props>
</property>
</bean>

@Override
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response,
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;
}
}
protected ModelAndView getModelAndView(String viewName, Exception ex, HttpServletRequest request) {
return getModelAndView(viewName, ex);
}
protected ModelAndView getModelAndView(String viewName, Exception ex) {
ModelAndView mv = new ModelAndView(viewName);
if (this.exceptionAttribute != null) {
if (logger.isDebugEnabled()) {
logger.debug("Exposing Exception as model attribute '" + this.exceptionAttribute + "'");
}
mv.addObject(this.exceptionAttribute, ex);
}
return mv;
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· 面试官:你是如何进行SQL调优的?
2020-12-06 Java 面向对象(十五)类的成员 之 内部类
2020-12-06 Java 面向对象(十四):接口