专栏首页技术那些事Java统一异常处理(配置文件集中化定义)

Java统一异常处理(配置文件集中化定义)

0、前言

无论任何项目,都避免不了在运行期间出现的一些异常,并伴随着因业务逻辑的需要而给出相应的提示,使得系统变得更加友好,这类提示处理,我们统称为异常处理(exceptiona handling)。

在项目中异常处理所抛出的异常码、异常提示 ,都需要进行一定的封装,以确保异常的统一,提高程序的可维护性。而不是随心所欲的来进行异常提醒,如:一些硬编码异常信息(throw new Exception("系统处理异常")),随着想项目的变大、开发人员的不同,这些异常码可能会五花八门,没有统一标准,给用户提示、给开发很容易带来些许的困惑。

本文不是讲解如何正确使用try、catch、finally等进行异常捕获,而是就异常码、异常信息进行封装,通过配置文件进行集中化定义,来统一异常处理,让异常处理变得更标准化、统一化,方便维护、管理。

1、异常处理

异常处理,又称为错误处理,提供了处理程序运行时出现的任何意外或异常情况的方法。异常处理使用 try、catch 和 finally 关键字来尝试可能未成功的操作,处理失败,以及在事后清理资源。

异常发生的原因有很多,通常包含以下几大类:

  • 用户输入了非法数据。
  • 要打开的文件不存在。
  • 网络通信时连接中断,或者JVM内存溢出。

这些异常有的是因为用户错误引起,有的是程序错误引起的。

要理解Java异常处理是如何工作的,你需要掌握以下三种类型的异常:

  • 检查性异常:最具代表的检查性异常是用户错误或问题引起的异常,这是程序员无法预见的。例如要打开一个不存在文件时,一个异常就发生了,这些异常在编译时不能被简单地忽略。
  • 运行时异常: 运行时异常是可能被程序员避免的异常。与检查性异常相反,运行时异常可以在编译时被忽略。
  • 错误: 错误不是异常,而是脱离程序员控制的问题。错误在代码中通常被忽略。例如,当栈溢出时,一个错误就发生了,它们在编译也检查不到的。

所有的异常类是从 java.lang.Exception 类继承的子类。

Exception 类是 Throwable 类的子类。除了Exception类外,Throwable还有一个子类Error 。

Java 程序通常不捕获错误。错误一般发生在严重故障时,它们在Java程序处理的范畴之外。

Error 用来指示运行时环境发生的错误。例如,JVM 内存溢出。一般地,程序不会从错误中恢复。

异常类有两个主要的子类:IOException 类和 RuntimeException 类。

本文就针对处理的是Exception类异常。

2、统一异常处理

本实战中将异常码、异常信息进行封装,通过properties配置文件进行集中化定义,并支持国际化异常码的定义,来统一异常处理。

2.1 消息结果的封装

全系统统一返回的数据格式为:

{    "statusCode":"00000000",    "msg":"成功",    "data": {        "username":"xcbeyond",        "sex":"男",        "age":18    }}

标准的json字符串,statusCode:状态码,msg:提示信息,data:结果数据(以实际数据而定的json)。

定义一个实体类Result,用来封装消息返回数据,如下:

package com.xcbeyond.execption.data;
import com.alibaba.fastjson.JSONObject;import com.fasterxml.jackson.annotation.JsonInclude;import com.xcbeyond.execption.util.ObjectUtils;import java.io.Serializable;
 /** * 返回结果 * @Auther: xcbeyond * @Date: 2019/5/24 17:55 */@JsonInclude(value = JsonInclude.Include.NON_NULL)public class Result implements Serializable {    //状态码    private String statusCode;    //提示信息    private String msg;    //结果数据    private Object data;     public Result() {
    }
    public Result(String statusCode, String msg) {        this.statusCode = statusCode;        this.msg = msg;    }
    public String getStatusCode() {        return statusCode;    }
    public void setStatusCode(String statusCode) {        this.statusCode = statusCode;    }     public String getMsg() {        return msg;    }
    public void setMsg(String msg) {        this.msg = msg;    }

    public Object getData() {        return data;    }

    public void setData(Object data) {        this.data = data;    }

    /**     * 重写toString方法,让Result对象以json字符串形式存在     * @return     *  Json字符串     */
    @Override
    public String toString() {        JSONObject json = new JSONObject();        json.put("statusCode", this.statusCode);        json.put("msg", this.msg);        if (null != this.data) {            json.put("data", ObjectUtils.modelToMap(this.data));
        }        return json.toJSONString();    }
}

