1.全局异常统一处理

SpringBoot中提供了@ControllerAdvice和@ExceptionHandler两个注解来实现专门对服务器500异常进行自定义处理。使用示例如下:

@ControllerAdvice注解表示我们定义的是一个控制器增强类,当其他任何控制器发生异常且异常类型符合@ExceptionHandler注解中指定的异常类时,原请求将会被拦截到这个我们自定义的控制器方法中。

在该方法中,我们可以拿到异常信息,于是便可以自定义该如何处理异常,是返回一个我们自定义的模版错误页面,还是返回JSON数据,这将都由我们根据实际应用场景而自己决定。并且我们还可以自定义异常类处理特殊情况。

另外,@ExceptionHandler注解只有一个value参数,为指定的异常类;@ControllerAdvice注解查看源码参数发现我们还可以指定需要拦截的控制器所在的包路径。

@ControllerAdvice
public class ExceptionController {

    @ExceptionHandler(Exception.class)
    @ResponseBody
    public Map globalException(HttpServletRequest request, Exception e) {
        Map<String,Object> map = new HashMap<>();
        map.put("code",500);
        map.put("message",e.getMessage());
        return map;
    }

    @ExceptionHandler(MyException.class)
    @ResponseBody
    public Map myException(HttpServletRequest request, Exception e) {
        Map<String,Object> map = new HashMap<>();
        map.put("code",500);
        map.put("message",e.getMessage());
        return map;
    }
}

2.自定义SpringBoot错误统一处理

SpringBoot默认错误处理机制中,完成任务处理的控制器实际上是SpringBoot在自动配置类中注入的BasicErrorController对象,该类继承AbstractErrorController,而AbstractErrorController又实现了ErrorController接口。

所以其实如果我们自定义一个BasicErrorController控制器,则Spring容器将不会再使用默认提供的BasicErrorController控制器,转而使用我们自定义的错误处理控制器。

2.1 继承AbstractErrorController类

自定义我们自己的BasicErrorController控制器,可以像默认的BasicErrorController一样直接继承AbstractErrorController,甚至可以直接照搬BasicErrorController的代码。

@Slf4j
@RestController
public class HttpErrorController extends AbstractErrorController {

    private final static String ERROR_PATH = "/error";

    public HttpErrorController(ErrorAttributes errorAttributes) {
        super(errorAttributes);
    }

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

    @RequestMapping(ERROR_PATH)
    public Map error(HttpServletRequest request, HttpServletResponse response){
        Map<String, Object> attributes = getErrorAttributes(request, true);
        //获取日志需要的请求url和详细堆栈错误信息
        String path = attributes.get("path").toString();
        String trace = attributes.get("trace").toString();
        log.error("path:{} trace:{}",path, trace);
        //获取错误时间、状态码和错误描述信息,返回给用户
        Date timestamp = (Date) attributes.get("timestamp");
        Integer status = (Integer) attributes.get("status");
        String error = attributes.get("error").toString();
        Map<String, Object> map = new HashMap<>();
        map.put("code",status);
        map.put("message",error);
        map.put("timestamp",timestamp);
        return map;
    }
}

2.2 实现ErrorController接口

可以直接实现ErrorController类来对默认BasicErrorController控制器进行替换。但是由于ErrorController接口只有一个过时了的方法,没有AbstractErrorController类提供的一些获取错误信息的方法,所以这种方式只能捕获到所有错误,但是不能获取错误的详细信息。
实现ErrorController接口需要实现getErrorPath()方法,返回的路径表示服务器将会重定向到该路径对应的控制器类。

@RestController
public class HttpErrorController implements ErrorController {

    private ErrorAttributes errorAttributes;

    private final static String ERROR_PATH = "/error";

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

    @RequestMapping(ERROR_PATH)
    public Map error(HttpServletRequest request, HttpServletResponse response){
        Map<String,Object> map = new HashMap<>();
        map.put("code","4xx");
        map.put("message","请求错误~");
        return map;
    }
}

一般推荐和上文第一种@ControllerAdvice+@ExceptionHandler注解的方式结合起来使用:

  • 这样@ControllerAdvice声明的增强控制器专门负责对服务器内部异常的500错误进行处理;
  • 而实现了ErrorController接口的这个错误处理控制器专门处理增强控制器不能捕获到的其他404等错误。

这两种方式一起使用并不会冲突,@ControllerAdvice声明的增强控制器会优先捕获异常,不能捕获的部分再由ErrorController接口的实现类处理即可。

Q.E.D.