企业实战之spring项目《自定义异常》

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/aiyaya_/article/details/78989226

前言

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

分析

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

正题

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

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

业务异常基类,BusinessException:

package com.zhuma.demo.exception;

import com.zhuma.demo.enums.ExceptionEnum;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;

import com.zhuma.demo.enums.ResultCode;
import org.springframework.core.NestedCheckedException;

/**
 * @desc 业务异常类
 * 
 * @author zhumaer
 * @since 9/18/2017 3:00 PM
 */
@Data
public class BusinessException extends RuntimeException {

    private static final long serialVersionUID = 194906846739586856L;

    protected String code;

    protected String message;

    protected ResultCode resultCode;

    protected Object data;

    public BusinessException() {
        ExceptionEnum exceptionEnum = ExceptionEnum.getByEClass(this.getClass());
        if (exceptionEnum != null) {
            resultCode = exceptionEnum.getResultCode();
            code = exceptionEnum.getResultCode().code().toString();
            message = exceptionEnum.getResultCode().message();
        }

    }

    public BusinessException(String message) {
        this();
        this.message = message;
    }

    public BusinessException(String format, Object... objects) {
        this();
        format = StringUtils.replace(format, "{}", "%s");
        this.message = String.format(format, objects);
    }

    public BusinessException(String msg, Throwable cause, Object... objects) {
        this();
        String format = StringUtils.replace(msg, "{}", "%s");
        this.message= String.format(format, objects);
    }

    public BusinessException(ResultCode resultCode, Object data) {
        this(resultCode);
        this.data = data;
    }

    public BusinessException(ResultCode resultCode) {
        this.resultCode = resultCode;
        this.code = resultCode.code().toString();
        this.message = resultCode.message();
    }

}

说明

  • 该类有四个属性,code、message、resultCode、data前两个不说,resultCode主要是内部自定义的一个放接口返回状态码的枚举(其实就是code和message的封装),data用来异常抛出时,向上传递一些附加信息,例如:参数异常抛出时会向上传递 哪个参数因为什么出错 等更详细的异常附加信息。
  • 无参构造器中有一段初始化其子类的code、message和resultCode的逻辑。

自定义异常-HTTP状态码-自定义详细状态码对应关系枚举类:

package com.zhuma.demo.enums;

import org.springframework.http.HttpStatus;

import com.zhuma.demo.exception.BusinessException;
import com.zhuma.demo.exception.DataConflictException;
import com.zhuma.demo.exception.DataNotFoundException;
import com.zhuma.demo.exception.InternalServerException;
import com.zhuma.demo.exception.ParameterInvalidException;
import com.zhuma.demo.exception.PermissionForbiddenException;
import com.zhuma.demo.exception.RemoteAccessException;
import com.zhuma.demo.exception.UserNotLoginException;

/**
 * @desc 异常、HTTP状态码、默认自定义返回码 映射类
 *
 * @author zhumaer
 * @since 9/21/2017 14:11 PM
 */
public enum ExceptionEnum {

    /**
     * 无效参数
     */
    PARAMETER_INVALID(ParameterInvalidException.class, HttpStatus.BAD_REQUEST, ResultCode.PARAM_IS_INVALID),

    /**
     * 数据未找到
     */
    NOT_FOUND(DataNotFoundException.class, HttpStatus.NOT_FOUND, ResultCode.RESULE_DATA_NONE),

    /**
     * 数据已存在
     */
    CONFLICT(DataConflictException.class, HttpStatus.CONFLICT, ResultCode.DATA_ALREADY_EXISTED),

    /**
     * 用户未登录
     */
    UNAUTHORIZED(UserNotLoginException.class, HttpStatus.UNAUTHORIZED, ResultCode.USER_NOT_LOGGED_IN),

    /**
     * 无访问权限
     */
    FORBIDDEN(PermissionForbiddenException.class, HttpStatus.FORBIDDEN, ResultCode.PERMISSION_NO_ACCESS),

    /**
     * 远程访问时错误
     */
    REMOTE_ACCESS_ERROR(RemoteAccessException.class, HttpStatus.INTERNAL_SERVER_ERROR, ResultCode.INTERFACE_OUTTER_INVOKE_ERROR),

    /**
     * 系统内部错误
     */
    INTERNAL_SERVER_ERROR(InternalServerException.class, HttpStatus.INTERNAL_SERVER_ERROR, ResultCode.SYSTEM_INNER_ERROR);

    private Class<? extends BusinessException> eClass;

    private HttpStatus httpStatus;

    private ResultCode resultCode;

