减少 try-catch ,这样做才叫优雅!
文章目录
情景引入 导入 问题 异常统一处理 出现的情景 开发环境 开发步骤 创建自定义异常 创建消息返回的包装实体 定义一系列的枚举返回信息 定义消息返回工具类 定义异常统一处理类(重点) 定义异常处理页面 效果 开发环境 正式环境 总结
情景引入
(我) 呼噜呼噜呼噜呼噜。。。。。。。。
(小白) 起床起床起床,,快点起床。。。
(我) 小白,又遇到什么事了,这么火急火燎的,年轻人,做事要稳重
(小白) 我遇到了一个很严重的问题,想让你指导指导我!
(我) 哎哟,这次这么虚心请教啦,那我不生气了,,你说,怎么了呢?
(小白) 就是,我在开发的过程中,因为是团队开发,所以,有时候逻辑就对不上,然后就会 莫名其妙的出现一些问题,并且显示的效果非常难堪,而且也不容易发现问题,每次都要查看后台才能知道问题,可是部署到服务器之后,都只能看Log日志来定位问题。
(我) 对呀,这项目开发本来就是一个团队的事情,这是很正常的事,有什么大惊小怪呢?
(小白) 所以,我想着,有没有什么办法,可以针对系统中的异常(未知和已知)能够友好的进行显示呢?这样,我们在交流的时候也相对更加方便呀,否则,总是看着一堆乱七八糟的错误,挺心烦的。
(我) 我好像理解了你的意思。你就是想着,能对系统中的异常能够友好显示或者说能方便你们团队开发嘛。
(小白) 对的对的,就是这么个意思。
(我) 这当然有了,而且你现在遇到的这个问题,其实在每个系统中都应该有进行处理,虽然它比较简单,但是不容小视,也有很多重要的东西的呢~那就好好听课吧!
(小白) 真开心,,,,,迫不及待了
导入
问题
针对Web项目来说,我们都知道,一般都是一个团队进行开发,而不会是一个人单打独斗,并且开发团队还有前后端的人员,那么有一定的规范就是必不可少的。
我们可能都遇到过一个问题,就是开发环境和正式上线的环境是有很大的差别的。开发环境是针对我们开发人员,而正式环境是一种以用户的角度来审视我们的整个系统。想想一个问题,如果遇到了我们在开发中没有碰到的异常,而用户却发现了,用户体验是不是会非常不好,而且这是我们的一个大忌。。
既然如此,我们也知道,开发过程中,有如此多的异常可能会出现,那么里面就包含着我们已经考虑到了的,然而还有一些隐藏的异常却是我们可能忽视的,所以,为了能够将那些潜在的异常不被用户直接发现,而影响用户体验,这---------异常统一处理,,,就必不可少!
异常统一处理
定义:
简单点说,就是针对我们系统中的异常,给予一定规范的处理结果。(比如,默认的情况,就是将异常堆栈信息直接打印到页面,然而这种是极其丑陋的)
出现的情景
开发人员预测得到的自定义异常:在开发中,开发人员对某些可能出现的情形是可以预知的,这时候是一种主动处理的状态。 开发人员无法预测的系统异常:在开发中,存在着开发人员无法全面思考到的异常,那么这时候就是一种潜在性的可能异常状态。 前端和后台交互异常:由于前后端的分离,而且前后端的开发方向也存在着差异,那么就有可能导致异常的出现。
开发环境
windows 7 + 渣渣笔记本 IDEA + SpringBoot + Mybatis +Mysql
开发步骤
创建自定义异常
分析:在系统中,存在着系统异常和我们人为的自定义异常,所以,为了能够有效的针对不同异常进行处理,那么拥有我们自定义的异常类是非常有必要的。
package com.hnu.csapp.exception;
/**
* @ Author :scw
* @ Description:自定义异常,为了区分系统异常和更方便系统的特定一些处理
* @ Modified By:
* @Version: 1
*/
public class MyException extends RuntimeException{
//错误码
private Integer code;
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public MyException(String message) {
super(message);
}
/**
* 构造器重载,主要是自己考虑某些异常自定义一些返回码
* @param code
* @param message
*/
public MyException(Integer code,String message) {
super(message);
this.code = code;
}
}
创建消息返回的包装实体
分析:对于后台返回给前端的数据来说,我们很多情况都是返回的JSON格式的数据(当然,并不是局限于这一种),那么JSON是一种格式化的形式。
所以,我们应该有效的针对这样的形式来给予一定的返回规范,这样也方便前端对于我们返回数据的解析。
比如:很多情况一般是如下的格式:
{
"retCode": 200, //通过状态码可以得到消息是否返回正常,然后再决定是否去解析data域的内容
"data": { //返回的数据内容
}
"retMes": success //返回的提示内容
}
所以,我们可以定义如下的类:
package com.hnu.csapp.exception;
/**
* @ Author :scw
* @ Description:异常处理实体包装类,自己用泛型进行写,扩展性强点
* @ Modified By:
* @Version: 1
*/
public class Result<T> {
//返回码
private Integer code;
//返回消息
private String msg;
//返回数据
private T data;
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
定义一系列的枚举返回信息
分析:在系统中,我们应该有统一的某些编码对应某些内容,这样能够方便开发人员进行及时的处理。
package com.hnu.csapp.exception;
/**
* @ Author :scw
* @ Description:自定义一些返回状态码,便于本系统的使用,自己先定义如下的,有需要就后续补充
* @ Modified By:
* @Version: 1
*/
public enum ResultEnum {
/**
* 成功.: 200 (因为http中的状态码200一般都是表示成功)
*/
SUCCESS(200,"成功"),
/**
* 系统异常. ErrorCode : -1
*/
SystemException(-1,"系统异常"),
/**
* 未知异常. ErrorCode : 01
*/
UnknownException(01,"未知异常"),
/**
* 服务异常. ErrorCode : 02
*/
ServiceException(02, "服务异常"),
/**
* 业务错误. ErrorCode : 03
*/
MyException(03,"业务错误"),
/**
* 提示级错误. ErrorCode : 04
*/
InfoException(04, "提示级错误"),
/**
* 数据库操作异常. ErrorCode : 05
*/
DBException(05,"数据库操作异常"),
/**
* 参数验证错误. ErrorCode : 06
*/
ParamException(06,"参数验证错误");
private Integer code;
private String msg;
ResultEnum(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public Integer getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
定义消息返回工具类
分析:对于消息的返回,这是一个非常普通的工作,所以,我们可以将其封装一个工具类,能够进行有效代码的封装,减少多余的代码
package com.hnu.csapp.exception;
/**
* @ Author :scw
* @ Description:返回消息处理的工具类,主要是处理操作成功和失败的一些内容
* @ Modified By:
* @Version: 1
*/
public class ResultUtil {
/**
* 操作成功的处理流程
* @param object
* @return
*/
public static Result getSuccess(Object object){
Result result = new Result();
//设置操作成功的返回码
result.setCode(200);
//设置操作成功的消息
result.setMsg("成功");
result.setData(object);
return result;
}
/**
* 重载返回成功的方法,因为有时候我们不需要任何的消息数据被返回
* @return
*/
public static Result getSuccess(){
return getSuccess(null);
}
/**
* 操作失败的处理流程
* @param code 错误码
* @param msg 错误消息
* @param o 错误数据(其实这个一般都不需要的,因为都已经返回失败了,数据都没必要返回)
* @return
*/
public static Result getError(Integer code, String msg, Object o){
Result result = new Result();
result.setCode(code);
result.setMsg(msg);
result.setData(o);
return result;
}
/**
* 重载,操作失败的方法(因为操作失败一般都不需要返回数据内容)
* @param code
* @param msg
* @return
*/
public static Result getError(Integer code, String msg){
return getError(code, msg, null);
}
}
定义异常统一处理类(重点)
分析:这是如何实现异常统一处理的关键地方,而且我也将不同的处理情形,进行了分开注释,所以,大家一定可以认真的看代码,我相信你一定能够明白。
package com.hnu.csapp.exception;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
/**
* @ Author :scw
* @ Description:异常统一处理类,方便用户可以更加友好的看到错误信息
* @ Modified By:
* @Version: 1
*/
@ControllerAdvice
public class ExceptionHandle {
//增加异常日志打印
private final static Logger logger = LoggerFactory.getLogger(ExceptionHandle.class);
//设置异常错误的页面
public static final String DEFAULT_ERROR_VIEW = "error";
/**
* 以json的格式进行返回内容(开发环境一般个人是用这个比较好)
* @param e
* @return
*/
@ExceptionHandler(Exception.class)
@ResponseBody
public Object handle(HttpServletRequest req, Exception e){
//如果是自定义的异常
if(e instanceof MyException){
MyException myException = (MyException)e;
return ResultUtil.getError(myException.getCode(),myException.getMessage());
}else{
//如果是系统的异常,比如空指针这些异常
logger.error("【系统异常】={}",e);
return ResultUtil.getError(ResultEnum.SystemException.getCode(),ResultEnum.SystemException.getMsg());
}
}
/**
* 判断是否是Ajax的请求
* @param request
* @return
*/
public boolean isAjax(HttpServletRequest request){
return (request.getHeader("X-Requested-With") != null
&&
"XMLHttpRequest".equals(request.getHeader("X-Requested-With").toString()));
}
/*
//备注:
//这个是正式项目完成之后的错误统一处理(开发情况先用上面的的)
//我们在开发过程中还是用json格式的会好一些,要不然看错误麻烦
@ExceptionHandler(value = Exception.class)
public ModelAndView defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {
e.printStackTrace();
//判断是否是Ajax的异常请求(如果是Ajax的那么就是返回json格式)
if(isAjax(req)){
//如果是自定义的异常
if(e instanceof MyException){
MyException myException = (MyException)e;
return ResultUtil.getError(myException.getCode(),myException.getMessage());
}else{
//如果是系统的异常,比如空指针这些异常
logger.error("【系统异常】={}",e);
return ResultUtil.getError(ResultEnum.SystemException.getCode(),ResultEnum.SystemException.getMsg());
}
}else{
//如果是系统内部发生异常,那么就返回到错误页面进行友好的提示
ModelAndView mav = new ModelAndView();
//这些就是要返回到页面的内容(其实不用都行,反正用户也不懂,没必要在页面显示都可以,先写着吧)
mav.addObject("exception", e);
mav.addObject("url", req.getRequestURL());
mav.setViewName(DEFAULT_ERROR_VIEW);
return mav;
}
}
*/
}
定义异常处理页面
分析:这个的话,其实主要是在正式环境才有,因为我们在测试环境的时候,一般都还是会将错误以JSON或者堆栈的格式显示在页面,而当上线的时候,那么就一定要有一个统一的错误页面,这样就能够让用户发现不了是系统出现了哪些问题。
效果
开发环境
正式环境
分析:当出现异常的时候,则显示如下的页面。(该页面是参考一个博友的,感觉挺有意思,,老司机~)
总结
异常统一处理,或许我们看起来实现非常简单,然而,其他它包含的思想却是一种大局思想,这是我们开发人员在开发过程中都应该关注的点,我们并不是只需要关注我们每个人开发的那点任务,而要以一种全局的角度去审视整个项目,这样也能够提升我们开问题的高度。
异常统一处理,是每个项目都存在的,只是可能实现的方式不一样而已,或者显示的效果不一样而已,这些都不是关键的地方。
异常统一处理这个问题,并不是很难,但是这个可以帮助我们延伸到其他的一些相关的开发层面的知识,比如:
登录拦截 权限管理 日志管理 事务处理 数据控制和过滤 。。。
所以,我们应该学会从一个问题,发散的看到相关类似的问题,这样,我们的系统才会更加健壮,高效和可扩展性强。