2.2 异常码、异常信息配置文件定义化 *

将异常码、异常信息统一集中定义到properties配置文件中,避免硬编码在代码中,方便维护,便于后期变动统一修改。异常码文件位于项目resources目录下\resources\error\,如下:

异常码文件名统一格式:模块名_error_zh_CN.properties/

模块名_error_en_US.properties(zh_CN、en_US区分国际化定义)

异常码统一格式定义,具体以实际项目情况而定,可参考如下标准定义:

#错误码定义8位#  ┌─1─┬─2─┬─3─┬─4─┬─5─┬─6─┬─7─┬─8─┐#  │预留 │C/B端│   模块名   │错误码  │#  └─1─┴─2─┴─3─┴─4─┴─5─┴─6─┴─7─┴─8─┘#第1位:#   预留#第2位:#   C/B端(客户端或服务端) 0-服务端, 1-客户端#第3、4位:#   2位模块名#第5、6、7、8位:#   4位错误码(后4位),各位含义如下:#   第5为:类别,可按业务分类、接口分类等划分,0-9#   第6-8位:3位具体错误码#       第6位:按以下含义定义分类:#           0:预留#           1:非空检查类提示,数据为空、不为空检查#           2:有效性检查提示,数据有效性检查(如格式、存在、不存在、不在有效值范围等)#           3:业务逻辑类提示,合法性/一致性/完整性检查提示#           4:预留/待扩展定义#           5:预留/待扩展定义#           6:预留/待扩展定义#           7:预留/待扩展定义#           8:预留/待扩展定义#           9:预留/待扩展定义#       第7、8位:二位顺序标号,00-99

封装异常码工具类ErrorUtils,用于从异常码文件中获取错误提示信息等,如下:

package com.xcbeyond.execption.util;
import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.context.NoSuchMessageException;import org.springframework.context.support.ResourceBundleMessageSource;import org.springframework.core.io.Resource;import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import java.util.ArrayList;import java.util.List;import java.util.Locale;
/** * 错误工具类,用于从错误码配置文件中获取错误提示信息等 * 支持国际化。 * @Auther: xcbeyond * @Date: 2019/5/24 17:16 */public class ErrorUtils {    private static final Logger log = LoggerFactory.getLogger(ErrorUtils.class);     private static ResourceBundleMessageSource resourceBundle = new ResourceBundleMessageSource();    private static final String ZH_LANGUAGE = "CHINESE";    private static final String EN_LANGUAGE = "AMERICAN/ENGLISH";    private static final String FILE_KEYWORKS = "error";    private static final String JAR_RESOURCES = "classpath*:error/*error*.properties";    private static final String RESOURCES = "classpath*:*error*.properties";     /**     * 静态代码块。     * 用于加载错误码配置文件     */    static {        try {            PathMatchingResourcePatternResolver patternResolver = new PathMatchingResourcePatternResolver();            List nameListCn = new ArrayList();
            Resource[] jarResources = patternResolver.getResources(JAR_RESOURCES);            if (log.isDebugEnabled())                log.debug("加载CLASSPATH下[error]文件夹错误码配置文件[" + jarResources.length + "]");            for (Resource resource : jarResources) {                String fileName = resource.getFilename();                fileName = fileName.substring(0, fileName.indexOf(FILE_KEYWORKS) + 5);                if (log.isDebugEnabled())                    log.debug("加载[error]下错误码配置文件[" + resource.getFilename() + "][" + fileName + "]");                nameListCn.add("error/" + fileName);            }
            Resource[] resources = patternResolver.getResources(RESOURCES);            if (log.isDebugEnabled())                log.debug("加载CLASSPATH根目录错误码配置文件[" + resources.length + "]");            for (Resource resource : resources) {                String fileName = resource.getFilename();                fileName = fileName.substring(0, fileName.indexOf(FILE_KEYWORKS) + 5);                if (log.isDebugEnabled())                   log.debug("加载错误码配置文件[" + resource.getFilename() + "][" + fileName + "]");                nameListCn.add(fileName);            }
            resourceBundle.setBasenames((String[]) nameListCn.toArray(new String[0]));            resourceBundle.setCacheSeconds(5);        } catch (Throwable localThrowable) {
        }    }    /**     * 获取错误码描述信息     * @param errCode 错误码     * @return     */    public static String getErrorDesc(String errCode) {        return getErrorDesc(errCode, "CHINESE");    }     /**     * 获取错误码描述信息     * @param errCode   错误码     * @param userLang  国际化语言     * @return     */    public static String getErrorDesc(String errCode, String userLang) {        String errDesc = "";        try {            if ((null == userLang) || (ZH_LANGUAGE.equals(userLang))) {                errDesc = resourceBundle.getMessage(errCode, null, Locale.SIMPLIFIED_CHINESE);            } else if (EN_LANGUAGE.equals(userLang)) {                errDesc = resourceBundle.getMessage(errCode, null, Locale.US);            }        } catch (NoSuchMessageException localNoSuchMessageException) {
       }        return errDesc;    }    /**     * 获取错误码描述信息     * @param errCode   错误码     * @param args  错误描述信息中参数     * @return     */    public static String getParseErrorDesc(String errCode, String[] args) {        return getParseErrorDesc(errCode, ZH_LANGUAGE, args);    }
    /**     * 获取错误码描述信息     * @param errCode   错误码     * @param userLang  国际化语言     * @param args  错误描述信息中参数     * @return     */    public static String getParseErrorDesc(String errCode, String userLang, String[] args) {        String errDesc = "";        try {            if ((null == userLang) || (ZH_LANGUAGE.equals(userLang)))                errDesc = resourceBundle.getMessage(errCode, args, Locale.SIMPLIFIED_CHINESE);            else if (EN_LANGUAGE.equals(userLang))                errDesc = resourceBundle.getMessage(errCode, args, Locale.US);        } catch (NoSuchMessageException localNoSuchMessageException) {        }        return errDesc;    }}

