分布式自定义异常工具包RuntimeException

在这里插入图片描述

前言

在java项目里,异常的使用是比不可少,但是很多的开发者并不知道异常在项目中要怎么使用会更好一些,今天就给大家说说项目中我是怎么使用的,也希望能引出你的更好的使用方法和想法。

分析

我们先来说说,目前很多项目都是怎么处理自定义异常的呢?因为项目采用的是基本都是MVC代码组织模式,所以很多的项目会按层次定义自己的异常,例如:DaoException、ServiceException、ControllerException,还有按照第三发组件定义自定义异常,例如:MysqlExceptioin、RedisException、ElasticSearchException,RabbitMqException这样的划分(这里只说下大类的异常划分,详细的就不说了),上述这些异常的划分都是有一些好处的,可以很容易定位到时哪个层或那个组件出现了问题,但是有一点如果你没有对这些功能组件或代码层做二次封装,其实是没有特别的必要这样划分异常的,因为功能组件通常都有一个自己的全局异常类,当出现异常的,其实也一眼就可以定位到这个异常原因,没有必要在不厌其烦的将异常一层层抛出,代码的意义其实不是很大,那么接下来说下我们的异常使用方式。

正题

来先让我们熟悉下异常的集成结构,Throwable是所有异常的父类,Error是错误,错误是致命的,所以在程序中我们通常不会碰到Error,也通常不会去自定义实现它,Exception是异常的父类,异常分为两大类,受检异常和运行时异常,受检异常就是在调用某个抛出受检异常方法时,调用方必须去处理掉该异常或手动向上抛出,否则编译不通过,运行时异常就是不会被编译器所检查,当程序运行到特定条件时,才会抛出异常,那么问题来啦,我们自定义的异常,到底是继承Exception(除RuntimeException的子类都是受检异常)还是RuntimeException(运行时异常)呢?我们通常是继承RuntimeException,也建议你也这样做。
下面说下我们的异常继承关系:

对于异常我们会划分出参数无效、数据已存在、数据不存在、权限不足、远程调用、内部错误等等的异常。他们都继承了全局业务异常(BusinessException),更详细的异常可以通过继承它们去向下划分,你可以看到,其实我们划分的异常是按照业务划分的,接口采用的Restful风格,全局异常捕获类会根据特定的异常,返回不同的HTTP状态码, 例如:参数无效会返回400Http状态码,数据不存在会返回404,系统内部错误返500等等。
下面我们展示一下代码:

package com.common.basic;

import lombok.Data;

/**
 * @author zcc
 */
@Data
public class BaseException extends RuntimeException {

    private int status;

    private String message;

    public BaseException(int status, String message) {
        this.status = status;
        this.message = message;
    }

}
说明:

该类有四个属性,code、message,code主要是内部自定义的一个放接口返回状态码的枚举,message用来异常抛出时,向上传递一些附加信息,例如:runtimeException报错信息或者一些自定义的错误信息。
无参构造器中有一段初始化其子类的code、message和resultCode的逻辑。

package com.common.enums;
/**
 * @author zcc
 */
public enum ApiCode {

    /**
     * 成功
     */
    OK(200, "Ok"),

    /**
     * 失败
     */
    FAIL(202, "Fail"),
    /**
     * 请求参数有误
     */
    BAD_REQUEST(400, "Bad Request"),
    /**
     * 未授权
     */
    UNAUTHORIZED(401, "Unauthorized"),
    /**
     * 拒绝(权限不足)
     */
    FORBIDDEN(403, "Forbidden"),
    /**
     * 没有数据
     */
    NOT_FOUND(404, "Not Found"),
    /**
     * 数据冲突
     */
    CONFLICT(409, "Conflict"),
    /**
     * 系统异常
     */
    ERROR(500, "Internal Server Error");

    private int value;
    private String message;

    public int getValue() {
        return value;
    }

    public String getMessage() {
        return message;
    }

    ApiCode(int value, String message) {
        this.value = value;
        this.message = message;
    }
}

配置handlers执行类:

package com.common.handlers;

import com.common.basic.BaseException;
import com.common.basic.ResponseApi;
import com.netflix.client.ClientException;
import com.common.enums.ApiCode;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.jdbc.UncategorizedSQLException;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.net.SocketTimeoutException;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author zcc
 */
@RestControllerAdvice
public class RepairController {

