用于解决程序在运行过程中产生的意外情况。语法错误或逻辑错误不算是异常。
一提到异常,大家难免会想到几个关键字。分别是:
try cach finally throw throws
他们都分别代表什么意思呢
try cach finally
try{
可能会产生异常代码块
}catch(异常类型 ex){
对异常进行处理
//调试时一般会打印出调试信息
//ex.printStackTrace();
}catch(异常类型 ex){
对异常进行处理
//调试时一般会打印出调试信息
//ex.printStackTrace();
}finally{
//无论前面异常执行不执行,这里一定会被执行的代码
//除非前面强制终止,System.exit(1) //终止java虚拟机
//这里不要用return,否则会屏蔽前面的return
//一般在这里进行清理、释放资源等工作
}
throw throws
throws:
声明将要抛出何种类型的异常
//下面的函数可能会抛出 异常类型1,异常类型2 这两种类型的异常.
public void 方法名() throws 异常类型1,异常类型2{
可能产生异常的代码
}
其本身不处理异常,只负责声明和抛出,使用它的人来处理异常
调用存在异常声明的方法时
try {
方法名();//方法调用
}catch(异常类型 ex){
处理异常
}
throw:
主动/手动抛出一些业务逻辑上的异常,自己手动创建要抛出的异常
public void 方法名(){
try {
if(业务异常)
throw new Exception("业务异常描述");
}catch(Exception ex){//捕获异常
//处理异常
}
}
public void 方法名() throws Exception{
if(业务异常){
throw new Exception("业务异常描述");
}
}
//调用
try {
方法名();
}catch(Exception ex){//捕获异常
//处理异常
}
⚠️:throw和throws有本质的区别。不要混为一谈。
throw是抛出异常阶段 的方案:
抛出异常有两种:一种是系统自动产生异常并抛出,一种是手动创建异常抛出(throw)
throws是捕获异常阶段 的方案:
捕获异常处理的方案有两种,一种是try/catch直接处理,一种是抛给别人处理(throws)
自定义异常
定义一个类,继承自异常类
public class MyException extends Exception{
public MyException(){//构造方法
super("异常描述");//调用父类构造方法
}
}
之后就可以在业务代码中 throw自己定义的异常了。
异常选择
- 如果父类中的方法没有throws异常,那么,子类中重写该方法时,也不能throws异常。对自己编写的可能出现异常的代码,只能自己try/catch
- 一个方法A中调用了方法 a1,a2,a3。那么,一般情况下a1,a2,a3建议用throws抛出异常,A中对异常进行try/catch。
异常链
多重嵌套抛出异常时,将异常信息一个一个的串联起来抛出的机制。否则只能看到最后一个异常。
- 方式1
throw Exception("本异常描述信息",捕获的异常) - 方式2
Exception ex = new Exception("本异常描述信息");
ex.initCause(捕获的异常);
throw ex;
异常实际案例
接下来,举一个实际项目中的异常捕获方式。但并不是唯一方式。
1.自定义一个全局异常捕获类
/**
* 全局异常捕获
*/
@ControllerAdvice
public class GlobalExceptionInterceptor {
private Logger log = LoggerFactory.getLogger(getClass());
/**
* 处理ServiceException异常 //自定义异常
*/
@ExceptionHandler(value = ServiceException.class)
@ResponseBody
public RespResult serviceExceptionHandle(ServiceException se) {
log.error("自定义异常堆栈:"+ ThrowableUtil.getStackTrace(se));
//对自定义异常处理的业务
return respBody;
}
/**
* 在 @RequestBody 上 加validate 校验参数失败后抛出的异常是
* MethodArgumentNotValidException异常。
* 参数为空,或没传参数都会抛此异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseBody
public RespResult MethodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
// 打印堆栈信息
log.error(ThrowableUtil.getStackTrace(e));
//参数校验异常处理逻辑
}
/**
* 在@RequestParam的参数没传时 抛这个异常
* MissingServletRequestParameterException 。
* 注意:是参数没传会抛,不是参数为空会抛
*/
@ExceptionHandler(MissingServletRequestParameterException.class)
@ResponseBody
public RespResult MissingServletRequestParameterExceptionHandler(MissingServletRequestParameterException e) {
// 打印堆栈信息
log.error(ThrowableUtil.getStackTrace(e));
//参数校验异常处理逻辑
}
/**
* 处理请求json格式不成器的异常:HttpMessageNotReadableException
*/
@ExceptionHandler(HttpMessageNotReadableException.class)
@ResponseBody
public RespResult HttpMessageNotReadableExceptionHandler(HttpMessageNotReadableException e) {
// 打印堆栈信息
log.error(ThrowableUtil.getStackTrace(e));
//请求体校验异常处理逻辑
}
/**
* 唯一索引重复异常:MySQLIntegrityConstraintViolationException
*/
@ExceptionHandler(SQLIntegrityConstraintViolationException.class)
@ResponseBody
public RespResult SQLIntegrityConstraintViolationException(SQLIntegrityConstraintViolationException e) {
// 打印堆栈信息
log.error(ThrowableUtil.getStackTrace(e));
String message = e.getMessage();
//唯一索引异常处理逻辑
}
...
其他类型异常捕获及处理
}
2.自定义异常类
我们看到,在上面全局异常捕获类中,有一个自定义异常类。具体内容如下:
在业务处理中,遇到需要抛异常的地方,你就可以构造一个这样的异常,然后抛出去,就会被全局异常捕获并处理了。
public interface ExceptionEnumInterface {
/**
* 错误码,为200表示正常
* @return
*/
String getCode();
/**
* 错误说明,code为200时返回null,否则返回错误说明<br/>
* 可包含占位符,占位符格式为中括号加数字表示,如{0}, {1},表示替换为第0个、第1个参数
* @return
*/
String getMsg();
}
public class ServiceException extends RuntimeException{
private ExceptionEnumInterface errorEnum;
private Object[] args;
private Exception e;
/**
* 构造一个包含特定异常码的业务异常,当异常描述中饮食可替换的占位符时,可用args中参数来替换占位符<br/>
* 占位符格式为中括号加数字表示,如{0}, {1},表示替换为第0个、第1个参数
* @param errorEnum
* @param args
* @return
*/
public static ServiceException of(ExceptionEnumInterface errorEnum, Object...args) {
return new ServiceException(errorEnum, args);
}
/**
* 构造一个包含特定异常码的业务异常,当异常描述中饮食可替换的占位符时,可用args中参数来替换占位符<br/>
* 占位符格式为中括号加数字表示,如{0}, {1},表示替换为第0个、第1个参数
* @param errorEnum
* @param args
* @return
*/
public static ServiceException of(ExceptionEnumInterface errorEnum, Exception e, Object...args) {
return new ServiceException(errorEnum,e,args);
}
/**
* 通过异常枚举接口来返回特定的业务异常
* @param errorEnum
* @param args
*/
private ServiceException(ExceptionEnumInterface errorEnum, Object...args) {
super(ExceptionEnumUtil.getErrorEnumMsg(errorEnum, args));
this.errorEnum = errorEnum;
this.args = args;
}
/**
* 通过异常枚举接口来返回特定的业务异常
* @param errorEnum
* @param args
*/
private ServiceException(ExceptionEnumInterface errorEnum, Exception e, Object...args) {
super(ExceptionEnumUtil.getErrorEnumMsg(errorEnum, args));
this.errorEnum = errorEnum;
this.args = args;
this.e = e;
}
public ExceptionEnumInterface getErrorEnum() {
return errorEnum;
}
public Object[] getArgs() {
return args;
}
public Exception getE() {
return e;
}
}
3. 抛异常
try {
//业务代码
} catch (Exception e) {
throw ServiceException.of(参数);
}