Spring异常解析源码分析

微信公众号:java二师兄[Click]
关注回复java 1024G最新java学习资料(包含大数据资料)免费领;

异常处理重要性

  1. 良好的异常处理体系,便于对程序的后期维护
  2. 当发生错误时,程序不至于崩溃,提高程序健壮性
  3. 当发生错误时,可以在短时间内找定位问题所在
  4. 当发生错误时,避免异常栈裸奔,暴露底层架构,提高项目安全性

Spring统一异常方式

  • 使用 @ ExceptionHandler 注解(缺点:异常处理的方法必须与出错的方法在同一个Controller里面,不能全局处理)
1    // 需要捕捉的异常
2    @ExceptionHandler({ BizException.class })
3        // Value用于设置response的状态码,例如404,200等,reason用于响应,可以是内容语句。
4    @ResponseStatus(code=HttpStatus.BAD_REQUEST,reason="bad request")
5      // 可以返回Json也可以进行跳转
6      @ResponseBody
7    public ServerResponse<?> exception(BizException e) {
8        return ServerResponse.createByErrorMessage(e.getMessage());
9    }
  • 实现 HandlerExceptionResolver 接口
11 @Component  
2 public class GlobalExceptionResolver implements HandlerExceptionResolver{  
3
4     public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,  Exception ex) {  
5         //Object handler ----> HandlerMethod 也可能为null
6         // 可以通过handler进行特定处理
7        ..............
8     }  
9}   

《Spring异常解析源码分析》

 

  • 使用@ControllerAdvice+ @ ExceptionHandler注解
 1@ControllerAdvice
 2@ResponseBody
 3public class GlobalExceptionResolver {
 4    @ResponseStatus(HttpStatus.BAD_REQUEST)
 5    // 可以不指定特定的异常即默认拦截所有异常
 6    @ExceptionHandler(HttpMessageNotReadableException.class)
 7    public ServiceResponse handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
 8        return  ServerResponse.createByErrorMessage(e.getMessage())
 9    }
10 // 其他代码省略
11}

SpringMVC异常处理源码剖析

  • 从DispatcherServlet的doDispatch方法入手
 1protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
 2        try {
 3            //省略请求处理代码部分
 4            }catch (Exception ex) {
 5                dispatchException = ex;
 6            }
 7           // 捕捉异常后调用processDispatchResult方法
 8            processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
 9        }
10        finally {
11
12        }
13    }
  • processDispatchResult如何处理呢
 1private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
 2            HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {
 3      // 先判断异常是否为null    
 4      if (exception != null) {
 5            if (exception instanceof ModelAndViewDefiningException) {
 6                logger.debug("ModelAndViewDefiningException encountered", exception);
 7                mv = ((ModelAndViewDefiningException) exception).getModelAndView();
 8            }else {
 9                Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
10                mv = processHandlerException(request, response, handler, exception);
11                errorView = (mv != null);
12            }
13        }
14    }
  • processHandlerException核心代码
 1protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
 2            Object handler, Exception ex) throws Exception {
 3                // 省略了不关系部分(异常解析器排序)
 4        ModelAndView exMv = null;
 5        for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) {
 6                         // 调用resolveException方法
 7                         // Spring 自带的异常处理器暂时不讲本质上和我们自定义差不多
 8            exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);
 9            if (exMv != null) {
10                               // 只要获取ModeAndView终止处理     
11                break;
12            }
13        }
14                 // 如果没获得直接抛出异常
15        throw ex;
16    }
  • Spring自带异常解析器主要接口和实现类《Spring异常解析源码分析》
    • AbstractHandlerMethodExceptionResolver和ExceptionHandlerExceptionResolver负责解析@ExceptionHandle
    • ResponseStatusExceptionResolver解析@ResponseStatus
    • DefaultHandlerExceptionResolver按照不同的类型分别对异常进行解析
    • SimpleMappingExceptionResolver: 通过配置的异常类和view的对应关系来解析异常

