SpringBoot异常处理

异常的处理方式有多种:

  • 自定义错误页面
  • @ExceptionHandler注解
  • @ControllerAdvice+@ExceptionHandler注解
  • 配置SimpleMappingExceptionResolver处理异常
  • 自定义 HandlerExceptionResolver 类处理异常

一、自定义错误页面

SpringBoot 默认的处理异常的机制:SpringBoot 默认的已经提供了一套处理异常的机制。一旦程序中出现了异常,SpringBoot 会向/error 的 url 发送请求。在SpringBoot 中提供了一个叫 BasicExceptionController 来处理/error 请求,然后跳转到默认显示异常的页面来展示异常信息。

如果我们需要将所有的异常同一跳转到自定义的错误页面,需要在src/main/resources/templates(使用模板引擎时采用)或WEB-INF/jsp(与application.properties配置的视图映射一致)下创建自己的异常页面,名称必须是error。

(1) application.properties全局配置

#jsp视图映射配置
spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp

(2) WEB-INF/jsp/error.jsp内容

<%@ page language="java" contentType="text/html; charset=utf-8"
    pageEncoding="utf-8"%>
<%@ page isErrorPage="true"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>error页面</title>
</head>
<body>
    <h5>出错了,联系管理员......</h5>
    <p><%=exception.getMessage()%></p>
</body>
</html>

(3) 编写controller

@RestController
public class DemoController {
    
    @RequestMapping("/showPage1")
    public Object showPage(){
        ModelAndView view = new ModelAndView();
        String demo = null;
        demo.toString(); //模拟异常
        view.setViewName("error");
        return view;
    }

}

二、@ExceptionHandler注解

将指定的异常交由具体的方法处理,显示特定的页面。

1. 由@ExceptionHandler注解指定特定异常的处理

@RestController
public class DemoController {
    /**
     * 模拟java.lang.NullPointerException异常
     */
    @RequestMapping("/showPage1")
    public Object showPage() {
        ModelAndView view = new ModelAndView();
        String demo = null;
        demo.toString();
        view.setViewName("index");
        return view;
    }
    
    /**
     * java.lang.NullPointerException 处理空指针异常
     * 该方法需要返回一个 ModelAndView:目的是可以让我们封装异常信息以及视 图的指定
     * 参数 Exception e:会将产生异常对象注入到方法中
     */
    @ExceptionHandler(value = { java.lang.NullPointerException.class })
    public ModelAndView nullPointerExceptionHandler(Exception e) {
        ModelAndView mv = new ModelAndView();
        mv.addObject("error", e.toString());
        mv.setViewName("error2");
        return mv;
    }
}

2. 编写error2.jsp

<%@ page language="java" contentType="text/html; charset=utf-8"
    pageEncoding="utf-8"%>
<%@ page isErrorPage="true"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>error页面</title>
</head>
<body>
    <h5>出错了,联系管理员......@ExceptionHandler</h5>
    <p>${error }</p>
</body>
</html>

三、@ControllerAdvice+@ExceptionHandler注解

仅使用@ExceptionHandler处理异常,需要主方法与异常处理方法在同一个Controller,这样异常处理的方法不能被其他Controller使用,有代码冗余。

使用@ControllerAdvice就可以解决这个问题。

1. 模拟异常的主方法

@RestController
public class DemoController {

    /**
     * 模拟java.lang.NullPointerException异常
     */
    @RequestMapping("/showPage1")
    public Object showPage() {
        ModelAndView view = new ModelAndView();
        String demo = null;
        demo.toString();
        view.setViewName("index");
        return view;
    }
    
}

2. 创建异常处理的Controller

/**
 * @RestControllerAdvice或@ControllerAdvice
 */
@RestControllerAdvice
public class ExceptionController {
    /**
     * java.lang.NullPointerException 处理空指针异常
     * 该方法需要返回一个 ModelAndView:目的是可以让我们封装异常信息以及视 图的指定
     * 参数 Exception e:会将产生异常对象注入到方法中
     */
    @ExceptionHandler(value = { java.lang.NullPointerException.class })
    public ModelAndView nullPointerExceptionHandler(Exception e) {
        ModelAndView mv = new ModelAndView();
        mv.addObject("error", e.toString());
        mv.setViewName("error3");
        return mv;
    }
}

3. 编写jsp

<%@ page language="java" contentType="text/html; charset=utf-8"
    pageEncoding="utf-8"%>
<%@ page isErrorPage="true"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>error页面</title>
</head>
<body>
    <h5>出错了,联系管理员......@ControllerAdvice</h5>
    <p>${error }</p>
</body>
</html>

四、配置SimpleMappingExceptionResolver处理异常

这种方法只能是对异常和视图进行关联,无法传递错误信息

//声明成一个配置类
@Configuration
public class GlobalException {
    /**
     * 该方法必须要有返回值。返回值类型必须是: SimpleMappingExceptionResolver 
     **/
    @Bean
    public SimpleMappingExceptionResolver getSimpleMappingExceptionResolver() {
        SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver();
        Properties mappings = new Properties();
        /**
         * 参数一:异常的类型,注意必须是异常类型的全名 
         * 参数二:视图名称 
         **/
        mappings.put("java.lang.ArithmeticException", "error1");
        mappings.put("java.lang.NullPointerException", "error4");
        // 设置异常与视图映射信息的
        resolver.setExceptionMappings(mappings);
        return resolver;
    }
}