2.3 异常类封装

本文封装两类异常:

  • 系统级异常:指系统级别的,如:网络通信时连接中断、系统连接、超时等异常
  • 业务处理异常:指用户输入了非法数据等业务逻辑存在的异常

(其他类别异常,可自行封装,如SQL类异常)

(1)异常基类BaseException,所有异常类都继承此类,如下:

package com.xcbeyond.execption;
import java.io.Serializable;
/** * 异常基类 * @Auther: xcbeyond * @Date: 2019/5/28 16:27 */public class BaseException extends RuntimeException implements Serializable {    public BaseException() {    }
    public BaseException(String message) {        super(message);    }
    public BaseException(Throwable cause) {        super(cause);    }
    public BaseException(String message, Throwable cause) {        super(message, cause);    }}

(2)系统级异常SystemException,如下:

package com.xcbeyond.execption;
import com.xcbeyond.execption.data.Result;
/** * 系统级异常。 *  指系统级别的,如:网络通信时连接中断、系统连接、超时等异常 * @Auther: xcbeyond * @Date: 2019/5/28 16:26 */public class SystemException extends BaseException{    private Result result = new Result();
    public SystemException(Result result) {        super(result.getStatusCode()+ ":" + result.getMsg());        this.result = result;    }
    public SystemException(String code, String msg) {        super(code + ":" + msg);        this.result.setStatusCode(code);        this.result.setMsg(msg);    }
    public SystemException(Result result, Throwable cause) {        super(result.getStatusCode() + ":" + result.getMsg(), cause);        this.result = result;    }
    public SystemException(String code, String msg, Throwable cause) {        super(code + ":" + msg, cause);        this.result.setStatusCode(code);        this.result.setMsg(msg);    }
    public Result getResult() {        return result;    }
    public void setResult(Result result) {        this.result = result;    }}

(3)业务处理异常类BusinessException,如下:

package com.xcbeyond.execption;
import com.xcbeyond.execption.data.Result;

