SpringMVC 中针对异常问题有一套完整的处理体系,这套体系非常好用,今天松哥就花点时间来和大家聊一聊 SpringMVC 中的异常处理体系,我们把 SpringMVC 中的异常体系从头到尾梳理一下。

创新互联公司专注于南召企业网站建设,响应式网站,商城网站制作。南召网站建设公司,为南召等地区提供建站服务。全流程按需策划设计,专业设计,全程项目跟踪,创新互联公司专业和态度为您提供的服务
在 SpringMVC 的异常体系中,处于最顶层的大 Boss 是 HandlerExceptionResolver,这是一个接口,里边只有一个方法:
- public interface HandlerExceptionResolver {
 - @Nullable
 - ModelAndView resolveException(
 - HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);
 - }
 
resolveException 方法就用来解析请求处理过程中所产生的异常,并最终返回一个 ModelAndView。
我们来看下 HandlerExceptionResolver 的实现类:
直接实现 HandlerExceptionResolver 接口的类有三个:
在 SpringMVC 中,大致的异常解析器就是这些,接下来我们来逐个学习这些异常解析器。
AbstractHandlerExceptionResolver 是真正干活的异常解析器的父类,我们就先从他的 resolveException 方法开始看起。
- @Override
 - @Nullable
 - 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) {
 - logException(ex, request);
 - }
 - return result;
 - }
 - else {
 - return null;
 - }
 - }
 
记录异常日志没啥好说的,doResolveException 则是一个空的模版方法,所以这里对我们来说主要就是两个方法:shouldApplyTo 和 prepareResponse,我们分别来看。
- 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;
 - }
 - }
 - }
 - }
 - return !hasHandlerMappings();
 - }
 
这里涉及到了两个对象:mappedHandlers 和 mappedHandlerClasses:
我们在配置异常解析器的时候可以配置这两个对象,进而实现该异常处理器只为某一个处理器服务,但是一般来说没这种需求,所以大家仅做了解即可。
如果开发者一开始配置了 mappedHandlers 或者 mappedHandlerClasses,则用这两个和处理器去比较,否则就直接返回 true,表示支持该异常处理。
prepareResponse 方法比较简单,主要是处理一下响应头的缓存字段。
- protected void prepareResponse(Exception ex, HttpServletResponse response) {
 - if (this.preventResponseCaching) {
 - preventCaching(response);
 - }
 - }
 - protected void preventCaching(HttpServletResponse response) {
 - response.addHeader(HEADER_CACHE_CONTROL, "no-store");
 - }
 
这是 AbstractHandlerExceptionResolver 的大致内容,可以看到还是非常 easy 的,接下来我们来看它的实现类。
2.1 AbstractHandlerMethodExceptionResolver
AbstractHandlerMethodExceptionResolver 主要是重写了 shouldApplyTo 方法和 doResolveException 方法,一个一个来看。
- @Override
 - 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 if (hasGlobalExceptionHandlers() && hasHandlerMappings()) {
 - return super.shouldApplyTo(request, handler);
 - }
 - else {
 - return false;
 - }
 - }
 
这块感觉没啥好说的,判断逻辑基本上都还是调用父类的 shouldApplyTo 方法去处理。
doResolveException
- @Override
 - @Nullable
 - protected final ModelAndView doResolveException(
 - HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
 - HandlerMethod handlerMethod = (handler instanceof HandlerMethod ? (HandlerMethod) handler : null);
 - return doResolveHandlerMethodException(request, response, handlerMethod, ex);
 - }
 - @Nullable
 - protected abstract ModelAndView doResolveHandlerMethodException(
 - HttpServletRequest request, HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception ex);
 