五、自定义 HandlerExceptionResolver 类处理异常

创建一个实现现 HandlerExceptionResolver 接口的全局异常处理类

//声明成一个配置类
@Configuration
public class GlobalException implements HandlerExceptionResolver{

    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,
            Exception ex) {
        ModelAndView view = new ModelAndView();
        //判断不同异常类型,做不同视图跳转
        if(ex instanceof NullPointerException){ 
            view.setViewName("error2");
            view.addObject("error", ex.toString());
        }
        return view;
    }
    
}

六、以JSON字符返回的方式统一处理异常

前面几种异常处理都是返回到相应的错误页面,平常我们用到的大部分还是用JSON字符串返回(ajax提交的方式)。

第一种:使用@ControllerAdvice和@ExceptionHandler注解

第二种: 使用ErrorController类来实现。

以JSON格式返回统一对象:

/**
 * 响应对象
 * @param <T>
 */
public class ResponseVo<T> implements Serializable {
    private static final long serialVersionUID = 1L;
    
    public static final int RESULT_FAIL = 0;
    public static final int RESULT_SUCCESS = 1;

    /**
     * 状态码.
     */
    private Integer code;

    /**
     * 提示信息.
     */
    private String msg;

    /**
     * 具体的数据.
     */
    private T data;

    public ResponseVo() {
        
    }


    public ResponseVo(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public ResponseVo(Integer code, String msg, T data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }


    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    @Override
    public String toString() {
        return "ResponseVo{" +
                "code=" + code +
                ", msg='" + msg + '\'' +
                ", data=" + data +
                '}';
    }
}

1. 使用@RestControllerAdvice和@ExceptionHandler注解

/**
 * @RestControllerAdvice 方法的返回值以字符串返回,
 * 等同于@ControllerAdvice + 方法上添加@ResponseBody
 */
@RestControllerAdvice
public class GlobalExceptionHandler {
    /**
     * java.lang.NullPointerException 处理空指针异常
     * 该方法需要返回一个 ModelAndView:目的是可以让我们封装异常信息以及视 图的指定
     * 参数 Exception e:会将产生异常对象注入到方法中
     */
    @ExceptionHandler(value = { java.lang.NullPointerException.class })
    public ResponseVo<String> nullPointerExceptionHandler(HttpServletResponse response, NullPointerException  ex) {
        //这里最好也是判断下ex.getMessage()的信息是否为空(或"null"),如果是这样的话,使用ex.toString()将完整的错误信息展示出来
        ResponseVo<String> responseVo = new ResponseVo<String>(ResponseVo.RESULT_FAIL, ex.getMessage());
        return responseVo;
    }
}

注解@RestControllerAdvice表示这是一个控制器增强类,当控制器发生异常且符合类中定义的拦截异常类,将会被拦截。可以使用basePackages属性定义拦截的控制器所在的包路径。

2. 使用ErrorController类来实现。

SpringBoot默认的错误处理类为BasicErrorController,这里编写一个自己的错误处理类,那么默认的处理类将不会起作用。

其中getErrorPath()返回的路径服务器将会重定向到该路径对应的处理类,本例中为error方法。

/**
 * @RestController直接以字符串的形式返回
 * 等同于@Controller + 方法上添加@ResponseBody
 */
@RestController
public class HttpErrorController implements ErrorController {

    private final static String ERROR_PATH = "/error";

    @RequestMapping(path = ERROR_PATH)
    public ResponseVo<String> error(HttpServletRequest request, HttpServletResponse response) {
        ResponseVo<String> result = new ResponseVo<String>(ResponseVo.RESULT_FAIL, "HttpErrorController error:" + response.getStatus());
        return result;
    }

    @Override
    public String getErrorPath() {
        return ERROR_PATH;
    }
}

3. 测试

@RestController
public class DemoController {

    @RequestMapping("/showPage")
    public Object showPage(){
         throw new NullPointerException("DemoController have exception");
    }
}

(1) 发出一个错误的请求,也就是没有对应的处理类。从返回可以看到是由HttpErrorController类处理

{"code":0,"msg":"HttpErrorController error:404","data":null}

(2) 发出一个正常的请求(DemoController的showPage()处理),处理类中抛出空异样

{"code":0,"msg":"TestController have exception","data":null}

4. 两者的区别

(1) 注解@RestControllerAdvice方式只能处理控制器抛出的异常。此时请求已经进入控制器中。

(2) ErrorController方式可以处理所有的异常,包括未进入控制器的错误,比如404,401等错误

(3) 如果应用中两者共同存在,则@RestControllerAdvice方式处理控制器抛出的异常,类ErrorController方式未进入控制器的异常。

(4) @RestControllerAdvice方式可以定义多个拦截方法,拦截不同的异常类,并且可以获取抛出的异常信息,自由度更大。

5. 总结

(1) 因为返回的是JSON字符串,所以可以使用@RestXXXX格式或@XXXX+@ResponseBody的组合

(2) ErrorController的处理范围比第一种处理方式要大,如果两种异常处理的方式在一个web应用同时存在,需要确保第一种处理方式是返回JSON字符串,否则可能会被ErrorController再一次处理。

posted @ 2019-09-15 13:23  codedot  阅读(481)  评论(0编辑  收藏  举报