/** * 业务处理异常 *  指用户输入了非法数据等业务逻辑存在的异常 * @Auther: xcbeyond * @Date: 2018/12/24 11:20 */public class BusinessException extends BaseException {    private Result result = new Result();
    public BusinessException(Result result) {        super(result.getStatusCode()+ ":" + result.getMsg());        this.result = result;    }
    public BusinessException(String code, String msg) {        super(code + ":" + msg);        this.result.setStatusCode(code);        this.result.setMsg(msg);    }
    public BusinessException(Result result, Throwable cause) {        super(result.getStatusCode() + ":" + result.getMsg(), cause);        this.result = result;    }
    public BusinessException(String code, String msg, Throwable cause) {        super(code + ":" + msg, cause);        this.result.setStatusCode(code);        this.result.setMsg(msg);    }
    public Result getResult() {        return result;    }
    public void setResult(Result result) {        this.result = result;    }}

(4)异常工具类ExecptionUtils

为方便在业务代码中进行统一异常调用,特封装异常工具类ExecptionUtils,如下:

package com.xcbeyond.execption.util;
import com.xcbeyond.execption.BusinessException;import com.xcbeyond.execption.SystemException;import com.xcbeyond.execption.data.Result;
/** * 异常工具类 * @Auther: xcbeyond * @Date: 2019/5/27 09:37 */public class ExecptionUtils {    /**     * 业务处理异常     * @param errCode   异常码     * @return     */    public static BusinessException businessException(String errCode) {        return new BusinessException(createResult(errCode));    }
    /**     * 业务处理异常     * @param errCode   异常码     * @param args  错误描述信息中的参数     * @return     */    public static BusinessException businessException(String errCode, String... args) {        return new BusinessException(createResult(errCode, args));    }
    /**     * 系统级异常     * @param errCode   异常码     * @return     */    public static SystemException systemException(String errCode) {        return new SystemException(createResult(errCode));    }
    /**     * 系统级异常     * @param errCode   异常码     * @param args  错误描述信息中的参数     * @return     */    public static SystemException systemException(String errCode, String... args) {        return new SystemException(createResult(errCode, args));    }

    private static Result createResult(String errCode) {        return new Result(errCode, getErrorMsg(errCode));    }
    private static Result createResult(String errCode, String msg) {        return new Result(errCode, msg);    }
    private static Result createResult(String errCode, String[] args) {        return new Result(errCode, getErrorMsg(errCode, args));    }
    /**     * 获取错误信息     * @param errCode   错误码     * @return     */    private static String getErrorMsg(String errCode) {        return ErrorUtils.getErrorDesc(errCode);    }
    /**     * 获取错误信息     * @param errCode   错误码     * @param args  错误描述信息中的参数     * @return     */    private static String getErrorMsg(String errCode, String[] args) {        return ErrorUtils.getParseErrorDesc(errCode, args);    }
}

2.4 全局异常捕获

一般异常捕获都是通过try/catch、throw new等方式进行捕获,而频繁的这样操作,有时让人觉得麻烦,代码变得不是那么的干净,尤其业务复杂的场合。就像下面这种:

try{    ..........}catch(Exception1 e){    ..........}catch(Exception2 e){    ...........}catch(Exception3 e){    ...........}

这样的代码既不简洁好看 ,我们敲着也烦, 一般我们可能想到用拦截器去处理。而在spring中提供了更好的方案,注解@ControllerAdvice@ExceptionHandler,进行全局统一异常处理。

本文定义全局异常捕获类GlobalExceptionHandler,如下:

package com.xcbeyond.execption.handler;
import com.xcbeyond.execption.BusinessException;import com.xcbeyond.execption.SystemException;import com.xcbeyond.execption.data.Result;import org.springframework.http.HttpStatus;import org.springframework.http.ResponseEntity;import org.springframework.web.bind.annotation.ControllerAdvice;import org.springframework.web.bind.annotation.ExceptionHandler;
/** * 全局异常捕获处理 * @Auther: xcbeyond * @Date: 2019/5/28 15:19 */@ControllerAdvicepublic class GlobalExceptionHandler {
    /**     * 业务逻辑异常。     *  HTTP响应状态为200     * @param businessException     * @return     */    @ExceptionHandler(value = BusinessException.class)    public ResponseEntity businessExceptionHandler(BusinessException businessException) {        Result result = businessException.getResult();        return new ResponseEntity(result, HttpStatus.OK);    }
    /**     * 系统异常。     *  HTTP响应状态为400     * @param systemException     * @return     */    @ExceptionHandler(value = SystemException.class)    public ResponseEntity systemExceptionHandler(SystemException systemException) {        Result result = systemException.getResult();        return new ResponseEntity(result, HttpStatus.BAD_REQUEST);    }}

