优雅地处理你的 Java 异常吧
点击下方“IT牧场”,选择“设为星标”
本文仅按照业务系统开发角度描述异常的一些处理看法.不涉及java的异常基础知识,可以自行查阅
《Java核心技术 卷I》和 《java编程思想》 可以得到更多的基础信息.
系统运行出错,但是完全不知道错误发生的位置. 我们找到了错误的位置,但是完全不知道是因为什么. 系统明明出了错误,但是就是看不到错误堆栈信息.
什么情况需要自定义异常
浪费log日志存储空间,并且栈顶并不是最接近发生异常的代码位置. 只有一种异常类,无法精准区分开异常类型 异常类后期难以修改以增加其携带的信息.
什么情况需要手动处理异常
你有能力处理异常,并且你知道如何处理 你有责任处理异常
自定义业务异常
/**
* 业务受理失败异常
*/
public class ServiceException extends RuntimeException {
//接收reason参数用来描述业务失败原因.
public ServiceException(String reason) { super(reason); }
}
接下来看下Controller层.
// UserController.java
/**
* 修改用户信息
* @param userID 用户ID
* @param user 修改用户信息表单数据
*/
@PutMapping("{userID}")
public JSONResult updateUser(@PathVariable("userID") Integer userID, @RequestBody UpdateUserForm userForm) {
User user = new User(); //准备业务逻辑层使用的领域模型
BeanUtils.copyProperties(userForm, user); //拷贝要修改的值
user.setUserId(userID); //设置主键到用户数据中
userService.updateUser(user); //调用更新业务逻辑
JSONResult json = new JSONResult(); //准备要响应的数据
json.put("user", user); //把修改后的用户数据还给页面
return json; // --
}
有效性: 比如用户所在岗位,是否属于数据库有记录的岗位ID,如果不存在,无效. 合法性: 比如用户名只允许输入最多12个字符,用户提交了20个字符,不合法.
要修改的用户ID不存在. 用户被锁定,不允许修改. 乐观锁机制发现用户已经被被人修改过. 由于某种原因,我们的程序无法保存到数据库. 一些程序员错误的开发了代码,导致保存过程中出现异常,比如NPE.
在ccontroller 调用userService的checkUserExist()方法. 在controller直接书写业务逻辑. 在service响应一个状态码机制,比如1 2 3表示错误信息,0 表示没有任何错误.
/**
* 修改用户信息
* @param user 要修改的用户数据
*/
public void updateUser(User user) {
User userOrig = userDao.getUserById(user.getUserID());
if (null == userOrig) {
throw new ServiceException("用户不存在");
}
if (userOrig.isLocked()) {
throw new ServiceException("用户被锁定,不允许修改");
}
if (!user.getVersion().equals(userOrig.getVersion())) {
throw new ServiceException("用户已经被别人修改过,请刷新重试");
}
// TODO 保存用户数据 ...
}
在controller 使用try-catch进行处理. 直接把异常抛给上层框架统一处理.
@ControllerAdvice(basePackages = { "com.xxx.xxx.bussiness.xxx" })
public class ModuleControllerAdvice {
private static final Logger LOGGER = LoggerFactory.getLogger(ModuleControllerAdvice.class);
private static final Logger SERVICE_LOGGER = LoggerFactory.getLogger(ServiceException.class);
/**
* 业务受理失败
*/
@ResponseBody
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(ServiceException.class)
private JSONResult handleServiceException(ServiceException exception) {
String message = "业务受理失败,原因:" + exception.getLocalizedMessage();
SERVICE_LOGGER.info(message);
JSONResult json = new JSONResult();
json.serCode(500001); // 500000表示系统异常,500001表示业务逻辑异常
json.setMessage(message);
return json;
}
}
首先,ServiceException一定要和其他的代码错误分离,不应该混为一谈. 其次,ServiceException并不一定要记录日志,我们应该提供独立的log对象,方便开关.
{
code: 200001,
message: "业务受理失败,原因:用户名称不存在!"
}
逻辑异常,这类异常用于描述业务无法按照预期的情况处理下去,属于用户制造的意外. 代码错误,这类异常用于描述开发的代码错误,例如NPE,ILLARG,都属于程序员制造的BUG. 专有异常,多用于特定业务场景,用于描述指定作业出现意外情况无法预先处理.
异常设计的初衷是解决程序运行中的各种意外情况,且异常的处理效率比条件判断方式要低很多.
系统有千万并发,不可能还去考虑这些中规中矩的按部就班的方式,别忘了MVC本来就浪费很多资源,代码量增加很多. 业务系统也存在很多巨量任务处理的情况.但是那些任务都是原子性的,现在MVC中的controller和service可不是原子性的,不然为什么要区分这么多层呢. 如果那么在乎效率,考虑下重写Throwable的fillStackTrace方法.你要知道异常的开销大到底大在什么地方,fillStackTrace是一个native方法,会填充异常类内部的运行轨迹.
不要用异常进行业务逻辑处理
//这是一个非常典型的反例,也是一个误区.
/**
* 处理业务消息
* @param message 要处理的消息
*/
public void processMessage(Message<String> message) {
try{
// 处理消息验证
// 处理消息解析
// 处理消息入库
}catch(ValidateException e ){
// 验证失败
}catch(ParseException e ){
// 解析失败
}catch(PersistException e ){
// 入库失败
}
}
我们提倡在 业务处理 的时候,如果发现无法处理直接抛出异常即可. 而并不是在 逻辑处理 的时候,用异常来判断逻辑进行的状况.
/**
* 处理业务消息
* @param message 要处理的消息
*/
public void processMessage(Message<String> message) {
// 处理消息验证
if(!message.isValud()){
MessageLogService.log("消息校验失败"+message.errors())
return ;
}
// 处理消息解析
if(!message.parse()){
MessageLogService.log("消息解析失败"+message.errors())
return ;
}
// TODO ....
}
来源:my.oschina.net/c5ms/blog/1827907
干货分享
最近将个人学习笔记整理成册,使用PDF分享。关注我,回复如下代码,即可获得百度盘地址,无套路领取!
•001:《Java并发与高并发解决方案》学习笔记;•002:《深入JVM内核——原理、诊断与优化》学习笔记;•003:《Java面试宝典》•004:《Docker开源书》•005:《Kubernetes开源书》•006:《DDD速成(领域驱动设计速成)》•007:全部•008:加技术群讨论
加个关注不迷路
喜欢就点个"在看"呗^_^
评论