doResolveException 是具体的异常处理方法,但是它里边却没有实质性操作,具体的事情交给 doResolveHandlerMethodException 方法去做了,而该方法是一个抽象方法,具体的实现在子类中。
2.1.1 ExceptionHandlerExceptionResolver
AbstractHandlerMethodExceptionResolver 只有一个子类就是 ExceptionHandlerExceptionResolver,来看下它的 doResolveHandlerMethodException 方法:
- @Override
 - @Nullable
 - protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
 - HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) {
 - ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);
 - if (exceptionHandlerMethod == null) {
 - return null;
 - }
 - 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();
 - ArrayList
 exceptions = new ArrayList<>(); - try {
 - if (logger.isDebugEnabled()) {
 - logger.debug("Using @ExceptionHandler " + exceptionHandlerMethod);
 - }
 - // Expose causes as provided arguments as well
 - Throwable exToExpose = exception;
 - while (exToExpose != null) {
 - exceptions.add(exToExpose);
 - Throwable cause = exToExpose.getCause();
 - exToExpose = (cause != exToExpose ? cause : null);
 - }
 - Object[] arguments = new Object[exceptions.size() + 1];
 - exceptions.toArray(arguments); // efficient arraycopy call in ArrayList
 - arguments[arguments.length - 1] = handlerMethod;
 - exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, arguments);
 - }
 - catch (Throwable invocationEx) {
 - // Any other than the original exception (or a cause) is unintended here,
 - // probably an accident (e.g. failed assertion or the like).
 - if (!exceptions.contains(invocationEx) && logger.isWarnEnabled()) {
 - logger.warn("Failure in @ExceptionHandler " + exceptionHandlerMethod, invocationEx);
 - }
 - // 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
 flashAttributes = ((RedirectAttributes) model).getFlashAttributes(); - RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
 - }
 - return mav;
 - }
 - }
 
这个方法虽然比较长,但是很好理解:
这就是 ExceptionHandlerExceptionResolver 的大致工作流程,可以看到,还是非常 easy 的。
2.2 DefaultHandlerExceptionResolver
这个看名字就知道是一个默认的异常处理器,用来处理一些常见的异常类型,我们来看一下它的 doResolveException 方法:
- @Override
 - @Nullable
 - 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);
 - }
 - }
 - catch (Exception handlerEx) {
 - }
 - return null;
 - }
 
可以看到,这里实际上就是根据不同的异常类型,然后调用不同的类去处理该异常。这里相关的处理都比较容易,以 HttpRequestMethodNotSupportedException 为例,异常处理就是对 response 对象做一些配置,如下:
- protected ModelAndView handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex,
 - HttpServletRequest request, HttpServletResponse response, @Nullable Object handler) throws IOException {
 - 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();
 - }
 
配置响应头,然后 sendError,最后返回一个空的 ModelAndView 对象。
其实这里哥哥异常处理方法都大同小异,松哥就不再赘述啦。
2.3 ResponseStatusExceptionResolver
这个用来处理 ResponseStatusException 类型的异常,或者使用了 @ResponseStatus 注解标记的普通异常类。我们来看下它的 doResolveException 方法:
- @Override
 - @Nullable
 - 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());
 - }
 - }
 - catch (Exception resolveEx) {
 - }
 - return null;
 - }
 
可以看到,首先判断异常类型是不是 ResponseStatusException,如果是,则直接调用 resolveResponseStatusException 方法进行异常信息处理,如果不是,则去查找到异常类上的 @ResponseStatus 注解,并从中查找出相关的异常信息,然后调用 resolveResponseStatus 方法进行处理。
可以看到,ResponseStatusExceptionResolver 处理的异常类型有两种:
这个比较简单,没啥好说的。
2.4 SimpleMappingExceptionResolver
SimpleMappingExceptionResolver 则是根据不同的异常显示不同的 error 页面。可能有的小伙伴还没用过 SimpleMappingExceptionResolver,所以松哥这里先简单说一下用法。
SimpleMappingExceptionResolver 的配置非常简单,直接提供一个 SimpleMappingExceptionResolver 的实例即可,如下:
- @Bean
 - SimpleMappingExceptionResolver simpleMappingExceptionResolver() {
 - SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver();
 - Properties mappings = new Properties();
 - mappings.put("java.lang.ArithmeticException", "11");
 - mappings.put("java.lang.NullPointerException", "22");
 - resolver.setExceptionMappings(mappings);
 - Properties statusCodes = new Properties();
 - statusCodes.put("11", "500");
 - statusCodes.put("22", "500");
 - resolver.setStatusCodes(statusCodes);
 - return resolver;
 - }
 