    @ExceptionHandler(Exception.class)
    public ResponseApi exceptionHandler(Exception e) {
        String message;
        boolean isRTE = e.getClass() == RuntimeException.class;
        Throwable throwable = isRTE ? e.getCause() : e;
        String msg = throwable.getMessage();
        String className = throwable.getClass().getName();
        if (throwable instanceof ClientException) {
            String serviceName = Optional.ofNullable(msg).map(item -> item.replace("Load balancer does not have available server for client: ", "")).orElse(msg);
            message = MessageFormat.format("服务[{0}]未启动", serviceName);
        } else if (throwable instanceof SocketTimeoutException) {
            message = MessageFormat.format("服务未启动,请联系部署或运维人员;线索:[{0}]", msg);
        } else if (className.startsWith("feign.FeignException")) {
            message = MessageFormat.format("服务调用异常,请联系相关功能开发人员并告知环境与重现流程;线索:[{0}]", msg);
        } else if (className.startsWith("feign.RetryableException")) {
            //RetryableException re = (RetryableException) throwable;
            message = MessageFormat.format("服务调用异常,可能是网络造成,请联系部署或开发人员落实;线索:[{0}]", msg);
        } else {
            message = MessageFormat.format("意料之外错误,请马上联系开发人员并告知环境和重现流程,线索:[{0}]", msg);
        }
        e.printStackTrace();
        return ResponseApi.error(ApiCode.ERROR, message);

    }

    @ExceptionHandler(BaseException.class)
    public ResponseApi baseExceptionHandler(BaseException e) {
        ResponseApi ra = new ResponseApi();
        ra.setStatus(e.getStatus());
        ra.setMessage(e.getMessage());
        ra.setData(e.getCause());
        e.printStackTrace();
        return ra;
    }

    @ExceptionHandler(ConstraintViolationException.class)
    public ResponseApi constraintValidationErrorHandler(ConstraintViolationException ex) {
        List<String> eis = ex.getConstraintViolations().stream().map(ConstraintViolation::getMessage).collect(Collectors.toList());
        ex.printStackTrace();
        return ResponseApi.sp(ApiCode.ERROR, eis.toString());
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseApi validationErrorHandler(MethodArgumentNotValidException ex) {
        List<String> errorInformation = ex.getBindingResult()
                .getAllErrors().stream()
                .map(ObjectError::getDefaultMessage)
                .collect(Collectors.toList());
        ex.printStackTrace();
        return ResponseApi.sp(ApiCode.ERROR, errorInformation.toString());
    }

    @ExceptionHandler(BindException.class)
    public ResponseApi exceptionHandler(BindException e) {
        List<String> errors = e.getAllErrors().stream().map(item -> {
            if (item instanceof FieldError) {
                FieldError errItem = (FieldError) item;
                return String.format("%s: %s", errItem.getField(), errItem.getDefaultMessage());
            } else {
                return item.getDefaultMessage();
            }
        }).collect(Collectors.toList());
        e.printStackTrace();
        return ResponseApi.sp(ApiCode.ERROR, errors);

    }

    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public ResponseApi exceptionHandler(HttpRequestMethodNotSupportedException e) {
        String method = e.getMethod();
        String[] supportedMethods = e.getSupportedMethods();
        String supportedStr = Arrays.toString(supportedMethods);
        String message = MessageFormat.format("请求方式错误,只能支持{0},当前请求方式为:{1}", supportedStr, method);
        e.printStackTrace();
        return ResponseApi.error(ApiCode.BAD_REQUEST, message);
    }

    @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
    public ResponseApi exceptionHandler(HttpMediaTypeNotSupportedException e) {
        MediaType contentType = e.getContentType();
        String message;
        if (Objects.isNull(contentType)) {
            message = "请求参数类型错误";
        } else {
            String type = contentType.getType();
            String subtype = contentType.getSubtype();
            message = MessageFormat.format("请求参数类型错误,当前类型:[{0}/{1}]", type, subtype);
        }
        e.printStackTrace();
        return ResponseApi.error(ApiCode.BAD_REQUEST, message);
    }

    @ExceptionHandler(HttpMessageNotReadableException.class)
    public ResponseApi exceptionHandler(HttpMessageNotReadableException e) {
        String message = "请求参数格式错误";
        e.printStackTrace();
        return ResponseApi.error(ApiCode.BAD_REQUEST, message);
    }

    @ExceptionHandler(UncategorizedSQLException.class)
    public ResponseApi exceptionHandler(UncategorizedSQLException e) {
        String sql = e.getSql();
        String message = MessageFormat.format("数据库操作失败,请联系相关开发处理; SQL:[{0}]", sql);
        e.printStackTrace();
        return ResponseApi.error(ApiCode.ERROR, message);
    }

    @ExceptionHandler(NullPointerException.class)
    public ResponseApi exceptionHandler(NullPointerException e) {
        List<String> sts = Stream.of(e.getStackTrace()).map(item -> {
            String fileName = item.getFileName();
            String className = item.getClassName();
            String methodName = item.getMethodName();
            int lineNumber = item.getLineNumber();
            String pattern = "File:%s Class:%s Method:%s LineNumber:%d";
            return String.format(pattern, fileName, className, methodName, lineNumber);
        }).collect(Collectors.toList());
        String message = MessageFormat.format("出错了,赶紧联系开发人员吧; 原因:{0}", "NPE");
        ResponseApi error = ResponseApi.error(ApiCode.ERROR, message);
        error.setData(sts);
        e.printStackTrace();
        return error;
    }

}

说明

主要是对各个exception的处理,对于不同的异常针对性处理,然后返回自定义的异常提示,用于分布式微服务的异常捕获,属于项目common包中的异常捕获工具。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值