java 自定义异常类

java 自定义异常类

以乐优商城为例

02、乐优微服务:后端返回前端数据问题

我们项目是前后端分离的项目,后端在处理过程需要给前端返回数据结果,这里分两种情况,一种是正常情况(也就是没有发生异常),另一种是发生异常的情况。我们希望不管是正常情况,还是异常情况下,后端都可以给前端不同的响应状态码以及响应内容,这样有利于前端根据不同情况进行业务处理。

1)模拟场景

我们预设这样一个场景,假如我们模拟新增商品,只传一个id,如果id为1则抛出异常。

2)代码

在ly-item中编写模拟的service:

package com.leyou.item.service;

import org.springframework.stereotype.Service;

@Service
public class ItemService {
    
    public Long saveItem(Long id){
        // 模拟添加操作,如果id为1抛异常
        if(id.equals(1L)){
            throw new RuntimeException("Id不能为1!");
        }
        return id;
    }

}

模拟controller:

@RestController
public class ItemController {
    @Autowired
    private ItemService itemService;

    @PostMapping("/save")
    public Long saveItem(@RequestParam("id") Long id){
        return itemService.saveItem(id);
    }

}

3)测试

请求
在这里插入图片描述
结果
在这里插入图片描述
经过测试,我们发现,在正常情况下只能返回200状态码,在异常情况下只能返回500状态码。显然这不满足我们的需求!接下来分两种情况进行讨论。

03、乐优微服务:正常情况数据返回

我们可以通过spring提供的状态码来给前端返回相应的状态码,具体操作如下:

package com.leyou.item.controller;

import com.leyou.item.service.ItemService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletResponse;

@RestController
public class ItemController {
    @Autowired
    private ItemService itemService;

    /**
     * ResponseEntity类
     *   1)status:修改响应状态码
     *   2)body: 修改响应正文
     * @param id
     * @return
     */
    @PostMapping("/save")
    public ResponseEntity<Long> saveItem(@RequestParam("id") Long id){
        return ResponseEntity.status(HttpStatus.CREATED).body(itemService.saveItem(id));
    }

}

在这里插入图片描述

04、乐优微服务:异常情况数据返回

1)场景说明

依然使用上面第二步模拟的案例,参数传1,结果如下:
在这里插入图片描述
此刻是spring给我们封装了一个异常对象,并返回给了客户端,但是这个数据一般企业都希望自己来封装。因为这里所有异常的状态码都是500,我们希望错误的状态码更细致一些,换句话来说,就是我们希望能够自己指定异常时的状态码。而RuntimeException异常又无法自定义状态码,只有自己定义一个异常类了。

2)自定义异常

首先我们先在公共子模块中定义一个乐优异常类,这样每个模块都可以共用这个异常类。

我们在ly-common模块的 com.leyou.common.exception.pojo包下创建如下异常类

package com.leyou.common.exception.pojo;

import lombok.Getter;

/**
 * 自定义异常类,封装自定义异常信息
 */
@Getter
public class LyException extends RuntimeException{
    private Integer status;

    public LyException(Integer status,String message){
        super(message);
        this.status = status;
    }

}


3)改造ly-item-service

我们把原来的throw new RuntimeException改为如下:

@Service
public class ItemService {
    
    public Long saveItem(Long id){
        // 模拟添加操作,如果id为1抛异常
        if(id.equals(1L)){
            throw new LyException(501, "Id不能为1!");
        }
        return id;
    }
}   

此刻我们就可以指定自己需要的状态码了

问题:

再次测试
在这里插入图片描述
发现状态码并没有改变。

可以明白,spring并不认识我们的LyException,它依然把LyException当成了来RuntimeException处理了。

所以我们要在common模块中自定义拦截异常的处理器类。

4)全局异常拦截器

接下来,我们使用SpringMVC提供的统一异常拦截器,因为是统一处理,我们放到ly-common项目中:

package com.leyou.common.exception.controller;

import com.leyou.common.exception.pojo.LyException;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

/**
 * 全局自定义异常拦截器(处理器)
 */
@ControllerAdvice  // 就会覆盖SpringMVC自带异常处理
public class LyExceptionController {

    /**
     * 定义异常处理定义
     */
    @ExceptionHandler(value = LyException.class) // 定义需要捕获什么异常
    public ResponseEntity<LyException> handlerException(LyException e) {
        return ResponseEntity.status(e.getStatus()).body(e);
    }
}


然后测试
在这里插入图片描述
但是,虽然我们要的信息都有了,但是返回的异常信息太多太多,很多都是框架的信息,前端根本用不到这些信息,如果我们将这些信息返回,那么响应的数据多了,一定程度上响应了数据的响应速度,所以我们需要进一步优化。

5)自定义异常结果类

为了让异常结果更友好,我们不在LyExceptionController返回LyException,而是自定义一个响应的类,这个类只包含异常响应的结果。

提供返回异常信息的自定义类

package com.leyou.common.exception.pojo;

import lombok.Getter;
import org.joda.time.DateTime;

/**
 * 异常结果类,封装异常结果信息
 */
@Getter
public class ExceptionResult {
    private Integer status; //异常状态码
    private String message; //异常消息
    private String timestamp;//异常发生时间

