1.0 为什么需要异常
关于Java异常的介绍,可以参考我之前写过的一篇博客Java基础(8)-异常体系以及项目中的运用,里面提到了对于异常的一些思考以及如何使用异常。
为什么需要异常呢?
我认为可以从三个不同的角度来思考。
- 后端开发者:使得后端开发者通过日志快速定位到程序中出现问题的地方,并对出现的问题进行解决。
- 前端开发者:根据不同的异常类型,做相应的样式展示
- 用户:嗯嗯??用户不懂技术,并不需要知道异常类型是什么,只需要文字或图形化的提示。
比如说CSDN的上传文件提示网络错误,我这边的网络肯定没有问题,不过是内部服务器在忙或者是出错了,所以给我们抛出个网络错误的标识。
1.1 异常处理1.0版本
所以,异常贯穿于后端与前端之间,后端抛出了异常,前端需要根据异常来展示不同的页面展示给用户,这是最基本的异常处理方式:
1.2 异常处理2.0版本
在1.0模式下,异常实质上是由前端进行了捕捉和转化,可是异常既然由后端程序产生,那么最正确的做法是由后端来进行处理,这也就是引出了异常处理的一些方式了。目前简单实现的方法就是使用状态码来代替异常:
后端将异常捕获到并进行处理,将异常转化为状态码,前端根据状态码来进行相应的处理展示即可:
这样做的好处是:
1. 由后端进行异常处理,不会对外暴露出异常的具体信息,更加安全
2. 前端只需要根据后端约定好的状态码就可以进行相应页面的跳转,而不需要进行额外的异常处理,各端只需要处理好各端的逻辑即可,实现了高内聚,低耦合的目标。
1.3 异常处理第一种方式
第一种方式十分简单粗暴,只需要在service层可能产生异常的地方抛出异常,然后在controller层进行捕捉并处理,这适合异常的数量较少的情况:
下面结合一个模板消息模块简单地讲讲如何运用异常处理,这是SpringBoot/SSM的一个项目,处理的思路是在service层进行异常的抛出,并且统一在controller层去进行异常捕获和处理:
- ArgsLoseException
/**
* @Auther: ARong
* @Description: 参数缺失异常
**/
public class ArgsLoseException extends Exception {
public ArgsLoseException(){}
public ArgsLoseException(String message){super(message);}
}
- ArgsErrorException
/**
* @Auther: ARong
* @Description: 参数错误异常
**/
public class ArgsErrorException extends Exception {
public ArgsErrorException() {}
public ArgsErrorException(String messasge) {super(messasge);}
}
- TemplateMessageSerivceImp
/**
* @Auther: ARong
* @Description: 发送模板消息
**/
@Service
@Transactional
@Slf4j
public class TemplateMessageSerivceImp implements TemplateMessageSerivce {
@Autowired
private MessageUtil messageUtil;
/**
* @param messageEntity
* @param opeType
* @auther: Arong
* @description: 注册成功后发送
* @param: [messageEntity]
* @return: AjaxResult
*/
@Override
public AjaxResult sendMessage(MessageEntity messageEntity, String opeType) throws ArgsLoseException, ArgsErrorException {
Map map = MessageUtil.MessageMap(
messageEntity.getOpenId(),
messageEntity.getPage(),
messageEntity.getFormId()
);
if (opeType == null || "".equals(opeType.trim())) {
throw new ArgsLoseException("opeType参数缺失");
}
if (!"YES".equals(opeType)) {
throw new ArgsErrorException("opeType参数错误");
}
//...业务逻辑
return null;
}
}
此时重点在于,controller层应该如何去处理这些异常呢?
首先我们需要注意的是,不应该让异常直接暴露在用户眼中,而是要将异常转化为前端的一种状态码,让前端去做相应的处理,例如根据状态码去提示用户哪些操作是错误的,或者说是网络延迟等等,总之重点在于前后端约定状态码以及将异常转化为用户眼中的一种状态:
- TemplateMessageController
@Controller
@CrossOrigin
@RequestMapping(value = "/message")
@ResponseBody
@Slf4j
public class TemplateMessageController {
@Autowired
private TemplateMessageSerivce templateMessageSerivce;
/**
* @auther: Arong
* @description: 注册成功后发送
* @param: [messageEntity]
* @return: AjaxResult
*/
@PostMapping(value = "/sendMessage")
public AjaxResult sendMessage(
@ModelAttribute MessageEntity messageEntity,
@RequestParam(name = "opeType") String opeType //操作类型
) {
AjaxResult ajaxResult = null;
try {
ajaxResult = templateMessageSerivce.sendMessage(messageEntity, opeType);
} catch (Exception e) {
//打印异常栈堆路径
e.printStackTrace();
//分不同异常给前端返回不同的状态码(不要将异常暴露给前端,而是通过状态码让前端去反应)
if (e instanceof ArgsLoseException) {
//回传状态码501
return new AjaxResult(501, "传入opeType为空");
} else if (e instanceof ArgsErrorException) {
//回传状态码502
return new AjaxResult(502, "参数错误");
}
}
return ajaxResult;
},
}
这种异常处理方式在需要处理的自定义异常很少时已经够用了,但是随着自定义异常越来越多,那么就需要在controller中处理的异常也就越多,如下:
if (e instanceof ArgsLoseException) {
//回传状态码501
return new AjaxResult(501, "传入opeType为空");
} else if (e instanceof ArgsErrorException) {
//回传状态码502
return new AjaxResult(502, "参数错误");
} else if (e instanceof Xx1Exception) {
//...
} else if (e instanceof Xx2Exception) {
//...
} else if (e instanceof Xx3Exception) {
//...
}
所以,接下来需要优化异常处理的方式
1.4 异常处理第二种方式-全局异常处理
为了优化上述的异常处理产生过多的else if的问题,这里引入SpringBoot的全局异常处理,简化异常处理的代码,以下是一个Demo项目,不涉及具体的业务模块,只做为示范:
- AjaxResult 返回结果
/**
* @Auther: ARong
* @Description: 处理结果
*/
@Data
public class AjaxResult {
public AjaxResult() {
}
public AjaxResult(int code, String message) {
this.code = code;
this.message = message;
}
private int code;
private String message;
}
- GlobalException 全局异常,里边使用内部类定义自定义异常
/**
* @Auther: ARong
* @Description: 全局异常定义
*/
public class GlobalException {
/*
* @Author ARong
* @Description 参数缺少异常
**/
@Data
public static class ArgsLoseException extends RuntimeException{
private int code = 501;
private String message;
public ArgsLoseException(String message) {
this.message = message;
}
}
/*
* @Author ARong
* @Description 参数错误异常
**/
@Data
public static class ArgsErrorException extends RuntimeException{
private int code = 502;
private String message;
public ArgsErrorException(String message) {
this.message = message;
}
}
}
- GlobalExceptionHandler
使用@ControllerAdvice和@ExceptionHandler实现全局异常处理器
/**
* @Auther: ARong
* @Description: 全局异常处理器
*/
@ControllerAdvice
@ResponseBody
@Slf4j
public class GlobalExceptionHandler {
/*
* @Author ARong
* @Description 捕捉异常并进行处理
* @Param
* @return
**/
@ExceptionHandler(value = Exception.class)
public AjaxResult exceptionHandler(Exception e) {
// 打印异常调用栈
e.printStackTrace();
AjaxResult result = new AjaxResult();
int code = 500;
String message = "服务器出现错误";
// 如果是自定义异常则进行转化
if (e instanceof GlobalException.ArgsErrorException) {
// 参数错误异常
code = ((GlobalException.ArgsErrorException) e).getCode();
message = ((GlobalException.ArgsErrorException) e).getMessage();
}
if (e instanceof GlobalException.ArgsLoseException) {
// 参数缺失异常
code = ((GlobalException.ArgsLoseException) e).getCode();
message = ((GlobalException.ArgsLoseException) e).getMessage();
}
result.setCode(code);
result.setMessage(message);
return result;
}
}
- DemoController 控制层
/**
* @Auther: ARong
* @Description: 控制层
*/
@Controller
@ResponseBody
public class DemoController {
@Autowired
private DemoService demoService;
@RequestMapping(value = "/get")
public AjaxResult get(
@RequestParam(name = "id") String id
) {
AjaxResult result = demoService.get(id);
return result;
}
}
- DemoService 业务层
/**
* @Auther: ARong
* @Description: 业务层
*/
@Service
public class DemoService {
public AjaxResult get(String id) {
if ("bug".equals(id)) {
// 参数错误
throw new GlobalException.ArgsErrorException("参数错误");
}
if ("null".equals(id)) {
// 参数缺失
throw new GlobalException.ArgsLoseException("参数缺失");
}
return new AjaxResult(200, "OK");
}
}
在配置了全局异常处理器后,controller层就无须去一个一个地捕获并处理这些自定义的异常了,而都交给处理器去做对应的处理和返回状态码即可。启动项目,使用postMan简单测试一下:
- 正常情况
-
触发参数错误异常
-
触发参数缺失异常
经过上述步骤,就可以实现一个基本的全局异常处理了,这将大大地简少controller层中的冗余代码量,提高程序的可阅读性,剩下的比较重要的东西就是状态码应该如何约定以及前端如何去做跳转了。
Thanks
https://blog.csdn.net/Colton_Null/article/details/84592748