Spring MVC 异常处理

统一处理某一类异常,能够减少代码的重复度和复杂度,有利于代码的维护。

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());
    }
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值