    ExceptionEnum(Class<? extends BusinessException> eClass, HttpStatus httpStatus, ResultCode resultCode) {
        this.eClass = eClass;
        this.httpStatus = httpStatus;
        this.resultCode = resultCode;
    }

    public Class<? extends BusinessException> getEClass() {
        return eClass;
    }

    public HttpStatus getHttpStatus() {
        return httpStatus;
    }

    public ResultCode getResultCode() {
        return resultCode;
    }

    public static boolean isSupportHttpStatus(int httpStatus) {
        for (ExceptionEnum exceptionEnum : ExceptionEnum.values()) {
            if (exceptionEnum.httpStatus.value() == httpStatus) {
                return true;
            }
        }

        return false;
    }

    public static boolean isSupportException(Class<?> z) {
        for (ExceptionEnum exceptionEnum : ExceptionEnum.values()) {
            if (exceptionEnum.eClass.equals(z)) {
                return true;
            }
        }

        return false;
    }

    public static ExceptionEnum getByHttpStatus(HttpStatus httpStatus) {
        if (httpStatus == null) {
            return null;
        }

        for (ExceptionEnum exceptionEnum : ExceptionEnum.values()) {
            if (httpStatus.equals(exceptionEnum.httpStatus)) {
                return exceptionEnum;
            }
        }

        return null;
    }

    public static ExceptionEnum getByEClass(Class<? extends BusinessException> eClass) {
        if (eClass == null) {
            return null;
        }

        for (ExceptionEnum exceptionEnum : ExceptionEnum.values()) {
            if (eClass.equals(exceptionEnum.eClass)) {
                return exceptionEnum;
            }
        }

        return null;
    }
}

参数无效异常类:

package com.zhuma.demo.exception;

import com.zhuma.demo.enums.ResultCode;

/**
 * @desc 参数无效异常
 * 
 * @author zhumaer
 * @since 9/18/2017 3:00 PM
 */
public class ParameterInvalidException extends BusinessException {

    private static final long serialVersionUID = 3721036867889297081L;

    public ParameterInvalidException() {
        super();
    }

    public ParameterInvalidException(Object data) {
        super();
        super.data = data;
    }

    public ParameterInvalidException(ResultCode resultCode) {
        super(resultCode);
    }

    public ParameterInvalidException(ResultCode resultCode, Object data) {
        super(resultCode, data);
    }

    public ParameterInvalidException(String msg) {
        super(msg);
    }

    public ParameterInvalidException(String formatMsg, Object... objects) {
        super(formatMsg, objects);
    }

}

说明
其他的异常也大同小异,不同的就是构造器参数的配置了,根据实际使用情况而定。

使用方式

下面我们看下,具体的该怎么使用这些异常的,以获取用户为例:

package com.zhuma.demo.web.user;

import java.util.Date;

import com.zhuma.demo.annotation.ResponseResult;
import com.zhuma.demo.comm.result.DefaultErrorResult;
import com.zhuma.demo.comm.result.PlatformResult;
import com.zhuma.demo.comm.result.Result;
import com.zhuma.demo.exception.BusinessException;
import com.zhuma.demo.exception.DataNotFoundException;
import com.zhuma.demo.exception.UserNotLoginException;
import com.zhuma.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import com.zhuma.demo.model.po.User;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @desc 用户管理控制器
 * 
 * @author zhumaer
 * @since 6/20/2017 16:37 PM
 */
@ResponseResult
@RestController
@RequestMapping("/users")
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("{id}")
    public User getUser(@PathVariable("id") String id) {
       User user = userService.getById(id);
       if (user == null) {
           throw new DataNotFoundException();
       }

        return user;
    }

}

PostMan调用结果展示:
这里写图片描述

扫描二维码关注公众号,回复: 3046238 查看本文章

我们看到只要我们发现没有用户信息时,直接抛出数据未找到异常,就可以直接返回数据未找到错误了,其中根据异常区转化相应错误信息和Http状态码的逻辑其实是由 全局异常处理器完成的,可以看下这篇《全局异常处理器》
只有当数据存在并且正确的时候,才将User信息返回,否则用异常来结束程序。

最后

因为代码并没有全部给全,如果你想要源码,也可以私聊我哈,若上述文字有描述不清楚,或者你有哪里不太懂的可以评论留言或者关注我的公众号,公众号里也会不定期的发送一些关于企业实战相关的文章哈!

欢迎关注我们的公众号或加群,等你哦!

猜你喜欢

转载自blog.csdn.net/aiyaya_/article/details/78989226