java异常知识小结
1、异常的分类
简单的说,运行期间阻止程序正常运行的就是异常,java的异常处理机制是很重要的一块,它通过少量代码来增强了应用的容错性和健壮性,java中异常的分类大致如下:
1、Throwable是Error和Exception的顶层父类;
2、Error是一般是指jvm的错误,比较少出现,不能通过代码层面去控制Error的产生;
3、Exception代表程序运行过程中,"不希望"出现的情况,比如空指针、数组下标越界、文件未找到等异常,通过适当的处理可"避免",也是开发所要关注的重点;
4、SQLException/IPException也被称为checked异常,对于可能发生checked异常的,程序编译期间就会强制捕获;Error被称为unchecked异常;而RuntimeException为unchecked异常,需要程序员手动的捕获处理。
2、异常的处理
通过try catch捕获异常
try catch处理异常是最常见的异常处理方式
try {
//里面放可能出现异常的逻辑
//发生异常就进入catch中的逻辑
//如果有finally语句,则最后执行finally中的逻辑
} catch (Exception e) {
if(e instanceof SQLException){
}
//可以使用异常的父类Exception来匹配所有异常
//每个catch会用于捕获特定的异常(包括其子类)
//如果try中没有捕获到异常,则执行finally中逻辑
//catch语句中有时会进行try逻辑的重试,视需求而定
} finally {
//finally语句是可选择的,一般finally里会做一些资源关闭、回收工作,比如关闭流,关闭未使用的线程等
//这里的逻辑一般都会执行
//有时候finally语句中自己还要try catch
}
注意事项:
1、catch中支持父类匹配,所有如果要不是直接catch(Exception e),而是catch(SQLException e){}catch(NullPointerException e){}这样的方式去匹配异常,那么要将捕获子类异常放在父类的前面,这样捕获才有意义;
2、try/catch/finally语块中定义的局部变量不能共享使用,如果要共享,就定义在try/catch/finally块外部;
3、catch语句中最好配合日志记录,方便定位异常;
4、对于非运行时异常(Exception),一定要try catch处理;
5、try中的逻辑一旦发生异常,紧跟在异常逻辑后面的代码便不再执行;
6、存在主线程和子线程时(比如使用Executor框架时),当子线程发生异常,主线程是不受影响的(线程只解决线程自己存在的问题),这点很关键,吃过这个亏; 但是可以通过比如Callable对象的异步线程去获取异常,返回给主线程;
7、普通的逻辑处理中,对于可预见或不可预见性的异常,最好try catch捕获下;
8、含有事务处理的逻辑中,如果要让事务顺利回滚,请不要在相应的代码中try catch,这一点可以参考https://blog.csdn.net/fanrenxiang/article/details/83024436
通过throws关键字抛出异常
public static void get() throws Exception{ }
throws抛出异常不同于try catch,它会将可能存在的异常显式的抛向这个方法的调用者,而自己不做任何处理
3、自定义异常类和异常处理器
项目中经常需要用到异常处理类来处理两类异常:1.程序逻辑中抛出的异常,比如不小心数组下标越界;2.处理404/500等这样的页面错误码响应,比如你直接访问一个项目域名但是路由却不存在的地址会直接抛出404页面,很不友好。
//自定义异常,用以区分精确的异常种类
public class GlobalException extends RuntimeException {
private Integer code;
private String message;
public GlobalException(Integer code,String message) {
super(message);
this.code = code;
this.message = message;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
@Override
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
import com.simons.cn.springbootdemo.util.Result;
import com.simons.cn.springbootdemo.util.ResultUtil;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* 类描述:全局api代码异常处理返回
*/
@ControllerAdvice
public class ApiExceptionHandler {
@ResponseBody
@ExceptionHandler
public Result handleException(Exception e) {
if (e instanceof GlobalException) {
GlobalException ge = (GlobalException) e;
return ResultUtil.success1(ge.getCode(), ge.getMessage());
}
return ResultUtil.success1(-1001, "unknown error!");
}
}
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletResponse;
/**
* 类描述:全局异常处理:处理类似于404、500酱紫的页面异常
* 创建人:simonsfan
*/
@Component
public class ErrorPageExceptionHandle implements ErrorController {
private static final String ERROR_PATH = "/error";
/**
* Returns the path of the error page.
*
* @return the error path
*/
@Override
public String getErrorPath() {
return ERROR_PATH;
}
/**
* Web页面错误处理
*/
@RequestMapping(value = ERROR_PATH, produces = "text/html")
public String errorPageHandler(HttpServletResponse response) {
int status = response.getStatus();
switch (status) {
case 403:
return "403"; //这里的403、404/500页面都需要自定义
case 404:
return "404";
case 500:
return "500";
}
return "index"; //都不是,跳回到首页
}
}
测试全局api代码异常处理返回
/**
* 测试api全局异常统一处理
*
* @return
*/
@GetMapping(value = "/hello")
@ResponseBody
public String hello() {
if (true) {
throw new GlobalException(200, "我是api,发生异常啦~");
}
return "hello";
}
测试处理类似于404、500酱紫的页面异常的话,直接访问一个项目域名下不存在的url即可跳转到自定义的404页面。
使用自定义异常类使得业务逻辑和异常处理分离解耦;同时自定义异常处理器能更加统一性、专一性的处理特定异常,能减少在try catch中的重复性代码,结合注解使用更加显得方便。
4、finally关键字
1、finally里的逻辑都会执行,除非执行过System.exit(0)或者逻辑都没走到try语句块中;
2、finally逻辑总是在return语句之前;
3、finally语句总是伴随着try或catch语句出现,try必须和catch或者finally同时使用;
关于finally语句,引申阅读:finally和return那点事
5、主线程如何获取子线程捕获到的异常
思路:使用Callable对象的get方法获取子线程的执行结果,子线程中进行try catch异常捕获
/** controller访问路由 **/
@RequestMapping("/test")
@ResponseBody
public String test() {
CommonResult result = getResult();
log.info("子线程任务执行结果:message=" + result.getMessage());
return result.getMessage();
}
/** 获取异步任务结果方法 **/
public static CommonResult getResult(){
CommonResult result = new CommonResult();
ExecutorService executorService = Executors.newCachedThreadPool();
FutureTask<CommonResult> future = new FutureTask<>(new ChargeCallable());
executorService.submit(future);
try {
result = future.get();
} catch (Exception e) {
log.error("主线程捕获到子线程任务中的异常");
}
return result;
}
/** 异步任务逻辑 **/
static class ChargeCallable implements Callable<CommonResult> {
private final Logger logger = LoggerFactory.getLogger(ChargeCallable.class);
@Override
public CommonResult call() {
CommonResult result = new CommonResult();
try {
//TODO 模拟逻辑
result.setErrorcode("0");
result.setMessage("success");
//TODO 模拟发生异常
throw new Exception("我是非运行时异常");
} catch (Exception e) {
logger.info("我是子线程,我发生异常了");
result.setErrorcode("-1");
result.setMessage("fail");
}
return result;
}
}