• SpringMVC全局异常统一处理


    SpringMVC全局异常统一处理以及处理顺序
    最近在使用SpringMVC做全局异常统一处理的时候遇到的问题,就是想把ajax请求和普通的网页请求分开返回json错误信息或者跳转到错误页。

    在实际做的时候先按照标准的方式自定义一个HandlerExceptionResolver,命名为SpringHandlerExceptionResolver,实现HandlerExceptionResolver接口,重写resolveException方法,具体实现如下:

    import com.alibaba.fastjson.JSON;
    import com.alibaba.fastjson.JSONObject;
    import com.alibaba.fastjson.support.config.FastJsonConfig;
    import com.alibaba.fastjson.support.spring.FastJsonJsonView;
    import com.butioy.common.bean.JsonResult;
    import com.butioy.common.exception.BaseSystemException;
    import java.util.HashMap;
    import java.util.Map;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.ConversionNotSupportedException;
    import org.springframework.beans.TypeMismatchException;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.core.Ordered;
    import org.springframework.http.converter.HttpMessageNotReadableException;
    import org.springframework.http.converter.HttpMessageNotWritableException;
    import org.springframework.validation.BindException;
    import org.springframework.web.HttpMediaTypeNotAcceptableException;
    import org.springframework.web.HttpMediaTypeNotSupportedException;
    import org.springframework.web.HttpRequestMethodNotSupportedException;
    import org.springframework.web.bind.MethodArgumentNotValidException;
    import org.springframework.web.bind.MissingPathVariableException;
    import org.springframework.web.bind.MissingServletRequestParameterException;
    import org.springframework.web.bind.ServletRequestBindingException;
    import org.springframework.web.context.request.async.AsyncRequestTimeoutException;
    import org.springframework.web.multipart.support.MissingServletRequestPartException;
    import org.springframework.web.servlet.HandlerExceptionResolver;
    import org.springframework.web.servlet.ModelAndView;
    import org.springframework.web.servlet.NoHandlerFoundException;
    import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException;
    
    /**
     * <p>
     * spring MVC 统一异常处理
     * </p>
     *
     * @author butioy
     */
    public class SpringHandlerExceptionResolver implements HandlerExceptionResolver {
    
        private static Logger logger = LoggerFactory.getLogger(SpringHandlerExceptionResolver.class);
    
        private FastJsonConfig fastJsonConfig;
    
        @Autowired
        public SpringHandlerExceptionResolver(FastJsonConfig fastJsonConfig) {
            this.fastJsonConfig = fastJsonConfig;
        }
    
        @Override
        public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
            ModelAndView mv = specialExceptionResolve(ex, request);
            if (null == mv) {
                String message = "系统异常,请联系管理员";
                //BaseSystemException是我自定义的异常基类,继承自RuntimeException
                if (ex instanceof BaseSystemException) {
                    message = ex.getMessage();
                }
                mv = errorResult(message, "/error", request);
            }
            return mv;
        }
    
        /**
         * 这个方法是拷贝 {@link org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver#doResolveException},
         * 加入自定义处理,实现对400, 404, 405, 406, 415, 500(参数问题导致), 503的处理
         *
         * @param ex      异常信息
         * @param request 当前请求对象(用于判断当前请求是否为ajax请求)
         * @return 视图模型对象
         */
        private ModelAndView specialExceptionResolve(Exception ex, HttpServletRequest request) {
            try {
                if (ex instanceof NoSuchRequestHandlingMethodException 
                    || ex instanceof NoHandlerFoundException) {
                    return result(HttpExceptionEnum.NOT_FOUND_EXCEPTION, request);
                }
                else if (ex instanceof HttpRequestMethodNotSupportedException) {
                    return result(HttpExceptionEnum.NOT_SUPPORTED_METHOD_EXCEPTION, request);
                }
                else if (ex instanceof HttpMediaTypeNotSupportedException) {
                    return result(HttpExceptionEnum.NOT_SUPPORTED_MEDIA_TYPE_EXCEPTION, request);
                }
                else if (ex instanceof HttpMediaTypeNotAcceptableException) {
                    return result(HttpExceptionEnum.NOT_ACCEPTABLE_MEDIA_TYPE_EXCEPTION, request);
                }
                else if (ex instanceof MissingPathVariableException) {
                    return result(HttpExceptionEnum.NOT_SUPPORTED_METHOD_EXCEPTION, request);
                }
                else if (ex instanceof MissingServletRequestParameterException) {
                    return result(HttpExceptionEnum.MISSING_REQUEST_PARAMETER_EXCEPTION, request);
                }
                else if (ex instanceof ServletRequestBindingException) {
                    return result(HttpExceptionEnum.REQUEST_BINDING_EXCEPTION, request);
                }
                else if (ex instanceof ConversionNotSupportedException) {
                    return result(HttpExceptionEnum.NOT_SUPPORTED_CONVERSION_EXCEPTION, request);
                }
                else if (ex instanceof TypeMismatchException) {
                    return result(HttpExceptionEnum.TYPE_MISMATCH_EXCEPTION, request);
                }
                else if (ex instanceof HttpMessageNotReadableException) {
                    return result(HttpExceptionEnum.MESSAGE_NOT_READABLE_EXCEPTION, request);
                }
                else if (ex instanceof HttpMessageNotWritableException) {
                    return result(HttpExceptionEnum.MESSAGE_NOT_WRITABLE_EXCEPTION, request);
                }
                else if (ex instanceof MethodArgumentNotValidException) {
                    return result(HttpExceptionEnum.NOT_VALID_METHOD_ARGUMENT_EXCEPTION, request);
                }
                else if (ex instanceof MissingServletRequestPartException) {
                    return result(HttpExceptionEnum.MISSING_REQUEST_PART_EXCEPTION, request);
                }
                else if (ex instanceof BindException) {
                    return result(HttpExceptionEnum.BIND_EXCEPTION, request);
                }
                else if (ex instanceof AsyncRequestTimeoutException) {
                    return result(HttpExceptionEnum.ASYNC_REQUEST_TIMEOUT_EXCEPTION, request);
                }
            } catch (Exception handlerException) {
                logger.warn("Handling of [" + ex.getClass().getName() + "] resulted in Exception", handlerException);
            }
            return null;
        }
    
        /**
         * 判断是否ajax请求
         *
         * @param request 请求对象
         * @return true:ajax请求  false:非ajax请求
         */
        private boolean isAjax(HttpServletRequest request) {
            return "XMLHttpRequest".equalsIgnoreCase(request.getHeader("X-Requested-With"));
        }
    
        /**
         * 返回错误信息
         *
         * @param message 错误信息
         * @param url     错误页url
         * @param request 请求对象
         * @return 模型视图对象
         */
        private ModelAndView errorResult(String message, String url, HttpServletRequest request) {
            logger.warn("请求处理失败,请求url=[{}], 失败原因 : {}", request.getRequestURI(), message);
            if (isAjax(request)) {
                return jsonResult(500, message);
            } else {
                return normalResult(message, url);
            }
        }
    
        /**
         * 返回异常信息
         *
         * @param httpException 异常信息
         * @param request 请求对象
         * @return 模型视图对象
         */
        private ModelAndView result(HttpExceptionEnum httpException, HttpServletRequest request) {
            logger.warn("请求处理失败,请求url=[{}], 失败原因 : {}", request.getRequestURI(), httpException.getMessage());
            if (isAjax(request)) {
                return jsonResult(httpException.getCode(), httpException.getMessage());
            } else {
                return normalResult(httpException.getMessage(), "/error");
            }
        }
    
        /**
         * 返回错误页
         *
         * @param message 错误信息
         * @param url     错误页url
         * @return 模型视图对象
         */
        private ModelAndView normalResult(String message, String url) {
            Map<String, String> model = new HashMap<String, String>();
            model.put("errorMessage", message);
            return new ModelAndView(url, model);
        }
    
        /**
         * 返回错误数据
         *
         * @param message 错误信息
         * @return 模型视图对象
         */
        private ModelAndView jsonResult(int code, String message) {
            ModelAndView mv = new ModelAndView();
            FastJsonJsonView view = new FastJsonJsonView();
            view.setFastJsonConfig(fastJsonConfig);
            view.setAttributesMap((JSONObject) JSON.toJSON(JsonResult.fail(code, message)));
            mv.setView(view);
            return mv;
        }
    }

    写好之后,在springContext.xml配置文件中配置一下

    <bean class="com.butioy.common.handler.SpringHandlerExceptionResolver"/>
    然后启动tomcat,一切正常没有错误信息,但是在请求的时候并没有按照我的猜想返回自定义的错误页,而是tomcat默认的错误页

    于是我就debugger跟踪一下代码执行情况。终于让我给发现了在Spring的DispatcherServlet类的processHandlerException方法中有一个handlerExceptionResolvers集合,这里面存放着声明的异常处理bean。

    这时会发现这里面有3个异常处理bean是我们没有声明的,查了资料才知道原来这3个bean是SpringMVC默认初始化的,在spring-webmvc的jar包中,跟DispatcherServlet.java同一包下的DispatcherServlet.properties配置文件,配置文件内容如下:

    虽然有3个默认的bean, 但是为什么优先级就高于我自定义的呢?于是我点开这些类看了一下。发现这些了都间接实现了Spring的Ordered接口。这个接口只有一个方法getOrder(),这个方法返回一个int类型值,该值就是表明这个bean的执行的优先级。上面的统一异常处理实现类的注释中也有提过,是拷贝了DefaultHandlerExceptionResolver#doResolveException方法,这里就知道上面的错误信息已经被这个类处理类。在上面debugger跟踪时, 我们知道DefaultHandlerExceptionResolver实例的bean的执行顺序是优先于我们自定义的SpringHandlerExceptionResolver的bean,所以那些404,415等错误信息一致会被SpringMVC默认的异常处理bean处理。这跟我们的初衷不符。于是我把SpringHandlerExceptionResolver改造了一下,也实现Ordered接口:

    这里我默认给它一个最高优先级,这样就可以让自定义的类优先执行了。果然,结果跟我预期一样。

    这里是返回错误页面:


    这里是json返回数据(我这里使用的是jsonp请求方式):


    至此,整个SpringMVC全局异常处理就完成了。这里我只是把ajax请求都当成请求json数据,所以就这样做了。实际依情况而定。

    注意: 使用这个方法的时候切记不要在要在spring-mvc.xml中配置 <mvc:default-servlet-handler/>,应为这会使得配置的SpringHandlerExceptionResolver对404错误不起作用。在DispatcherServlet类中的doDispatch方法中我们会发现一行代码

    // Determine handler for the current request.
    mappedHandler = getHandler(processedRequest);
    if (mappedHandler == null || mappedHandler.getHandler() == null) {
      noHandlerFound(processedRequest, response);
      return;
    }

    这里的getHandler()会获取一个handler,如果没有与url对应handler的就会获取到
    <mvc:default-servlet-handler/>声明的一个
    org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler。
    这样就不会执行noHandlerFound方法,也就不会抛出404异常了。
    还有一点需要注意,就是在web.xml配置文件中,在配置DispatcherServlet的时候要加上

    <init-param>
        <!-- 如果未发现映射路径,抛出异常,而不是跳转到在web.xml配置的404错误页 -->
        <param-name>throwExceptionIfNoHandlerFound</param-name>
        <param-value>true</param-value>
    </init-param>

    因为如果不加上这个配置,在发生404的时候,就不会抛出异常,而是返回一个404状态的响应了 
    下面是DispatcherServlet.java的源码一部分:

    protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception {
      if (pageNotFoundLogger.isWarnEnabled()) {
          pageNotFoundLogger.warn("No mapping found for HTTP request with URI [" + getRequestUri(request) +
                  "] in DispatcherServlet with name '" + getServletName() + "'");
      }
      if (this.throwExceptionIfNoHandlerFound) {
          throw new NoHandlerFoundException(request.getMethod(), getRequestUri(request),
                    new ServletServerHttpRequest(request).getHeaders());
      }
      else {
          response.sendError(HttpServletResponse.SC_NOT_FOUND);
      }
    }

    在这里可以看到,如果throwExceptionIfNoHandlerFound为false,不会抛出异常,而是会给浏览器一个404状态的响应。而DispatcherServlet.java中这个属性的默认值就是false。

    原文链接:https://blog.csdn.net/butioy_org/article/details/78718405

  • 相关阅读:
    LINUX云计算40个相关问题
    sql
    补肾
    少吃食品
    抗癌食品
    爱情感言
    分布式事务的处理
    前端
    JAVA->Enum
    Sleuth+Zipkin+Log
  • 原文地址:https://www.cnblogs.com/muxi0407/p/11607708.html
Copyright © 2020-2023  润新知