2.5 应用

将上述定义封装的异常,进行实际应用。

下述只是为了进行异常应用测试,并不符合实际业务场景。

以用户登录接口的service层UserServiceImpl类实现讲解,代码如下:

package com.xcbeyond.execption.service.impl;
import com.xcbeyond.execption.model.User;import com.xcbeyond.execption.service.UserService;import com.xcbeyond.execption.util.ExecptionUtils;import org.springframework.http.HttpStatus;import org.springframework.http.ResponseEntity;import org.springframework.stereotype.Service;import org.springframework.util.StringUtils;
/** * @Auther: xcbeyond * @Date: 2019/5/28 17:04 */@Servicepublic class UserServiceImpl implements UserService {
    public ResponseEntity login(User user) {        if (StringUtils.isEmpty(user.getUsername())) {            throw ExecptionUtils.businessException("EE3001");        }        if (StringUtils.isEmpty(user.getPassword())) {            throw ExecptionUtils.businessException("EE3002");        }
        if (!"xcbeyond".equals(user.getUsername())) {            throw ExecptionUtils.businessException("EE4001", user.getUsername());        }
        /**         * 测试系统级异常.         * 通过用户名和密码相同时,来模拟网络连接异常         */        if (user.getPassword().equals(user.getUsername())) {            throw ExecptionUtils.systemException("EE9999");        }
        return new ResponseEntity(HttpStatus.OK);    }}

此例有三类场合异常处理:

(1)不带参的逻辑异常处理

throw ExecptionUtils.businessException("EE3002");

返回数据:

(2)带参的逻辑异常处理

throw ExecptionUtils.businessException("EE4001", user.getUsername());

返回数据:

(3)系统级异常处理

throw ExecptionUtils.systemException("EE9999");

返回数据:

本文分享自微信公众号 - 程序猿技术大咖(cxyjsdk),作者:xcbey0nd

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-06-02

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 如何写出完美的接口:接口规范定义、接口管理工具推荐

    无规矩不成方圆,为了开发人员间更好的配合,我特意整理了这么一篇文档供大家参考学习,如有意见、见解,请在评论区留言探讨。

    xcbeyond
  • IntelliJ IDEA 几个经典操作

    工欲善其事必先利其器,好的工具,必将提高效率。在此,分享几个IntelliJ IDEA 的经典操作、插件,相信会对你有所帮助。

    xcbeyond
  • 颠覆QQ,干掉微信?腾讯内测“朋友”,会是下一个国民社交APP吗

    拥有微信、QQ 两大社交平台的腾讯,近日,悄悄地内测一款拓展新朋友产品:朋友App。

    xcbeyond
  • Argo库解析sjon

    AWeiLoveAndroid
  • 【一分钟知识】常用集合List、Map、Set

    Collection和Collections的区别 Collection是一个接口,它是Set、List等容器的父接口; Collections是个一个工具类...

    java思维导图
  • Android项目重构之路:架构篇

    去年10月底换到了新公司,做移动研发组的负责人,刚开始接手android项目时,发现该项目真的是一团糟。首先是其架构,是按功能模块进行划分的,本来按模块划分也挺...

    Keegan小钢
  • 带你涨姿势的认识一下Kafka之消费者

    之前我们介绍过了 Kafka 整体架构,Kafka 生产者,Kafka 生产的消息最终流向哪里呢?当然是需要消费了,要不只产生一系列数据没有任何作用啊,如果把 ...

    cxuan
  • Eigen的基础使用

    #Eigen的安装 下载Eigen以后直接引用头文件即可,需要的头文件如下 ? Eigen支持的编译器类型 GCC, version 4.4 and new...

    Pulsar-V
  • 结构型设计模式:外观设计模式

    Facade(外观)模式为子系统中的各类(或结构与方法)提供一个简明一致的界面,隐藏子系统的复杂性,使子系统更加容易使用。

    程序你好
  • 重回80年代!为网页注入迷人的孟菲斯设计风

    复古一直在流行,这也是为什么过去许多设计趋势常常会沉寂若干年之后,又再次走到潮流的最前线。今天,我们要聊的是孟菲斯设计风(Memphis Design),这种设...

    前朝楚水

扫码关注云+社区

领取腾讯云代金券