自定义统一异常处理
我们在做项目开发的时候,总是会有各类异常情况的发生。有些时候代码报错,返回的错误码又都是一致,也无法区别到具体的错误信息。且有异常就到处使用try-catch抛出,造成代码冗余。
举个例子:
我有一个添加页面
这个添加页面接口的service层代码是这样写的:
/** * 新增页面 * @param cmsPage:serice层进行业务逻辑的处理,传递的是页面填写的信息内容。 * @return */ public CmsPageResult add(CmsPage cmsPage){ CmsPage cmsPage1= cmsPageRepository.findByPageNameAndSiteIdAndPageWebPath(cmsPage.getPageName(),cmsPage.getSiteId(),cmsPage.getPageWebPath()); if(cmsPage1==null){ cmsPage.setPageId(null); CmsPage save=cmsPageRepository.save(cmsPage); return new CmsPageResult(CommonCode.SUCCESS,save); } return new CmsPageResult(CommonCode.FAIL,null); }
上面的代码,只要操作不成功仅向用户返回“错误代码:11111,失败信息:操作失败”,无法区别具体的错误信息。service方法在执行过程出现异常在哪捕获?在service中需要都加try/catch,如果在controller也需要添加 try/catch,代码冗余严重且不易维护。
解决方案:
1、在Service方法中的编码顺序是先校验判断,有问题则抛出具体的异常信息,最后执行具体的业务操作,返回成功信息。
2、在统一异常处理类中去捕获异常,无需controller捕获异常,向用户返回统一规范的响应信息。 代码模板如下。
public CmsPageResult add(CmsPage cmsPage){ //添加时,先校验cmsPage是否为空 if(cmsPage == null){ //抛出异常,非法请求 //... } //根据页面名称查询(页面名称已在mongodb创建了唯一索引) CmsPage cmsPage1 = cmsPageRepository.findByPageNameAndSiteIdAndPageWebPath(cmsPage.getPageName(), cmsPage.getSiteId(), cmsPage.getPageWebPath()); //校验页面是否存在,已存在则抛出异常 if(cmsPage1 !=null){ //抛出异常,已存在相同的页面名称 //... } cmsPage.setPageId(null);//添加页面主键由spring data 自动生成 CmsPage save = cmsPageRepository.save(cmsPage); //返回结果 CmsPageResult cmsPageResult = new CmsPageResult(CommonCode.SUCCESS,save); return cmsPageResult; }
接下来我会对不同类型的异常做处理。
首先,补充一下可预知异常和不可知异常的知识。
可预知异常:指的是我们在开发的时候,知道它可能会发生的异常,我们在代码中手动抛出本系统定义的特定异常类型。如,商品信息已存在。数据格式错误等等 。由于是我们自己抛出的异常,通常异常信息比较齐全。
不可预知异常:通常是由于系统出现bug、或一些不要抗拒的错误(比如网络中断、服务器宕机等),异常类型为 RuntimeException类型(运行时异常)。
可预知异常处理
先定义好,请求服务响应码接口:
package com.xuecheng.framework.model.response; /** * Created * 10000-- 通用错误代码 * 22000-- 媒资错误代码 * 23000-- 用户中心错误代码 * 24000-- cms错误代码 * 25000-- 文件系统 */ public interface ResultCode { //操作是否成功,true为成功,false操作失败 boolean success(); //操作代码 int code(); //提示信息 String message(); }
服务端回应请求类,可以根据需要自己定义服务端回应格式。
package com.xuecheng.framework.model.response; import lombok.Data; import lombok.NoArgsConstructor; import lombok.ToString; /** * * @Description: * @Date:Created * @Modified By: */ @Data @ToString @NoArgsConstructor public class ResponseResult implements Response { //操作是否成功 boolean success = SUCCESS; //操作代码 int code = SUCCESS_CODE; //提示信息 String message; public ResponseResult(ResultCode resultCode){ this.success = resultCode.success(); this.code = resultCode.code(); this.message = resultCode.message(); } public static ResponseResult SUCCESS(){ return new ResponseResult(CommonCode.SUCCESS); } public static ResponseResult FAIL(){ return new ResponseResult(CommonCode.FAIL); } }
请求服务端接口返回的格式,例:
自定义异常类
import com.xuecheng.framework.model.response.ResultCode; /** * @Author youjp * @Description //TODO=自定义异常类 * @Date 2020-07-24$ 15:08$ * @throw **/ public class CustomException extends RuntimeException{ private ResultCode resultCode; public CustomException(ResultCode resultCode) { //异常信息为错误代码+异常信息 super("错误代码:"+resultCode.code()+"错误信息:"+resultCode.message()); this.resultCode = resultCode; } public ResultCode getResultCode() { return resultCode; } }
异常抛出类
package com.xuecheng.framework.exception; import com.xuecheng.framework.model.response.ResultCode; /** * @Author youjp * @Description //TODO=异常抛出类 * @Date 2020-07-24$ 15:12$ * @throw **/ public class ExceptionCast { /** * 使用此静态方法抛出自定义异常 * @param resultCode */ public static void cast(ResultCode resultCode){ throw new CustomException(resultCode); } }
异常捕获类
ControllerAdvice,是Spring3.2提供的新注解,它是一个Controller增强器,可对controller中被@RequestMapping注解的方法加一些逻辑处理。最常用的就是异常处理。 需要配合@ExceptionHandler使用。当将异常抛到controller时,可以对异常进行统一处理,规定返回的json格式或是跳转到一个错误页面
使用 @ControllerAdvice和@ExceptionHandler注解来捕获指定类型的异常。
package com.xuecheng.framework.exception; import com.xuecheng.framework.model.response.ResponseResult; import com.xuecheng.framework.model.response.ResultCode; 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; /** * @Author youjp * @Description //TODO= 异常捕获类:使用 @ControllerAdvice和@ExceptionHandler注解来捕获指定类型的异常 * @Date 2020-07-24$ 15:16$ * @throw **/ @ControllerAdvice public class ExceptionCatch { private static final Logger log= LoggerFactory.getLogger(ExceptionCatch.class); /** * 捕获customException异常 * @param e * @return */ @ExceptionHandler(CustomException.class) @ResponseBody public ResponseResult customException(CustomException e){ log.error("catch exception : {}\r\nexception: ",e.getMessage(), e); ResultCode resultCode=e.getResultCode(); ResponseResult responseResult=new ResponseResult(resultCode); return responseResult; } }
这里我们已经实现了,可预知异常的捕获了。接下来我们只需要通过枚举类去定义自己想要的异常提醒格式。
package com.xuecheng.framework.domain.cms.response; import com.xuecheng.framework.model.response.ResultCode; import lombok.ToString; /** * Created by youjp */ @ToString public enum CmsCode implements ResultCode { CMS_ADDPAGE_EXISTSNAME(false,24001,"页面名称已存在!"), //操作代码 boolean success; //操作代码 int code; //提示信息 String message; private CmsCode(boolean success, int code, String message){ this.success = success; this.code = code; this.message = message; } @Override public boolean success() { return success; } @Override public int code() { return code; } @Override public String message() { return message; } }
异常处理测试
然后在services层,需要抛出异常的地方,调用抛出异常的代码即可。
ExceptionCast.cast(CmsCode.CMS_ADDPAGE_EXISTS);
如,我这里添加页面时,判定得到页面重复,抛出自定义异常。
/** * 新增页面 * @param cmsPage * @return */ public CmsPageResult add(CmsPage cmsPage){ CmsPage cmsPage1= cmsPageRepository.findByPageNameAndSiteIdAndPageWebPath(cmsPage.getPageName(),cmsPage.getSiteId(),cmsPage.getPageWebPath()); if (cmsPage1!=null){ //校验页面是否存在,已存在则抛出异常 ExceptionCast.cast(CmsCode.CMS_ADDPAGE_EXISTS); //**********重点部分 } cmsPage.setPageId(null); CmsPage save=cmsPageRepository.save(cmsPage); return new CmsPageResult(CommonCode.SUCCESS,save); }
启动工程,扫描到异常捕获的类ExceptionCatch
在springBoot的启动类中添加
@ComponentScan(basePackages="com.xuecheng.framework")//扫描异常包所在的包
然后测试添加页面接口:新增一个已经存在的页面,进行测试。抛出已存在异常。
后面如果有其他异常,只需要自定义枚举类,调用抛出异常代码就好啦
ExceptionCast.cast(CmsCode.CMS_ADDPAGE_EXISTS);
不可预知异常处理
不可预知异常,就是在编写代码的时候,没有预料到的。比如我们使用postman来测试添加接口,不携带请求参数进行请求,它会出现参数转换异常。
org.springframework.http.converter.HttpMessageNotReadableException此异常是springMVC在进行参数转换时报的错误。
上边的响应信息在客户端是无法解析的,客户端无法理解时什么错误,我们也应该按照定义的错误格式返回信息。
针对上边的问题其解决方案是:
1、我们在map中配置HttpMessageNotReadableException和错误代码。
2、在异常捕获类中对Exception异常进行捕获,并从map中获取异常类型对应的错误代码,如果存在错误代码则返回此错误,否则统一返回99999错误。
具体的开发实现如下:
1、在通用错误代码类CommCode中配置非法参数异常
package com.xuecheng.framework.model.response; import lombok.ToString; /** * * @Description: * @Date:Created in 2018/1/24 18:33. * @Modified By: */ @ToString public enum CommonCode implements ResultCode{ SUCCESS(true,10000,"操作成功!"), FAIL(false,11111,"操作失败!"), UNAUTHENTICATED(false,10001,"此操作需要登陆系统!"), UNAUTHORISE(false,10002,"权限不足,无权操作!"), INVALID_PARAM(false,10003,"非法参数!"), SERVER_ERROR(false,99999,"抱歉,系统繁忙,请稍后重试!"); // private static ImmutableMap<Integer, CommonCode> codes ; //操作是否成功 boolean success; //操作代码 int code; //提示信息 String message; private CommonCode(boolean success,int code, String message){ this.success = success; this.code = code; this.message = message; } @Override public boolean success() { return success; } @Override public int code() { return code; } @Override public String message() { return message; } }
2、在异常捕获类中配置 HttpMessageNotReadableException 为非法参数异常。
异常捕获类代码如下:
package com.xuecheng.framework.exception; import com.google.common.collect.ImmutableMap; import com.xuecheng.framework.model.response.CommonCode; import com.xuecheng.framework.model.response.ResponseResult; import com.xuecheng.framework.model.response.ResultCode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import rx.exceptions.Exceptions; /** * @Author youjp * @Description //TODO= 异常捕获类:使用 @ControllerAdvice和@ExceptionHandler注解来捕获指定类型的异常 * @Date 2020-07-24$ 15:16$ * @throw **/ @ControllerAdvice public class ExceptionCatch { private static final Logger log = LoggerFactory.getLogger(ExceptionCatch.class); //使用exceptions存放异常类型和错误代码的映射,ImmutabelMap的特点是一旦创建不可改变,并且线程安全 private static ImmutableMap<Class<? extends Throwable>, ResultCode> EXCEPTIONS; //使用builder来构建一个异常类型和错误代码的异常 protected static ImmutableMap.Builder<Class<? extends Throwable>, ResultCode> builder = ImmutableMap.builder(); static{ //在这里加入一些基础的异常类型判断 builder.put(HttpMessageNotReadableException.class,CommonCode.INVALID_PARAM); } /** * 捕获customException异常,可预知异常处理 * * @param e * @return */ @ExceptionHandler(CustomException.class) @ResponseBody public ResponseResult customException(CustomException e) { log.error("catch exception : {}\r\nexception: ", e.getMessage(), e); ResultCode resultCode = e.getResultCode(); ResponseResult responseResult = new ResponseResult(resultCode); return responseResult; } /** * 不可预知异常捕获 :捕获exception异常 * * @param exception * @return */ @ExceptionHandler(Exception.class) @ResponseBody public ResponseResult exception(Exception exception) { //记录日志 log.error("catch exception:{}", exception.getMessage()); if (EXCEPTIONS == null) EXCEPTIONS = builder.build(); final ResultCode resultCode = EXCEPTIONS.get(exception.getClass()); final ResponseResult responseResult; if (resultCode != null) { responseResult = new ResponseResult(resultCode); } else { responseResult = new ResponseResult(CommonCode.SERVER_ERROR); } return responseResult; } }
再次使用postman测试
遇到其他运行时才发现的异常,我们以后也可以通过这种方式来处理。
有兴趣的老爷,可以关注我的公众号【一起收破烂】,回复【006】获取2021最新java面试资料以及简历模型120套哦~