自定义统一异常处理
我们在做项目开发的时候,总是会有各类异常情况的发生。有些时候代码报错,返回的错误码又都是一致,也无法区别到具体的错误信息。且有异常就到处使用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套哦~