拦截404

  上面介绍的方法并不能拦截404,为什么要拦截404呢?首先为了产品的安全,不随便暴露后台所用的中间件,避免黑客利用中间件本身的漏洞攻击网站,另外也可以项目与用户有良好的交互。

  • 利用Spring MVC的最精确匹配原则(@requestMapping(“*)拦截的这个方法返回一个自定义的404界面)

  • 利用web容器提供的error-page

1<error-page>
2    // 也可以拦截其他错误码比如500
3    <error-code>404</error-code>
4    // 确保resource目录不被spring拦截
5    <location>/resource/view/404.htm</location>
6  </error-page>
  • 重写DispatcherServlet的noHandlerFound方法
 1protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception{
 2        if(pageNotFoundLogger.isWarnEnabled())
 3            pageNotFoundLogger.warn((new StringBuilder()).append("No mapping found for HTTP request with URI [").append(getRequestUri(request)).append("] in DispatcherServlet with name '").append(getServletName()).append("'").toString());
 4        if(throwExceptionIfNoHandlerFound){
 5            throw new NoHandlerFoundException(request.getMethod(), getRequestUri(request), (new ServletServerHttpRequest(request)).getHeaders());
 6        } else{
 7            //response.sendError(404);
 8            // return;
 9            // 修改为
10            // 定义一个请求路径为404的Controller方法
11            response.sendRedirect(request.getContextPath() + "/404"); 
12        }
13    }

项目中异常处理方案

 1@Component
 2public class GlobalExceptionResolver implements HandlerExceptionResolver {
 3
 4    public static final String AJAX="X-Requested-With";
 5
 6    public ModelAndView resolveException(HttpServletRequest request,
 7            HttpServletResponse response, Object handler, Exception ex) {
 8     // 区分是否是ajax请求  
 9         boolean isAjax = isAjax(request);
10         return handleException(request,response,handler,ex,isAjax);
11    }
12    /**
13     * 判断当前请求是否为异步请求.
14     * @param request
15     * @param response
16     * @return boolean
17     * @author zhaoxin
18     * @date 2018年9月18日 上午10:59:35
19     */
20    private boolean isAjax(HttpServletRequest request){
21        return StrUtil.isNotBlank(request.getHeader(AJAX));
22    }
23    /**
24     * 处理异常
25     * @return ModelAndView
26     * @author zhaoxin
27     * @date 2018年9月18日 上午11:52:40
28     */
29    private ModelAndView handleException(HttpServletRequest request,
30            HttpServletResponse response, Object handler,
31            Throwable ex, boolean isajax) {
32        //异常信息记录到日志文件中
33        LogUtil.logError("Capture global exceptions:"+ex.getMessage());
34        LogUtil.logException(ex);
35        //分普通请求和ajax请求分别处理
36        if(isajax){
37            return handleAjax(request,response,handler,ex);
38        }else{
39            return handlerNotAjax(request,response,handler,ex);
40        }
41    }
42
43    /**
44     * ajax异常处理并返回.
45     * @param request
46     * @param response
47     * @param handler
48     * @param initialEx
49     */
50    private ModelAndView handleAjax(HttpServletRequest request,
51            HttpServletResponse response, Object handler,Throwable initialEx){
52        response.setHeader("Cache-Control", "no-store");
53                // 返回JsonView
54        ModelAndView mav = new ModelAndView(new FastJsonJsonView());
55        mav.addObject("msg", "System exceptions please check logs");
56        mav.addObject("status", Const.RespondeCode.ERROR.getCode());
57        mav.addObject("success",false);
58        return mav;
59    }
60    /**
61     * 普通页面异常处理并返回.
62     * @param request
63     * @param response
64     * @param handler
65     * @param deepestException
66     * @return
67     */
68    private ModelAndView handlerNotAjax(HttpServletRequest request,HttpServletResponse response, Object handler, Throwable ex) {
69        Map<String, Object> model = new HashMap<>();
70        model.put("message", "System exceptions please check logs");
71        model.put("ex", ex);
72        return new ModelAndView("common/error500", model);
73    }
74
75
76}

求关注

《Spring异常解析源码分析》

 

 

    原文作者:Spring Boot
    原文地址: https://blog.csdn.net/zxx007zxx/article/details/84501640
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