废弃说明:
这个专栏的文章本意是记录笔者第一次搭建博客的过程,文章里里有很多地方的写法都不太恰当,现在已经废弃,关于SpringBoot + Vue 博客系列,笔者重新写了这个系列的文章,不敢说写的好,但是至少思路更加清晰,还在看SpringBoot + Vue 博客系列文章的朋友可以移步:https://blog.csdn.net/li3455277925/category_10341110.html,文章中有错误的地方或者大家有什么意见或建议都可以评论或者私信交流。
处理在Controller产生的异常
- 定义
com.qianyucc.blog.exception.ApiException
类,表示在Controller产生异常的超类
package com.qianyucc.blog.exception;
import lombok.*;
/**
* @author lijing
* @date 2019-10-12 10:25
* @description api异常的超类
*/
@Data
@NoArgsConstructor
public class ApiException extends RuntimeException {
protected Long errorCode;
protected Object data;
public ApiException(Long errorCode, String message, Object data, Throwable e) {
super(message, e);
this.errorCode = errorCode;
this.data = data;
}
public ApiException(Long errorCode, String message, Object data) {
this(errorCode, message, data, null);
}
public ApiException(Long errorCode, String message) {
this(errorCode, message, null, null);
}
public ApiException(String message, Throwable e) {
this(null, message, null, e);
}
public ApiException(Throwable e) {
super(e);
}
}
- 自定义枚举类型
com.qianyucc.blog.model.enums.ErrorCode
,用来表示全局错误码
package com.qianyucc.blog.model.enums;
/**
* @author lijing
* @date 2019-10-12 10:30
* @description 全局错误码
*/
public enum ErrorCode {
TOKEN_EXPIRED(10001L),
UNKNOW_ERROR(50000L),
SUCCESS(20000L),
RESOURCES_NOT_FOUND(40004L);
private Long code;
ErrorCode(Long code) {
this.code = code;
}
public Long getCode() {
return code;
}
public void setCode(Long code) {
this.code = code;
}
}
- 自定义异常
com.qianyucc.blog.exception.ApiTokenExpiredException
,用来处理token过期异常
package com.qianyucc.blog.exception;
import com.qianyucc.blog.model.enums.*;
/**
* @author lijing
* @date 2019-10-08 19:55
* @description token过期异常
*/
public class ApiTokenExpiredException extends ApiException {
public ApiTokenExpiredException() {
super(ErrorCode.TOKEN_EXPIRED.getCode(),"token已过期");
}
}
- 新建
com.qianyucc.blog.advice.ApiExceptionHandlerAdvice
类,用来处理全局异常,只要程序出现异常都会走以下代码,这里自定义了一个RespDataVO
类,用于封装返回信息
package com.qianyucc.blog.advice;
import com.qianyucc.blog.exception.*;
import com.qianyucc.blog.model.enums.*;
import com.qianyucc.blog.model.vo.*;
import org.springframework.http.*;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.*;
import javax.servlet.http.*;
import java.time.*;
/**
* @author lijing
* @date 2019-10-12 10:37
* @description 全局异常处理
*/
@ControllerAdvice
class ApiExceptionHandlerAdvice {
@ExceptionHandler(value = Exception.class)
@ResponseBody
public ResponseEntity<RespDataVO> exception(Exception exception, HttpServletResponse response) {
RespDataVO respDataVO = new RespDataVO();
if (exception instanceof ApiException) {//api异常
ApiException apiException = (ApiException) exception;
respDataVO.setCode(apiException.getErrorCode());
if (apiException instanceof ApiTokenExpiredException) {
response.setStatus(403);
} else {
response.setStatus(500);
}
} else if (exception instanceof NoHandlerFoundException) {// 没有匹配的api
respDataVO.setCode(ErrorCode.RESOURCES_NOT_FOUND.getCode());
response.setStatus(404);
} else {//未知异常
respDataVO.setCode(ErrorCode.UNKNOW_ERROR.getCode());
response.setStatus(500);
}
respDataVO.setTimestamp(Instant.now().toEpochMilli());
respDataVO.setMsg(exception.getMessage());
ResponseEntity<RespDataVO> responseEntity = new ResponseEntity<>(respDataVO, HttpStatus.valueOf(response.getStatus()));
return responseEntity;
}
}
这里注意token过期我们返回的状态码为403,而不是500,因为我们在前端会拦截状态码问为500的响应,并跳转到
error500
界面。
package com.qianyucc.blog.model.vo;
import lombok.*;
import java.time.*;
/**
* @author lijing
* @date 2019-10-12 10:40
* @description 封装返回信息
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class RespDataVO {
private Long code;
private String msg;
private Object data;
private Long timestamp;
public static RespDataVO ok(String msg, Object data) {
return new RespDataVO(ErrorCode.SUCCESS.getCode(), msg, data, Instant.now().toEpochMilli());
}
public static RespDataVO ok(String msg) {
return new RespDataVO(ErrorCode.SUCCESS.getCode(), msg, null, Instant.now().toEpochMilli());
}
public static RespDataVO error(String msg, Object obj) {
return new RespDataVO(ErrorCode.UNKNOW_ERROR.getCode(), msg, obj, Instant.now().toEpochMilli());
}
public static RespDataVO error(String msg) {
return new RespDataVO(ErrorCode.UNKNOW_ERROR.getCode(), msg, null, Instant.now().toEpochMilli());
}
}
- 这样我们在
UserController
里面发现token
过期的时候就可以抛出异常
- 在
application.yml
里面添加如下配置:
- 测试:
- 启动项目,打开浏览器控制台可以看到有在浏览器缓存中token的存在,所以在项目加载的时候会向后端发送请求校验token是否过期。
- 打开
NetWork
可以看到getUserInfo
的状态码为500,打开可以看到服务端返回信息如下:
当我们输入一个不存在的地址时:
处理在业务层产生的异常
- 由于业务层异常不用将信息返回至前台,所以不用定义错误码,于是新建
com.qianyucc.blog.exception.TokenInvalidException
类,来处理业务层的token无效异常
package com.qianyucc.blog.exception;
/**
* @author lijing
* @date 2019-10-12 11:34
* @description 业务层token无效异常
*/
public class TokenInvalidException extends RuntimeException {
public TokenInvalidException() {
super("非法token!");
}
public TokenInvalidException(String message) {
super(message);
}
}
- 这时在
UserService
中即可抛出异常
- 测试业务层异常:
编写TestController
用来制造一个异常
package com.qianyucc.blog.controller;
import com.qianyucc.blog.model.entity.*;
import com.qianyucc.blog.service.*;
import org.springframework.beans.factory.annotation.*;
import org.springframework.web.bind.annotation.*;
/**
* @author lijing
* @date 2019-10-12 11:58
* @description 用户测试的控制器
*/
@RestController
public class TestController {
@Autowired
private UserService userService;
@GetMapping("test")
public Object test() {
UserDO abc = userService.getUserInfo("abc");
return abc;
}
}
在浏览器里访问http://localhost:8886/test
,发现前台可以接受到错误信息
参考文章:这样设计 Java 异常更优雅,赶紧学!