为什么使用统一异常处理

在程序运行的过程中可能会发生一些异常信息,所以我们常在controller方法中使用代码块 try catch 进行处理,比如下面的代码块:

@Controller
public class HelloController {

    @ResponseBody
    @GetMapping("/exp1")
    public Response exception1() {
            try {
            int i =  1/0;
        } catch(Exception e) {
            e.printStackTrace();
            return Response.build(ResponseCode.ERROR ,e.getMessage(), null);
        }
        return Response.success();

    }

    @ResponseBody
    @GetMapping("/exp2")
    public Response exception2() {
           try {
            int i =  2/0;
        } catch(Exception e) {
            e.printStackTrace();
            return Response.build(ResponseCode.ERROR ,e.getMessage(), null);
        }
        return Response.success();

    }
}

虽然可以实现我们的需求,但是每个方法中都使用这样的代码块显得整个系统程序很臃肿,所以,可以使用注解@RestControllerAdvice统一异常处理的方法来解决我们的问题。

统一异常处理

在了解这之前请先看统一返回格式,以下是统一异常处理的代码。

@RestControllerAdvice
@Component
public class GlobalExceptionHandler {

    private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(GlobalExceptionHandler.class);

    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    public Response defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {
        logger.error("GlobalExceptionHandler error : ", e);

        if (null != e.getCause()) {
            return Response.build(ResponseCode.ERROR ,e.getCause().getMessage(), null);
        } else {
            return Response.build(ResponseCode.ERROR ,e.getMessage(), null);
        }

    }

}

修改我们的controller代码重新启动项目浏览器输入http://localhost:8080/exp1进行测试

@Controller
public class HelloController {

    @ResponseBody
    @GetMapping("/exp1")
    public Response exception1() {
        int i =  1/0;
        return Response.success();
    }

    @ResponseBody
    @GetMapping("/exp2")
    public Response exception2() {
        int i =  1/0;
        return Response.success();
    }
}
输出

SpringBoot统一异常处理

后台日志输出

SpringBoot统一异常处理

在我们前面的SpringBoot参数校验的几种方式章节中,使用BindingResult bindingResult方式也不够优雅的,即便我们将校验出错时的方法封装成工具类的时候也会有大量重复的代码,所以可以将它放到统一异常处理这里进行优化

带校验的统一异常处理

以下是带校验统一异常处理的代码

package com.example.yxjc.exception;

import com.example.yxjc.resp.Response;
import com.example.yxjc.resp.ResponseCode;
import com.example.yxjc.utils.ValidatedUtils;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.servlet.http.HttpServletRequest;

@RestControllerAdvice
@Component
public class GlobalExceptionHandler {

    private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(GlobalExceptionHandler.class);

    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    public Response defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {
        logger.error("GlobalExceptionHandler error : ", e);

        if (null != e.getCause()) {
            return Response.build(ResponseCode.ERROR ,e.getCause().getMessage(), null);
        } else {
            return Response.build(ResponseCode.ERROR ,e.getMessage(), null);
        }

    }

    @ExceptionHandler({BindException.class,MethodArgumentNotValidException.class})
    @ResponseBody
    public Response parameterExceptionHandler(Exception e) {
        //logger.error("", e);
        // 获取异常信息
        BindingResult bindingResult;
        if (e instanceof BindException) {
            bindingResult = ((BindException) e).getBindingResult();
        } else {
            bindingResult = ((MethodArgumentNotValidException) e).getBindingResult();
        }
        logger.warn("校验错误!");
        // 判断异常中是否有错误信息,如果存在就使用异常中的消息,否则使用默认消息
        if (bindingResult.hasErrors()) {
            String msg = getErrors(bindingResult);
            return Response.build(ResponseCode.VALIDATE_ERROR ,msg, null);
        }
        return Response.build(ResponseCode.VALIDATE_ERROR , "参数错误", null);
    }
    
    public static String getErrors(BindingResult bindingResult){
        //如果没有通过,跳转提示
        List<FieldError> list = bindingResult.getFieldErrors();
        StringBuffer msg = new StringBuffer();
        for (FieldError error : list) {
            msg.append(error.getDefaultMessage());
            msg.append("<br>");
        }
        return msg.toString();
    }


} 
在controller代码中,我们增加一个校验方法testValidate。
@Controller
@Validated
public class HelloController {

    @ResponseBody
    @PostMapping("/testValidate")
    public String testValidate(@Validated User user) {        
        System.out.println(user.getName());
        return "hello";
    }

}

user.java 校验类

package com.example.yxjc.domain;
import org.hibernate.validator.constraints.Range;
import javax.validation.constraints.NotNull;
import java.io.Serializable;

public class User implements Serializable {

    private static final long serialVersionUID = 2733191978271622951L;

    @NotNull(message = "名字不能空")
    private String name;
    @Range(min=0, max=2, message = "性别输入错误")
    private String gender;


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }


}

重新启动应用,使用Postman测试一下:

SpringBoot统一异常处理

系统后台报错日志信息

SpringBoot统一异常处理