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 |