对Springboot项目进行统一异常处理

自定义统一异常处理

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

收破烂的小熊猫~

你的鼓励将是我创造最大的东西~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值