摘要:在项目开发过程中会碰到很多异常,一些异常是因为用户的操作产生的,另外一些可能是系统的原因(如网络、操作系统和服务器等)。统一处理所有的异常能够减少代码的重复度和复杂度,有利于代码的维护,不对外暴露原始错误,给用户友好的错误提示。
在项目开发过程中会碰到很多异常,一些异常是因为用户的操作产生的,另外一些可能是系统的原因(如网络、操作系统和服务器等)。统一处理所有的异常能够减少代码的重复度和复杂度,有利于代码的维护,不对外暴露原始错误,给用户友好的错误提示。
对异常的处理一般分为两种:抛出异常或使用try…catch…finally捕获处理异常。程序运行时如果产生了一个异常,则JVM会产生一个对应的异常类对象,包含异常事件类型、发生异常时应用程序的状态和调用过程等信息,然后程序再向上一级抛出异常,查找是否有匹配的异常处理程序,如果没有就中断程序,如果有,就把异常交给匹配的异常处程序进行相应的处理。
Java中的异常基类是java.lang.Throwable,它是所有异常类的根类。
java.lang.Throwable分为两类异常类型:一类是java.lang.Error,表示错误信息,另外一类是java.lang.exception,表示异常信息。
(1)java.lang.Error是throwable的子类,代表编译时间和系统错误,用于指示合理的应用程序不应该试图捕获的严重问题,常见的错误信息有:
StackOverFlowError:栈空间溢出错误;
OutOfMemoryError:内存溢出错误;
IllegalAccessError:非法的访问权限错误;
NoClassDefFoundError:JVM未找到类错误;
NoSuchMethodError:JVM未找到方法错误。
java.lang.Error由java虚拟机生成并抛出,包括动态链接失败、虚拟机错误等,它不是由程序员引发的错误,而且这类错误一般程序不做处理。
(2)java.lang.Exception异常具体分为检查期异常和运行时异常。运行时异常有一个基类java.lang.RuntimeException,它是在运行时抛出的,是java.lang.Exception的子类。所有继承自java.lang.RuntimeException的异常类都是运行时异常,其他继承自java.lang.Exception但不继承java.lang.RuntimeException的异常都是检查异常。检查异常是在方法上固定存在的,如果调用了该方法就必须处理此异常。
常见的检查期异常:java.io.IOException是I/O异常,java.io.FileNotFoundException是文件找不到异常,ClassNotFoundException是类找不到异常,java.lang.SecurityException是安全异常。
常见的运行时异常:java.lang.NullPointerException是“臭名昭著”的空指针异常,java.lang.IndexOutOfBoundsException和java.lang.ArrayIndexOutOfBoundsException都是数组越界异常,java.lang.IllegalArgumentException是非法参数异常,java.util.Concurrent-ModificationException是修改状态异常。
要在项目中完成统一异常的处理,需要使用两个注解:
@ControllerAdvice和@ExceptionHandler。
@ControllerAdvice是一个非常有用的注解,它是一个增强的Controller注解,使用这个注解可以实现3个功能,分别是全局异常处理、全局数据绑定和全局数据预处理。
@ExceptionHandler注解的作用对象是方法,并且在运行时有效,value可以指定异常类。由该注解注释的方法具有灵活的输入参数,包括一般的异常或特定的异常(即自定义异常)。如果注解没有指定异常类,则默认进行映射,捕获到该异常后再进行相应处理。
在Spring Boot项目中可以完成全局异常的统一处理,能够给用户提供友好的错误提示信息。下面演示本项目的异常处理过程。首先自定义异常:
package com.example.thymeleafdemo.exception;
import lombok.Data;
/**
* 自定义异常
*/
@Data
public class MyBusinessException extends RuntimeException {
private static final long serialVersionUID = 1L;
private int code;
private String message;
public MyBusinessException(String message) {
super(message);
this.message = message;
}
public MyBusinessException(int code, String message) {
super(message);
this.code = code;
}
}
然后设置全局异常的捕获处理方法,代码如下:
package com.example.thymeleafdemo.exception;
import com.example.thymeleafdemo.event.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.StringJoiner;
/**
* 全局异常处理
*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 处理自定义异常
*/
@ExceptionHandler(MyBusinessException.class)
public Result handleBizException(MyBusinessException ex) {
result.setCode(ex.getCode);
result.setMessage(ex.getMessage);
return result;
}
/**
* 参数校验不通过异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result
handleMethodArgumentNotValidException(MethodArgumentNotValidException
ex) {
StringJoiner sj = new StringJoiner(";");
ex.getBindingResult.getFieldErrors.forEach(x ->
sj.add(x.getDefaultMessage));
result.setCode(505);
result.setMessage(sj.toString);
return result;
}
/**
* Controller参数绑定错误 */
@ExceptionHandler(MissingServletRequestParameterException.class)
public Result
handleMissingServletRequestParameterException(MissingServletRequestPar
ameterException ex) {
result = new Result;result.setCode(506);
result.setMessage(ex.getMessage);
return result;
}
/**
* 其他未知异常
*/
@ExceptionHandler(value = Exception.class)
public Result handleException(Exception ex) {
log.error(ex.getMessage, ex);
result.setCode(507);
result.setMessage("服务器内部错误");
return result;
}
}
以上处理方法中分别处理了MyBusinessException类的异常,又针对参数校验不通过的异常分别进行了不同的处理。下面再写一个Controller入口,分别处理系统中可能发生的两种不同的异常,即产品空指针的异常和自定义异常:
package com.example.thymeleafdemo.exception;
import com.example.thymeleafdemo.event.Result;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ExceptionController { /**
* 系统内部错误
*/
@GetMapping("/exception")
public Result testException {
int i = 1 / 0;
Resultresult.setCode(200);
result.setMessage("success");
result.setData("cc");
return result;
}
/**
* 自定义异常
*/
@GetMapping("/myException")
public Result testMyexception {
throw new MyBusinessException(508, "自定义的异常");
}
}
启动当前项目,访问localhost:8080/exception,接口返回的异常信息如图4.12所示。再访问localhost:8080/myException得到自定义异常的错误提示,如图4.13所示。全局的异常处理完成后,对用户屏蔽服务器内部错误,只给用户简单的提示。
统一异常处理通过@ControllerAdvice注解向控制器发送通知,并接收所有Controller层的通知,再结合@ExceptionHandler注解对指定的异常进行捕获处理,最后将结果返回给用户。
来源:程序员高级码农II一点号