SpringBoot-统一异常处理
一、什么是异常处理
异常(exception) 是程序运行过程中发生的事件, 该事件可以中断程序指令的正常执行流程.
1当Java程序运行时出现问题时,系统会自动检测到该错误,并立即生成一个与该错误对应的异常对象
2:然后把该异常对象提交给JAVA虚拟机
3:JAVA虚拟机会自动寻找相应的处理代码来处理这个异常,如果没有找到,则程序终止
4:程序员可以自己编写代码来扑捉可能出现的异常,并编写代码来处理相应的异常.
二、为什么要统一异常处理
例:需求:
获取某女生的年龄并判断:
小于10,返回“应该在上小学”;
大于10且小于16,返回“可能在上初中”;
需求:把System.out.println(bindingResult.getFieldError().getDefaultMessage());信息输出到网页
版本一:
GirlController:
@RestController
public class GirlController {
/**
* getLogger方法中的参数与类名对应
*/
private final static Logger logger = LoggerFactory.getLogger(GirlController.class);
@Autowired
private GirlRepository girlRepository;
@Autowired
private GirlService girlService;
//添加一个女生
@PostMapping(value="/girls")
public Girl girlAdd(@Valid Girl girl, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
System.out.println(bindingResult.getFieldError().getDefaultMessage());
return null;
}
girl.setCupSize(girl.getCupSize());
girl.setAge(girl.getAge());
return girlRepository.save(girl);
}
}
Girl:
@Entity //该注解表示该类在数据库中有对应的表 不用创建该表
public class Girl {
@Id
@GeneratedValue
private Integer id;
@NotBlank(message = "这个字段必传")
private String cupSize;
/**
* 给年龄加上限制:年龄必须大于18岁
* value:值
* message:提示信息
*/
@Min(value =18,message = "年龄必须大于18岁")
private Integer age;
@NotNull(message = "金额必传")
private Double money;
public Girl() {
}
@Override
public String toString() {
return "Girl{" +
"id=" + id +
", cupSize='" + cupSize + '\'' +
", age=" + age +
'}';
}
public Double getMoney() {
return money;
}
public void setMoney(Double money) {
this.money = money;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getCupSize() {
return cupSize;
}
public void setCupSize(String cupSize) {
this.cupSize = cupSize;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
目前,金额必传是打印在控制台上的,现在需要打印在网页上:
修改方法:
版本二:
@PostMapping(value="/girls")
public Object girlAdd(@Valid Girl girl, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return bindingResult.getFieldError().getDefaultMessage();
}
girl.setCupSize(girl.getCupSize());
girl.setAge(girl.getAge());
return girlRepository.save(girl);
}
结果:
问题:返回的格式不统一,对返回的格式作统一(失败和成功):
{
"code":1,
"msg":"金额必传",
"data":null
}
{
"code":0
"msg":"成功",
"data":{
"id":20,
"cupSize":"B",
"age":25,
"money":1.2
}
}
版本三:
Result:
/**
* Created by HCX on 2017/8/17.
* http请求返回的最外层对象
*/
public class Result {
//错误码
private Integer code;
//提示信息
private String msg;
//具体的内容
private T 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;
}
}
GirlController:
@RestController
public class GirlController {
/**
* getLogger方法中的参数与类名对应
*/
private final static Logger logger = LoggerFactory.getLogger(GirlController.class);
@Autowired
private GirlRepository girlRepository;
@Autowired
private GirlService girlService;
//添加一个女生
@PostMapping(value="/girls")
public Result girlAdd(@Valid Girl girl, BindingResult bindingResult) {
if(bindingResult.hasErrors()){
Result result = new Result();
result.setCode(1);
result.setMsg(bindingResult.getFieldError().getDefaultMessage());
result.setData(null);
return result;
}
girl.setCupSize(girl.getCupSize());
girl.setAge(girl.getAge());
Result result = new Result();
result.setCode(0);
result.setMsg("成功");
result.setData(girlRepository.save(girl));
return result;
}
}
运行结果:
可见代码重复了,进行优化:版本四
定义返回结果的工具类ResultUtil:
public class ResultUtil {
public static Result success(Object object){
Result result = new Result();
result.setCode(0);
result.setMsg("成功");
result.setData(object);
return result;
}
public static Result success(){
return success(null);
}
public static Result error(Integer code,String msg){
Result result = new Result();
result.setCode(code);
result.setMsg(msg);
return result;
}
}
GirlController:
@RestController
public class GirlController {
/**
* getLogger方法中的参数与类名对应
*/
private final static Logger logger = LoggerFactory.getLogger(GirlController.class);
@Autowired
private GirlRepository girlRepository;
@Autowired
private GirlService girlService;
//添加一个女生
@PostMapping(value="/girls")
public Result girlAdd(@Valid Girl girl, BindingResult bindingResult) {
if(bindingResult.hasErrors()){
return ResultUtil.error(1,bindingResult.getFieldError().getDefaultMessage());
}
girl.setCupSize(girl.getCupSize());
girl.setAge(girl.getAge());
return ResultUtil.success(girlRepository.save(girl));
}
}
三、案例
需求:获取某女生的年龄并判断,小于10,返回“应该在上小学”;大于10且小于16,返回“可能在上初中”
GirlController:
@RestController
public class GirlController {
/**
* getLogger方法中的参数与类名对应
*/
private final static Logger logger = LoggerFactory.getLogger(GirlController.class);
@Autowired
private GirlRepository girlRepository;
@Autowired
private GirlService girlService;
@GetMapping(value = "girls/getAge/{id}")
public void getAge(@PathVariable("id") Integer id) throws Exception{
girlService.getAge(id);
}
}
GirlService:
@Service
public class GirlService {
@Autowired
private GirlRepository girlRepository;
public void getAge(Integer id) throws Exception{
Girl girl = girlRepository.findOne(id);
Integer age = girl.getAge();
if(age<10){
//返回“你可能在上小学” code=100
throw new Exception("你可能在上小学");
}else if(age>10 && age<16){
//返回“你可能在上初中” code=101
throw new Exception("你可能在上初中");
}
}
}
ExceptionHandle:
@ControllerAdvice
public class ExceptionHandle {
/**
* @ExceptionHandler(value = Exception.class):声明要捕获的异常类
* @return
*/
@ExceptionHandler(value = Exception.class)
@ResponseBody
public Result handle(Exception e){
return ResultUtil.error(100,e.getMessage());
}
}
此时,已经按照我们所期望的格式返回了:
问题:返回的错误码都是一样的,但是在实际问题中,我们希望定位的错误更准确,希望不同的错误可以返回不同的错误码,所以可以自定义一个Exception:
GirlException:
public class GirlException extends RuntimeException{
/**
* 注意要继承自RuntimeException,底层RuntimeException继承了Exception,
* spring框架只对抛出的异常是RuntimeException才会进行事务回滚,
* 如果是抛出的是Exception,是不会进行事物回滚的
*/
private Integer code;
public GirlException(Integer code,String message) {
//父类的构造方法本身会传message进去
super(message);
this.code = code;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
}
ExceptionHandle:
@ControllerAdvice
public class ExceptionHandle {
/**
* @ExceptionHandler(value = Exception.class):声明要捕获的异常类
* @return
*/
@ExceptionHandler(value = Exception.class)
@ResponseBody
public Result handle(Exception e){
if(e instanceof GirlException){
GirlException girlException = (GirlException)e;
return ResultUtil.error(girlException.getCode(),girlException.getMessage());
}else {
return ResultUtil.error(-1,"未知错误");
}
}
}
GirlService:
@Service
public class GirlService {
@Autowired
private GirlRepository girlRepository;
public void getAge(Integer id) throws Exception{
Girl girl = girlRepository.findOne(id);
Integer age = girl.getAge();
if(age<10){
//返回“你可能在上小学” code=100
throw new GirlException(100,"你可能在上小学");
}else if(age>10 && age<16){
//返回“你可能在上初中” code=101
throw new GirlException(101,"你可能在上初中");
}
}
}
GirlController:
@RestController
public class GirlController {
/**
* getLogger方法中的参数与类名对应
*/
private final static Logger logger = LoggerFactory.getLogger(GirlController.class);
@Autowired
private GirlRepository girlRepository;
@Autowired
private GirlService girlService;
@GetMapping(value = "girls/getAge/{id}")
public void getAge(@PathVariable("id") Integer id) throws Exception{
girlService.getAge(id);
}
}
运行结果:
当抛出的是系统异常时,可以通过日志进行记录:
ExceptionHandle:
@ControllerAdvice
public class ExceptionHandle {
//记录系统异常
private final static Logger logger = LoggerFactory.getLogger(ExceptionHandle.class);
/**
* @ExceptionHandler(value = Exception.class):声明要捕获的异常类
* @return
*/
@ExceptionHandler(value = Exception.class)
@ResponseBody
public Result handle(Exception e){
if(e instanceof GirlException){
GirlException girlException = (GirlException)e;
return ResultUtil.error(girlException.getCode(),girlException.getMessage());
}else {
logger.error("【系统异常】 {}",e);
return ResultUtil.error(-1,"未知错误");
}
}
}
问题:在自定义异常的错误码和信息时,如果过多,没有统一管理,则会出现重复。使用枚举统一管理code和message:
ResultEnum:
public enum ResultEnum {
UNKNOW_ERROR(-1,"未知错误"),
SUCCESS(0,"成功"),
PRIMARY_SCHOOL(100,"你可能在上小学"),
MIDDLE_SCHOOL(101,"你可能在上初中"),
;
private Integer code;
private String msg;
ResultEnum(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public Integer getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
GirlException:
public class GirlException extends RuntimeException{
/**
* 注意要继承自RuntimeException,底层RuntimeException继承了Exception,
* spring框架只对抛出的异常是RuntimeException才会进行事务回滚,
* 如果是抛出的是Exception,是不会进行事物回滚的
*/
private Integer code;
public GirlException(ResultEnum resultEnum) {
//父类的构造方法本身会传message进去
super(resultEnum.getMsg());
this.code = resultEnum.getCode();
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
}
GirlService:
@Service
public class GirlService {
@Autowired
private GirlRepository girlRepository;
public void getAge(Integer id) throws Exception{
Girl girl = girlRepository.findOne(id);
Integer age = girl.getAge();
if(age<10){
//返回“你可能在上小学” code=100
throw new GirlException(ResultEnum.PRIMARY_SCHOOL);
}else if(age>10 && age<16){
//返回“你可能在上初中” code=101
throw new GirlException(ResultEnum.MIDDLE_SCHOOL);
}
}
}
GirlController:
@RestController
public class GirlController {
/**
* getLogger方法中的参数与类名对应
*/
private final static Logger logger = LoggerFactory.getLogger(GirlController.class);
@Autowired
private GirlRepository girlRepository;
@Autowired
private GirlService girlService;
@GetMapping(value = "girls/getAge/{id}")
public void getAge(@PathVariable("id") Integer id) throws Exception{
girlService.getAge(id);
}
}