在 mappings 中配置异常和 view 之间的对应关系,要写异常类的全路径,后面的 11、22 则表示视图名称;statusCodes 中配置了视图和响应状态码之间的映射关系。配置完成后,如果我们的项目在运行时抛出了 ArithmeticException 异常,则会展示出 11 视图,如果我们的项目在运行时抛出了 NullPointerException 异常,则会展示出 22 视图。
这是用法,了解了用法之后我们再来看源码,就容易理解了,我们直接来看 doResolveException 方法:
- @Override
 - @Nullable
 - protected ModelAndView doResolveException(
 - HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
 - String viewName = determineViewName(ex, request);
 - if (viewName != null) {
 - Integer statusCode = determineStatusCode(request, viewName);
 - if (statusCode != null) {
 - applyStatusCodeIfPossible(request, response, statusCode);
 - }
 - return getModelAndView(viewName, ex, request);
 - }
 - else {
 - return null;
 - }
 - }
 
在上面这个过程中,有两个比较长的方法,松哥这里需要和大家额外多说两句。
这个就是根据异常类型找到视图名,我们来看下具体的查找方式:
- @Nullable
 - protected String determineViewName(Exception ex, HttpServletRequest request) {
 - String viewName = null;
 - if (this.excludedExceptions != null) {
 - for (Class> excludedEx : this.excludedExceptions) {
 - if (excludedEx.equals(ex.getClass())) {
 - return null;
 - }
 - }
 - }
 - if (this.exceptionMappings != null) {
 - viewName = findMatchingViewName(this.exceptionMappings, ex);
 - }
 - if (viewName == null && this.defaultErrorView != null) {
 - viewName = this.defaultErrorView;
 - }
 - return viewName;
 - }
 
- @Nullable
 - protected Integer determineStatusCode(HttpServletRequest request, String viewName) {
 - if (this.statusCodes.containsKey(viewName)) {
 - return this.statusCodes.get(viewName);
 - }
 - return this.defaultStatusCode;
 - }
 
这个就比较容易,直接去 statusCodes 中查看是否有视图对应的状态码,如果有则直接返回,如果没有,就返回一个默认的。
3.HandlerExceptionResolverComposite
最后,还有一个 HandlerExceptionResolverComposite 需要和大家介绍下,这是一个组合的异常处理器,用来代理哪些真正干活的异常处理器。
- @Override
 - @Nullable
 - public ModelAndView resolveException(
 - HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
 - if (this.resolvers != null) {
 - for (HandlerExceptionResolver handlerExceptionResolver : this.resolvers) {
 - ModelAndView mav = handlerExceptionResolver.resolveException(request, response, handler, ex);
 - if (mav != null) {
 - return mav;
 - }
 - }
 - }
 - return null;
 - }
 
它的 resolveException 方法就比较简单了,这种写法我们已经见到过很多次了,不再赘述。
好啦,今天就和大家简单聊一聊 SpringMVC 中的异常处理体系,整体来说并不难,小伙伴们可以仔细品一品。
本文转载自微信公众号「江南一点雨」,可以通过以下二维码关注。转载本文请联系江南一点雨公众号。
                当前名称:SpringMVC异常处理体系深入分析
                
                路径分享:http://www.csdahua.cn/qtweb/news7/504657.html
            
网站建设、网络推广公司-快上网,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 快上网