统一处理某一类异常,能够减少代码的重复度和复杂度,有利于代码的维护。
Spring 统一异常处理有 4 种方式,分别为:
-  使用 @ ExceptionHandler 注解 
-  实现 HandlerExceptionResolver 接口 
-  使用 @controlleradvice 注解 
-  使用 @Restcontrolleradvice注解 
1.1 使用 @ ExceptionHandler 注解
使用@ExceptionHandler注解作用在方法上面,参数是具体的异常类型。
一旦系统抛出这种类型的异常时,会引导到该方法来处理。
但是它的缺陷很明显,处理异常的方法和出错的方法(或者异常最终抛出来的地方)必须在同一个controller,不能全局控制。
 @GetMapping("/gooderr")
    public Good test3(Good good){
        int i=5/0;
        return good;
    }
//如果不加这个 就会报500页面错误
@ExceptionHandler(Exception.class)
public ModelAndView myException(Exception e) {
    ModelAndView error = new ModelAndView("error");
    error.addObject("error", e.getMessage());
    return error;
}编写一个error.jsp文件
<%@page pageEncoding="UTF-8" language="java" contentType="text/html;UTF-8" %>
<html>
<body>
<h1>这是错误页面</h1>
<p>${error}</p>
</body>
</html>1.2 实现 HandlerExceptionResolver 接口
springmvc提供一个HandlerExceptionResolver接口,自定义全局异常处理器必须要实现这个接口,如下:
创建一个包:
handlerexr包下创建
@Component
public class HandlerResolver implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(
            HttpServletRequest httpServletRequest,
            HttpServletResponse httpServletResponse,
                             Object o, Exception e) {
        ModelAndView mv=new ModelAndView();
        mv.addObject("msg",e.getMessage());
        mv.setViewName("error");
        return mv;
    }
}测试运行:http://localhost:8080/gooderr
分类异常处理:创建三个异常页面 复制三个 分别是空指针,Arithmetic计算异常,全局异常
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <h2>测试成功,欢迎来到我的世界</h2>
    <p>空指针异常:${msg}</p>
    <img src="../img/q1.jpg">
</body>
</html>先验证是否正确:http://localhost:8080/gooderr
异常验证controller:
@GetMapping("/gooderr")
    @ResponseBody
    public Good test2(Good good) throws BizException {
        if(good.getNum()==1){
            int i=5/0;
        }else if (good.getNum()==2){
            String name=null;
            name.equals("abc");
        }
        return good;
    }访问地址:http://localhost:8080/gooderr?num=1 计算异常
http://localhost:8080/gooderr?num=2 空指针异常
内容输出:
@Component
public class HandlerResolver implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(
            HttpServletRequest httpServletRequest,
            HttpServletResponse httpServletResponse,
                             Object o, Exception e) {
        ModelAndView mv=new ModelAndView();
        if(e instanceof ArithmeticException){
            mv.setViewName("Arithmetic");
           mv.addObject("msg",e.getMessage());
        }
        if(e instanceof NullPointerException){
            mv.setViewName("nullpoint");
        }
        mv.addObject("msg",e.toString());
/*
        mv.addObject("msg",e.getMessage());
        mv.setViewName("error");*/
        return mv;
    }
}直接测试
1.3 使用 @ControllerAdvice+ @ ExceptionHandler 注解 (重点)
@ExceptionHandler 可以返回 ModelAndView 定制异常视图。
@ControllerAdvice 是一个增强的 Controller,@ExceptionHandler 可以拦截特定的异常,因此可以更精确的配置异常处理逻辑。
创建一个类advice中: MyExceptionAdvice
@ControllerAdvice
public class MyExceptionAdvice {
    @ExceptionHandler(NullPointerException.class)
    public ModelAndView processException(NullPointerException ex){
        ModelAndView mv = new ModelAndView("nullpoint");
        mv.addObject("msg",ex.toString());
        return mv;
    }
    @ExceptionHandler(ArithmeticException.class)
    public ModelAndView processException2(ArithmeticException ex){
        ModelAndView mv = new ModelAndView("arith");
        mv.addObject("msg",ex.toString());
        return mv;
    }
}测试运行:
简写方式:
@ExceptionHandler(Exception.class)
    public ModelAndView processException(Exception e){
      
        ModelAndView mv=new ModelAndView();
        if(e instanceof ArithmeticException){
            mv.setViewName("arith");
            mv.addObject("msg",e.getMessage());
        }
        if(e instanceof NullPointerException){
            mv.setViewName("nullpoint");
             mv.addObject("msg",e.toString());
        }
        // mv.addObject("msg",e.toString());
       
        return mv;
    }但是使用第一种方式还是比较清晰的:
