02月08, 2021

7、SpringBoot 异常处理

SpringBoot 异常处理

一、自定义错误页面

SpringBoot默认的已经提供了一套处理异常的机制,一旦程序中出现异常,SpringBoot会向/error的url发送请求,在SpringBoot中提供了一个叫BasicErrorController来处理/error请求(注意:以前的版本是BasicExceptionController),在这个类中,就可以很明显的看到,传递的文件位置,以及传递了一个叫error的参数

BasicErrorController

@Controller
@RequestMapping({"${server.error.path:${error.path:/error}}"})
public class BasicErrorController extends AbstractErrorController {
    private final ErrorProperties errorProperties;

    public BasicErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties) {
        this(errorAttributes, errorProperties, Collections.emptyList());
    }

    public BasicErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties, List<ErrorViewResolver> errorViewResolvers) {
        super(errorAttributes, errorViewResolvers);
        Assert.notNull(errorProperties, "ErrorProperties must not be null");
        this.errorProperties = errorProperties;
    }

    public String getErrorPath() {
        return null;
    }

    @RequestMapping(
        produces = {"text/html"}
    )
    public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
        HttpStatus status = this.getStatus(request);
        Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
        response.setStatus(status.value());
        ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
        return modelAndView != null ? modelAndView : new ModelAndView("error", model);
    }

因此,我们想定义一个简单的错误页面,就很轻松了,在resources/templates目录下建立一个叫error.html的文件就可以了,如下:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<span th:text="${error}"></span>
</body>
</html>

二、@ExceptionHandler

我们完全可以在出现异常的Controller中自定义注入

@GetMapping("/test")
public String testException(){
    String str = null;
    str.equals("aaa");
    return "register";
}

@ExceptionHandler(value = {java.lang.NullPointerException.class})
public ModelAndView handleNullPointerException(Exception e){
    ModelAndView mv = new ModelAndView();
    mv.addObject("error",e.toString());
    mv.setViewName("error");
    System.out.println("e = " + e);
    return mv;
}

三、全局异常处理

上面的做法需要在每个Controller类中加入异常注入,肯定是不可取的,对于异常的处理,当然是最好全局都能用最好,SpringBoot为我们提供了@ControllerAdvice来解决这个问题,其实就是将之前写在单独Controller里面的ExceptionHandler集中移入到一个类中进行处理

@ControllerAdvice
public class GlobalException {
    // 空指针异常
    @ExceptionHandler(value = {java.lang.NullPointerException.class})
    public ModelAndView handleNullPointerException(Exception e){
        ModelAndView mv = new ModelAndView();
        mv.addObject("error",e.toString());
        mv.setViewName("error");
        System.out.println("e = " + e);
        return mv;
    }

    // 算术异常
    @ExceptionHandler(value = {java.lang.ArithmeticException.class})
    public ModelAndView handleArithmeticException(Exception e){
        ModelAndView mv = new ModelAndView();
        mv.addObject("error",e.toString());
        mv.setViewName("error");
        System.out.println("e = " + e);
        return mv;
    }
}

四、全局异常类的统一处理

很明显上面的类,如果要处理很多种异常,就需要建很多方法,这样实在太费时费力,可以使用SimpleMappingExceptionResovler来进行统一处理

@Configuration
public class GlobalException {
    @Bean
    public SimpleMappingExceptionResolver getSimpleMappingExceptionResolver(){
        SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver();
        Properties properties = new Properties();
        properties.setProperty("java.lang.ArithmeticException","error");
        properties.setProperty("java.lang.NullPointerException","error");
        resolver.setExceptionMappings(properties);
        //设置默认异常页面
        resolver.setDefaultErrorView("error");
        //设置默认异常对象
        resolver.setExceptionAttribute("error");
        return resolver;
    }
}

五、自定义HandlerExceptionResolver类处理异常

@Configuration
public class GlobalException implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
        ModelAndView mv = new ModelAndView();
        //可以设置不同的异常跳转不同的页面,当我这里处理的都是一个页面直接设置一个异常页面就可以了
        mv.setViewName("error");
//        if (e instanceof ArithmeticException){
//            mv.setViewName("error");
//        }
//        else if(e instanceof ArithmeticException){
//            mv.setViewName("error");
//        }
        mv.addObject("error",e);
        return mv;
    }
}

六、自定义异常处理

为了更加适用前后端分离的应用设置,将异常处理和之前的返回前端ResultVO的信息再进行了一下封装

统一信息接口

public interface BaseInfoInterface {
    Integer status();
    String message();
}

返回信息枚举

public enum ResultCode implements BaseInfoInterface {
    SUCCESS(1,"成功"),
    // 常见错误
    BODY_NOT_MATCH(400,"请求的数据格式不符!"),
    SIGNATURE_NOT_MATCH(401,"请求的数字签名不匹配!"),
    NOT_FOUND(404, "未找到该资源!"),
    INTERNAL_SERVER_ERROR(500, "服务器内部错误!"),
    SERVER_BUSY(503,"服务器正忙,请稍后再试!"),
    // 参数错误1001-1999
    PARAM_IS_INVALID(1001,"参数无效"),
    PARAM_IS_BLANK(1002,"参数为空"),
    PARAM_TYPE_BAND_ERROR(1003,"参数类型错误"),
    PARAM_NOT_COMPLETE(1004,"参数缺失"),
    // 用户错误2001-2999
    USER_NOT_LOGGED_IN(2001,"用户未登录"),
    USER_LOGIN_ERROR(2002,"账户不存在或密码错误"),
    USER_ACCOUNT_FORBIDDEN(2003,"账户已被禁用"),
    USER_NOT_EXIST(2004,"用户不存在"),
    USER_HAS_EXISTED(2005,"用户已存在"),
    USER_PASS_ERROR(2006,"密码错误"),
    USER_AUTHENTICATION_ERROR(2007,"认证失败"),
    USER_AUTHORIZATION_ERROR(2008,"没有权限"),
    // 服务器错误3001-3999
    SERVER_OPTIMISTIC_LOCK_ERROR(3001,"操作冲突"),
    SERVER_INNER_ERROR(3002,"服务器内部错误"),
    SERVER_UNKNOW_ERROR(3003,"服务器未知错误"),
    SERVER_EMPTY_RESULT_DATA_ACCESS_ERROR(3004,"没有找到对应的数据");

