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

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

自定义统一异常处理

我们在做项目开发的时候,总是会有各类异常情况的发生。有些时候代码报错,返回的错误码又都是一致,也无法区别到具体的错误信息。且有异常就到处使用try-catch抛出,造成代码冗余

举个例子

我有一个添加页面

20200401134307494.png

这个添加页面接口的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);
    }
}

请求服务端接口返回的格式,例:

20200401134307494.png

自定义异常类

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")//扫描异常包所在的包

然后测试添加页面接口:新增一个已经存在的页面,进行测试。抛出已存在异常。

20200401134307494.png

后面如果有其他异常,只需要自定义枚举类,调用抛出异常代码就好啦

ExceptionCast.cast(CmsCode.CMS_ADDPAGE_EXISTS);

不可预知异常处理

不可预知异常,就是在编写代码的时候,没有预料到的。比如我们使用postman来测试添加接口,不携带请求参数进行请求,它会出现参数转换异常。

20200401134307494.png

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测试

20200401134307494.png

遇到其他运行时才发现的异常,我们以后也可以通过这种方式来处理。

有兴趣的老爷,可以关注我的公众号【一起收破烂】,回复【006】获取2021最新java面试资料以及简历模型120套哦~

相关文章
|
27天前
|
前端开发 Java
表白墙/留言墙 —— 初级SpringBoot项目,练手项目前后端开发(带完整源码) 全方位全步骤手把手教学
文章通过一个表白墙/留言墙的初级SpringBoot项目实例,详细讲解了如何进行前后端开发,包括定义前后端交互接口、创建SpringBoot项目、编写前端页面、后端代码逻辑及实体类封装的全过程。
55 3
表白墙/留言墙 —— 初级SpringBoot项目,练手项目前后端开发(带完整源码) 全方位全步骤手把手教学
|
27天前
|
前端开发 Java 数据安全/隐私保护
用户登录前后端开发(一个简单完整的小项目)——SpringBoot与session验证(带前后端源码)全方位全流程超详细教程
文章通过一个简单的SpringBoot项目,详细介绍了前后端如何实现用户登录功能,包括前端登录页面的创建、后端登录逻辑的处理、使用session验证用户身份以及获取已登录用户信息的方法。
112 2
用户登录前后端开发(一个简单完整的小项目)——SpringBoot与session验证(带前后端源码)全方位全流程超详细教程
|
20天前
|
Java 数据库连接 Maven
springBoot:项目建立&配置修改&yaml的使用&resource 文件夹(二)
本文档介绍了如何创建一个基于Maven的项目,并配置阿里云仓库、数据库连接、端口号、自定义启动横幅及多环境配置等。同时,详细说明了如何使用YAML格式进行配置,以及如何处理静态资源和模板文件。文档还涵盖了Spring Boot项目的`application.properties`和`application.yaml`文件的配置方法,包括设置数据库驱动、URL、用户名、密码等关键信息,以及如何通过配置文件管理不同环境下的应用设置。
|
26天前
|
NoSQL Java MongoDB
Springboot WebFlux项目结合mongodb进行crud
这篇文章介绍了如何使用Spring Boot WebFlux框架结合MongoDB进行基本的CRUD(创建、读取、更新、删除)操作,包括项目设置、实体类和Repository的创建、控制器的实现以及配置文件的编写。
39 0
Springboot WebFlux项目结合mongodb进行crud
|
18天前
|
JavaScript 前端开发 Java
解决跨域问题大集合:vue-cli项目 和 java/springboot(6种方式) 两端解决(完美解决)
这篇文章详细介绍了如何在前端Vue项目和后端Spring Boot项目中通过多种方式解决跨域问题。
229 1
解决跨域问题大集合:vue-cli项目 和 java/springboot(6种方式) 两端解决(完美解决)
|
1天前
|
JavaScript Java 项目管理
Java毕设学习 基于SpringBoot + Vue 的医院管理系统 持续给大家寻找Java毕设学习项目(附源码)
基于SpringBoot + Vue的医院管理系统,涵盖医院、患者、挂号、药物、检查、病床、排班管理和数据分析等功能。开发工具为IDEA和HBuilder X,环境需配置jdk8、Node.js14、MySQL8。文末提供源码下载链接。
|
24天前
|
Java Maven Android开发
eclipse如何导入springboot项目
本文介绍了如何在Eclipse中导入Spring Boot项目。
20 1
eclipse如何导入springboot项目
|
26天前
|
前端开发 Java Apache
Springboot整合shiro,带你学会shiro,入门级别教程,由浅入深,完整代码案例,各位项目想加这个模块的人也可以看这个,又或者不会mybatis-plus的也可以看这个
本文详细讲解了如何整合Apache Shiro与Spring Boot项目,包括数据库准备、项目配置、实体类、Mapper、Service、Controller的创建和配置,以及Shiro的配置和使用。
182 1
Springboot整合shiro,带你学会shiro,入门级别教程,由浅入深,完整代码案例,各位项目想加这个模块的人也可以看这个,又或者不会mybatis-plus的也可以看这个
|
18天前
|
Java Maven Spring
springboot学习一:idea社区版本创建springboot项目的三种方式(第三种为主)
这篇文章介绍了在IntelliJ IDEA社区版中创建Spring Boot项目的三种方法,特别强调了第三种方法的详细步骤。
67 0
springboot学习一:idea社区版本创建springboot项目的三种方式(第三种为主)
|
1天前
|
关系型数据库 MySQL Java
SpringBoot项目中mysql字段映射使用JSONObject和JSONArray类型
SpringBoot项目中mysql字段映射使用JSONObject和JSONArray类型
7 0