Java-异常:不恰当的异常转换、不充分的日志记录、过度或不当的异常捕获
- Java-异常:不恰当的异常转换、不充分的日志记录、过度或不当的异常捕获
- 一、前期准备
- 二、案例分析
- 1、不恰当的异常转换
- 2、不充分日志记录
- 3、过度或不当的异常捕获
- 三、正确处理方式
- 1、方式1
- 2、方式2
Java-异常:不恰当的异常转换、不充分的日志记录、过度或不当的异常捕获
异常相关概念可以参考:秋刀鱼不做梦-Java 异常处理详解
在这里,我重点针对异常不恰当的转换、不充分的日志记录、过度或不当的异常捕获的问题来说
一、前期准备
封装API的响应结果
@Data
@AllArgsConstructor
public class APIResponse<T> {
private boolean success;
private T data;
private int code;
private String message;
}
自定义异常
public class BusinessException extends RuntimeException {
private int code;
public BusinessException(String message, int code) {
super(message);
this.code = code;
}
public int getCode() {
return code;
}
}
定义全局异常处理器
@RestControllerAdvice
@Slf4j
public class RestControllerExceptionHandler {
private static int GENERIC_SERVER_ERROR_CODE = 2000;
private static String GENERIC_SERVER_ERROR_MESSAGE = "服务器忙,请稍后再试";
@ExceptionHandler
public APIResponse handle(HttpServletRequest req, HandlerMethod method, Exception ex) {
if (ex instanceof BusinessException) {
BusinessException exception = (BusinessException) ex;
log.warn(String.format("访问 %s -> %s 出现业务异常!", req.getRequestURI(), method.toString()), ex);
return new APIResponse(false, null, exception.getCode(), exception.getMessage());
} else {
log.error(String.format("访问 %s -> %s 出现系统异常!", req.getRequestURI(), method.toString()), ex);
return new APIResponse(false, null, GENERIC_SERVER_ERROR_CODE, GENERIC_SERVER_ERROR_MESSAGE);
}
}
}
一个异常处理器,用于处理@RestController注解的控制器中抛出的异常。它使用@Slf4j注解来使用日志记录功能。
- @RestControllerAdvice注解表示这是一个全局异常处理类。
- @ExceptionHandler注解表示该方法用于处理异常。
- handle方法用于捕获异常,并根据异常类型返回不同的响应。
- 如果异常是BusinessException类型,则记录日志并返回一个包含异常信息的APIResponse对象。
- 如果异常不是BusinessException类型,则记录日志并返回一个包含通用错误信息的APIResponse对象。
controller
@Slf4j
@RestController
@RequestMapping("handleexception")
public class HandleExceptionController {
//……
private void readFile() throws IOException {
Files.readAllLines(Paths.get("a_file"));
}
}
二、案例分析
@GetMapping("exception")
public void exception(@RequestParam("business") boolean b) {
if (b)
throw new BusinessException("订单不存在", 2001);
throw new RuntimeException("系统错误");
}
自定义异常生效
1、不恰当的异常转换
@GetMapping("wrong1")
public void wrong1() {
try {
readFile();
} catch (IOException e) {
throw new RuntimeException("系统忙请稍后再试");
}
}
问题:将具体的IOException转换为非具体的RuntimeException,失去了原始异常的详细信息,不利于问题的定位和调试。应该尽可能保留或封装原始异常,以便于追踪错误源头。
2、不充分日志记录
@GetMapping("wrong2")
public void wrong2() {
try {
readFile();
} catch (IOException e) {
log.error("文件读取错误, {}", e.getMessage());
throw new RuntimeException("系统忙请稍后再试");
}
}
问题:
- 虽然记录了日志但异常转换仍存在问题:与wrong1类似,该方法虽然在抛出新异常前记录了详细的错误信息,但是最终抛出的是一个不包含原始异常信息的RuntimeException。这会导致堆栈跟踪信息丢失,不易于问题诊断。
- 可以改进日志内容:虽然记录了错误信息,但是直接使用e.getMessage()可能不足以覆盖所有上下文信息,理想情况下应考虑记录更多上下文,如当前执行的操作、涉及的变量值等。
3、过度或不当的异常捕获
@GetMapping("wrong3")
public void wrong3(@RequestParam("orderId") String orderId) {
try {
readFile();
} catch (Exception e) {
log.error("文件读取错误", e);
throw new RuntimeException();
}
}
问题:
- 过度捕获异常:使用了非常广泛的Exception进行捕获,这意味着它会捕获所有类型的异常,包括那些可能本应由上层处理或根本就不应该被捕获的异常。这可能导致隐藏问题,使得某些异常被不恰当地处理。
- 缺少异常信息:抛出的RuntimeException没有提供任何具体信息,这对于调试和日志记录是非常不利的。至少应该提供一个有意义的错误消息。
- 日志记录不完整:虽然记录了错误日志,但是没有利用占位符插入异常信息(e)到日志消息中,这可能会遗漏重要的异常详情。
上述的问题主要影响系统的可维护性和故障排查能力,所以我们需要更加细致地处理异常,保留异常链,以及提供充分的日志信息
三、正确处理方式
1、方式1
@GetMapping("right1")
public void right1() {
try {
readFile();
} catch (IOException e) {
log.error("文件读取错误", e);
throw new RuntimeException("系统忙请稍后再试");
}
}
2、方式2
@GetMapping("right2")
public void right2() {
try {
readFile();
} catch (IOException e) {
throw new RuntimeException("系统忙请稍后再试", e);
}
}
方式1和方式2虽然表达不一样,但是都解决了:不恰当的异常转换、不充分的日志记录、过度或不当的异常捕获的问题,虽然代码简单,但是涉及异常操作时,也要引起重视,不然,出bug了,不好搞
- 异常精确捕获,并精确处理,比方说上面right1和right2都是IOException
- 获取更详细的异常日志,直接打印 e
- 注意异常转换,丢失异常的上下