    public ExceptionResult(LyException e){
        this.status = e.getStatus();
        this.message = e.getMessage();
        this.timestamp = DateTime.now().toString("yyyy-MM-dd HH:mm:ss");
    }

}


定义好类后,我们改造全局异常处理器类:

package com.leyou.common.exception.controller;

import com.leyou.common.exception.pojo.ExceptionResult;
import com.leyou.common.exception.pojo.LyException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * 全局异常拦截
 */
@ControllerAdvice  //该注解会把所有Controller的异常拦截
public class LyExceptionController {

    /**
     * 拦截具体异常方法
     */
    @ExceptionHandler(value = LyException.class)
    @ResponseBody
    public ResponseEntity<ExceptionResult> handlerException(LyException e){
        return ResponseEntity.status(e.getStatus()).body(new ExceptionResult(e));
    }


}

重启微服务,然后测试
在这里插入图片描述
到这一步,我们的全局异常问题已经解决了,要的响应码和响应内容都是我们自定义的了。

但是还有一个问题,在实际开发中,我们的异常是很多的,而且这些异常是和前端约定好的,但现在异常响应码和异常响应内容都是写死在代码中,如果某一个响应码或者响应内容需要修改,那么所有用到这个响应码和响应内容的地方都要跟着改,很不方便,会造成很多重复的工作,所以还需要进一步优化。

6)自定义异常枚举

因为现在的异常响应信息包含多个内容,所以只用字符串就很难满足我们的需求,所以我们需要定义一个类来装这些信息,但是异常信息其实都是提前约定好的,所以用枚举比较符合我们需求。

枚举:把一件事情的所有可能性列举出来。在计算机中,枚举也可以叫多例,单例是多例的一种情况 。

单例:一个类只能有一个实例。

多例:一个类只能有有限个数的实例。

单例的实现:

  • 私有化构造函数
  • 在成员变量中初始化本类对象
  • 对外提供静态方法,访问这个对象

枚举代码:

package com.leyou.common.exception.pojo;

import lombok.Getter;

/**
 * 黑马程序员
 */
@Getter
public enum ExceptionEnum {
    INVALID_FILE_TYPE(400, "无效的文件类型!"),
    INVALID_PARAM_ERROR(400, "无效的请求参数!"),
    INVALID_PHONE_NUMBER(400, "无效的手机号码"),
    INVALID_VERIFY_CODE(400, "验证码错误!"),
    INVALID_USERNAME_PASSWORD(400, "无效的用户名和密码!"),
    INVALID_SERVER_ID_SECRET(400, "无效的服务id和密钥!"),
    INVALID_NOTIFY_PARAM(400, "回调参数有误!"),
    INVALID_NOTIFY_SIGN(400, "回调签名有误!"),

    CATEGORY_NOT_FOUND(404, "商品分类不存在!"),
    BRAND_NOT_FOUND(404, "品牌不存在!"),
    SPEC_NOT_FOUND(404, "规格不存在!"),
    GOODS_NOT_FOUND(404, "商品不存在!"),
    CARTS_NOT_FOUND(404, "购物车不存在!"),
    APPLICATION_NOT_FOUND(404, "应用不存在!"),
    ORDER_NOT_FOUND(404, "订单不存在!"),
    ORDER_DETAIL_NOT_FOUND(404, "订单数据不存在!"),

    DATA_TRANSFER_ERROR(500, "数据转换异常!"),
    INSERT_OPERATION_FAIL(500, "新增操作失败!"),
    UPDATE_OPERATION_FAIL(500, "更新操作失败!"),
    DELETE_OPERATION_FAIL(500, "删除操作失败!"),
    FILE_UPLOAD_ERROR(500, "文件上传失败!"),
    DIRECTORY_WRITER_ERROR(500, "目录写入失败!"),
    FILE_WRITER_ERROR(500, "文件写入失败!"),
    SEND_MESSAGE_ERROR(500, "短信发送失败!"),
    INVALID_ORDER_STATUS(500, "订单状态不正确!"),
    STOCK_NOT_ENOUGH_ERROR(500, "库存不足!"),

    UNAUTHORIZED(401, "登录失效或未登录!");

    private int status;
    private String message;

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

改造LyException类

package com.leyou.common.exception.pojo;

import lombok.Getter;

/**
 * 自定义异常类,封装自定义异常信息
 */
@Getter
public class LyException extends RuntimeException{
    private Integer status;

    public LyException(Integer status,String message){
        super(message);
        this.status = status;
    }

    public LyException(ExceptionEnum exceptionEnum){
        super(exceptionEnum.getMessage());
        this.status = exceptionEnum.getStatus();
    }
}


改造ly-item-service

package com.leyou.item.service;

import com.leyou.common.exception.pojo.ExceptionEnum;
import com.leyou.common.exception.pojo.LyException;
import org.springframework.stereotype.Service;

@Service
public class ItemService {
    
    public Long saveItem(Long id){
        // 模拟添加操作,如果id为1抛异常
        if(id.equals(1L)){
            //throw new RuntimeException("Id不能为1!");
            throw new LyException(ExceptionEnum.UNAUTHORIZED);
        }
        return id;
    }

}

Memorial Day is 518 days
I miss you
xiaokeai

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值