    private Integer status;
    private String message;

    ResultCode(Integer status, String message) {
        this.status = status;
        this.message = message;
    }
    public Integer status(){
        return this.status;
    }
    public String message(){
        return this.message;
    }

}

返回对象

@Data
public class ResultVO implements Serializable {
    private Integer status;
    private String message;
    private Object data;
    public ResultVO(ResultCode resultCode, Object data){
        this.status = resultCode.status();
        this.message = resultCode.message();
        this.data = data;
    }
    public ResultVO(ResultCode resultCode){
        this.status = resultCode.status();
        this.message = resultCode.message();
    }
    public ResultVO(Integer status, String message){
        this.status = status;
        this.message = message;
    }

    // 返回成功
    public static ResultVO success(){
        ResultVO resultVO = new ResultVO(ResultCode.SUCCESS);
        return resultVO;
    }
    // 返回成功
    public static ResultVO success(Object data){
        ResultVO resultVO = new ResultVO(ResultCode.SUCCESS,data);
        return resultVO;
    }
    // 返回失败
    public static ResultVO fail(ResultCode resultCode){
        ResultVO resultVO = new ResultVO(resultCode);
        return resultVO;
    }
    // 返回失败
    public static ResultVO fail(ResultCode resultCode, Object data){
        ResultVO resultVO = new ResultVO(resultCode,data);
        return resultVO;
    }
    // 返回失败
    public static ResultVO fail(Integer status, String message){
        ResultVO resultVO = new ResultVO(status,message);
        return resultVO;
    }
}

自定义异常

@EqualsAndHashCode
@Data
public class BizRuntimeException extends RuntimeException {
    private Integer status;
    private String message;

    public BizRuntimeException() {
    }

    public BizRuntimeException(String message){
        super(message);
        this.message = message;
    }
    public BizRuntimeException(Integer status, String message){
        super(status+"");
        this.status = status;
        this.message = message;

    }

    public BizRuntimeException(BaseInfoInterface baseInfoInterface) {
        super(baseInfoInterface.status() + "");
        this.status = baseInfoInterface.status();
        this.message = baseInfoInterface.message();
    }

    public BizRuntimeException(BaseInfoInterface baseInfoInterface, Throwable cause) {
        super(baseInfoInterface.status() + "", cause);
        this.status = baseInfoInterface.status();
        this.message = baseInfoInterface.message();
    }
}

全局异常配置

//@RestControllerAdvice
@ControllerAdvice
public class GlobalExceptionHandler {
    private Logger logger = LoggerFactory.getLogger(getClass());

    /**
     * 自定义异常
     * @param request
     * @param e
     * @return
     */
    @ExceptionHandler(value = BizRuntimeException.class)
    @ResponseBody
    public ResultVO bizExceptionHandler(HttpServletRequest request, BizRuntimeException e){
        logger.error("发生业务异常,原因是:" + e.getMessage());
        return ResultVO.fail(e.getStatus(),e.getMessage());
    }

    /**
     * 处理空指针的异常
     * @param request
     * @param e
     * @return
     */
    @ExceptionHandler(value = NullPointerException.class)
    @ResponseBody
    public ResultVO exceptionHandler(HttpServletRequest request, NullPointerException e){
        logger.error("发生空指针异常!原因是:",e);
        return ResultVO.fail(ResultCode.BODY_NOT_MATCH);
    }

    /**
     * 处理其他异常
     * @param request
     * @param e
     * @return
     */
    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    public ResultVO exceptionHandler(HttpServletRequest request, Exception e){
        logger.error("未知异常!原因是:",e);
        return ResultVO.fail(ResultCode.INTERNAL_SERVER_ERROR);
    }
}

具体使用

@RestController
public class UserRestController {
    @PostMapping("/userRest")
    public boolean insert(User user) {
        System.out.println("开始新增...");
        //如果姓名为空就手动抛出一个自定义的异常!
        if(user.getUsername() == null){
            throw  new BizRuntimeException(-1,"用户姓名不能为空!");
        }
        return true;
    }

    @PutMapping("/userRest")
    public boolean update(User user) {
        System.out.println("开始更新...");
        //这里故意造成一个空指针的异常,并且不进行处理
        String str=null;
        str.equals("111");
        return true;
    }

    @DeleteMapping("/userRest")
    public boolean delete(User user)  {
        System.out.println("开始删除...");
        //这里故意造成一个异常,并且不进行处理
        Integer.parseInt("abc123");
        return true;
    }
}

本文链接:http://www.yanhongzhi.com/post/springboot-7.html

-- EOF --

Comments