自定义异常类:自己编写一个异常类:
public class BizException extends Exception{
    private int code;
    public BizException(String msg){
        super(msg);
    }
    public BizException(int code,String msg){
        super(msg);
        this.code=code;
    }
    public int getCode() {
        return code;
    }
    public void setCode(int code) {
        this.code = code;
    }
}在MyExceptionAdvice中加一个方法:
 @ExceptionHandler(BizException.class)
    public ModelAndView processException2(BizException e){
        ModelAndView mv=new ModelAndView();
        mv.addObject("msg",e.getCode()+"-->"+e.toString());
        mv.setViewName("biz");
        return mv;
    }创建一个Biz的异常jsp页面:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <h2>测试成功,欢迎来到我的世界</h2>
    <p>自己定义的异常:${msg}</p>
</body>
</html>controller中修改:
 @GetMapping("/gooderr2")
    @ResponseBody
    public Good test3(Good good) throws BizException {
        if(good.getNum()==1){
            int i=5/0;
        }else if (good.getNum()==2){
            String name=null;
            name.equals("abc");
        }else if(good.getNum()==3){
            throw  new BizException(301,"我看错你了");
        }else if(good.getNum()==4){
            throw  new BizException(401,"我看错你了");
        }
        return good;
    }1.4 针对RestController 统一处理
@RestControllerAdvice
public class MyRestContollerAvice {
    @ExceptionHandler( Exception.class)
    public Object   error(Exception ex){
        Map map = new HashMap();
        map.put("code",1);
        map.put("message",ex.getMessage());
        return  map;
    }
}1.5 扩展 统一返回值
创建统一返回值类:
@Data
public class ResponseDTO {
    private int code;
    private String message;
    private Object data;
    public static ResponseDTO success(Object data){
        ResponseDTO dto=new ResponseDTO();
        dto.setData(data);
        return dto;
    }
    public static ResponseDTO error(int code,String msg){
        ResponseDTO dto=new ResponseDTO();
        dto.setCode(code);
        dto.setMessage(msg);
        return dto;
    }
}统一异常处理
@RestControllerAdvice
@Slf4j
public class MyResponseAdvice implements ResponseBodyAdvice<Object> {
    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        return true;
    }
    
    @ExceptionHandler
    public Object processException(Exception ex){
        ex.printStackTrace();
        if(ex instanceof BizException){
            return   ResponseDTO.error(((BizException)ex).getCode(),ex.getMessage());
        }
        //其它异常
      return   ResponseDTO.error(999,ex.getMessage());
    }
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        Object responseDTO = null;
         responseDTO =  ResponseDTO.success(body);
        
         //针对String返回值,我们做了下特殊处理,因为我们硬生生的给人家套了个外壳,StringHttpMessageConverter无能为力,所以我们自己把它转换成string就可以了
        if (selectedConverterType == StringHttpMessageConverter.class){
          return JSONUtil.toJsonStr(responseDTO);
        }
        return responseDTO;
    }
}整体代码:
package com.by.advice;
@RestControllerAdvice
public class MyRestControllerAdvice  implements ResponseBodyAdvice<Object>{
    @Override
    public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
        return true;
    }
    /**
     * beforeBodyWrite 在controller返回之前
     *      执行MethodParameter 获取方法参数中的类型
     * @param body 响应体内容
     * @param methodParameter 获取方法参数中的类型
     * @param mediaType 媒体类型:决定浏览器将以什么形式、什么编码对资源进行解析
     * @param selectedConverter 报文信息转换器。
     */
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType,
                                  Class<? extends HttpMessageConverter<?>> selectedConverter,
                                  ServerHttpRequest serverHttpRequest,
                                  ServerHttpResponse serverHttpResponse) {
        if(body instanceof ResponseDTO){
            return body;
        }
        ResponseDTO success = ResponseDTO.success(body);
        //针对String返回值 我们做特殊的处理
        if(selectedConverter== StringHttpMessageConverter.class){
            return JSONUtil.toJsonStr(success);
        }else {
            return success;
        }
    }
   /* @ExceptionHandler(Exception.class)
    public Object error( Exception e){
        ResponseDTO dto=new ResponseDTO();
        if(e instanceof BizException){
            BizException biz= (BizException) e;
            dto.setCode(biz.getCode());
        }else{
            dto.setCode(666);
        }
        dto.setMessage(e.getMessage());
        return dto;
    }*/
   /* @ExceptionHandler(BindException.class)
    public String processException(BindException e){
        BindingResult result = e.getBindingResult();
        StringBuilder sb=new StringBuilder();
        List<ObjectError> allError=result.getAllErrors();
        for (ObjectError error : allError) {
            sb.append(error.getDefaultMessage());
        }
        return sb.toString();
    }*/
    @ExceptionHandler(Exception.class)
    public Object error( Exception e){
        int code=666;
        if(e instanceof BizException){
            BizException biz= (BizException) e;
            code=biz.getCode();
        }
        if(e instanceof MethodArgumentNotValidException){
            StringBuilder sb = new StringBuilder();
            List<ObjectError> allErrors = ((MethodArgumentNotValidException) e).getBindingResult().getAllErrors();
            for(ObjectError error : allErrors){
                sb.append(error.getDefaultMessage());
            }
            return   ResponseDTO.error(888,sb.toString());
        }
        return   ResponseDTO.error(code,e.getMessage());
    }
}









