SpringBoot自定义异常和全局捕获异常实现
简介
整篇文章围绕着用户模块进行演示。
1、自定义异常
1.1、自定义异常枚举类 UserExceptionEnum.java
根据自己的习惯,新建一个包enumerate,用来装项目内所有的枚举。在该包下创建一个枚举类:UserExceptionEnum.java(用户模块异常的枚举,养成好习惯创建枚举类时以Enum结尾。)
/**
* 枚举描述:
* 用户模块的定义异常的枚举
* @ClassName ExceptionEnum
* @Description TODO
* @Author msi
* @Date 2020/6/10 19:45
* @Version 1.0
*/
public enum UserExceptionEnum {
/********** 邮箱的异常 **********/
// 邮箱是无效的
MAILBOX_IS_INVALID("100101", "注册的邮箱不存在"),
// 邮箱未注册
MAILBOX_NOT_EXIST("100102", "邮箱不存在"),
// 邮箱已被注册
MAILBOX_IS_EXIST("100103", "账号已经被注册,请登录"),
/********** 手机号的异常 **********/
// 手机号是无效的
PHONE_IS_INVALID("100201", "手机号无效"),
// 手机号未注册
PHONE_NOT_EXIST("100202", "手机号不存在"),
// 手机号已被注册
PHONE_IS_EXIST("100203", "手机号已经被注册,请登录"),
/********** 用户名的异常 **********/
// 用户名是无效的
USERNAME_IS_INVALID("100301", "用户名无效"),
// 用户名未注册
USERNAME_NOT_EXIST("100302", "用户名不存在"),
// 用户名已被注册
USERNAME_IS_EXIST("100303", "用户名已经被注册,请登录"),
/********** 注册时验证码 **********/
CAPTCHA_MISMATCH("100401", "输入验证码与发送邮箱的验证码不匹配"),
/********** 登陆时用户名和密码不匹配 **********/
USER_WRONG_PASSWORD("100501","密码输入错误");
/**
* 错误码
*/
private String code;
/**
* 错误信息
*/
private String message;
/**
* 是否发送邮件: false不发送
*/
private Boolean isSend;
/**
* 这里默认isSend=false,不发送
* 后期根据此参数进行判断是否需要将异常以邮件的信息发送给技术人员。
*/
UserExceptionEnum(String code, String message){
this.code = code;
this.message = message;
this.isSend = false;
}
UserExceptionEnum(String code, String message, Boolean isSend) {
this.code = code;
this.message = message;
this.isSend = isSend;
}
public String getCode() {
return code;
}
public String getMessage() {
return message;
}
public Boolean getSend() {
return isSend;
}
}
1.2、自定义异常类
同理,在项目内创建一个exception的包,用来装自定义的所有异常。
首先我们先要创建一个异常基类(父类),保证我们后续创建的异常类都有统一的模板结构。
1.2.1、创建异常基类 BaseException.java
异常基类里面有三个成员分别是:
- code:描述了错误异常码
- message: 描述具体的错误信息
- isSend: 异常是否发送邮件
可以看到,这个和刚刚定义的 UserExceptionEnum.java 结构类似,在本例中笔者选择了继承Exception,读者也可以自行选择其它异常类,这都是ok的。
/**
* 类描述:
* 自定义异常的基类,其它模块的异常继承进行扩展
* @ClassName BaseException
* @Description TODO
* @Author msi
* @Date 2020/6/10 19:41
* @Version 1.0
*/
public class BaseException extends Exception{
/**
* 错误代码
*/
protected String code;
/**
* 错误信息
*/
protected String message;
/**
* 是否发送错误邮件,false不发送
*/
protected Boolean isSend;
public BaseException(String message) {
super(message);
}
public String getCode() {
return code;
}
public Boolean getSend() {
return isSend;
}
@Override
public String getMessage() {
return message;
}
}
1.2.2、创建用户模块异常类 UserException.java
这里直接继承刚写好的异常基类,使其具有严格正确的结构。
/**
* 类描述:
* 用户相关业务的异常类
* @ClassName UserException
* @Description TODO
* @Author msi
* @Date 2020/6/10 21:06
* @Version 1.0
*/
public class UserException extends BaseException{
public UserException(String message) {
super(message);
}
/**
* 用户模块的异常枚举类,定义了异常码和异常信息
* @param userExceptionEnum 用户模块的异常枚举类
*/
public UserException(UserExceptionEnum userExceptionEnum) {
this("错误代码(" + userExceptionEnum.getCode() + ")->" + userExceptionEnum.getMessage());
super.code = userExceptionEnum.getCode();
super.message = userExceptionEnum.getMessage();
super.isSend = userExceptionEnum.getSend();
}
@Override
public String getCode() {
return code;
}
@Override
public Boolean getSend() {
return super.getSend();
}
@Override
public String getMessage() {
return message;
}
}
2、全局异常捕获实现 UserGlobalExceptionHandler.java
新建一个包:config,用来放置项目的一些配置相关类。在该包下创建一个类:UserGlobalExceptionHandler.java
这里使用了**@ControllerAdvice**注解,这个注解可以这样使用
- @ControllerAdvice(basePackages = “com.cfl.jd.controller”) ,捕获指定包下的异常
- @ControllerAdvice(assignableTypes = {UserController.class}),捕获指定类的异常
接下来笔者带领你们解析下下面的代码
-
首先UserGlobalExceptionHandler.java加上了注解@ControllerAdvice(basePackages = “com.cfl.jd.controller”),表示这个类,需要捕获com.cfl.jd.controller下发生的的异常。
-
类中有两个方法:
2.1:
@ExceptionHandler(BaseException.class)
@ResponseBody
public Map<String, Object> customErrorDispose(BaseException e)2.2
@ExceptionHandler(Exception.class)
@ResponseBody
public Map<String, Object> otherErrorDispose(Exception e)
方法名 | 作用 | 使用注解 | 方法参数类型 | 使用方式 |
---|---|---|---|---|
customErrorDispose | 处理自定义异常 | @ExceptionHandler(BaseException.class) | BaseException | 手动抛异常:throw new UserException(UserExceptionEnum.MAILBOX_IS_EXIST); |
otherErrorDispose | 处理系统其他异常 | @ExceptionHandler(Exception.class) | Exception | 不需要手动抛异常 |
/**
* 全局捕获异常
* 捕获com.cfl.jd.controller下的所有类的异常,返回json字符串
* @ClassName GlobalExceptionHandler
* @Description TODO
* @Author msi
* @Date 2019/7/28 21:51
*/
@Slf4j
@ControllerAdvice(basePackages = "com.cfl.jd.controller")
public class UserGlobalExceptionHandler extends MemberVariable{
@Autowired
private ApplicationValue applicationValue;
/**
* 捕获全局自定义异常
* @Description TODO
* @param e
* @return
*/
@ExceptionHandler(BaseException.class)
@ResponseBody
public Map<String, Object> customErrorDispose(BaseException e){
// 将错误记录在日志中。
Map<String, Object> errorResultMap = new HashMap<>();
errorResultMap.put("responseCode", e.getCode());
errorResultMap.put("detailed", e.getMessage() + "\n" + GetNowUtil.getDateTime());
// 打印错误日志
log.error("错误代码({}),错误信息({})", e.getCode(), e.getMessage());
// 判断该异常是否需要发送邮件
if (e.getSend()) {
// 给发送邮件的队列 QueueConsts.SEND_EMAIL_QUEUE 生成消息
String emailTopic = "错误日志";
String emailContext = "尊敬的管理员,您好:\n项目发生bug,请尽快解决\n" + "错误代码("+e.getCode()+"),错误信息("+e.getMessage()+")\n(这是一封自动发送的邮件,请不要直接回复)";
String emailEnd = applicationValue.getApplicationName() + "\n" + GetNowUtil.getDateTime();
EmailDTO emailDTO = new EmailDTO(applicationValue.getReceiveEmail(), emailTopic, emailContext, emailEnd);
super.rabbitTemplate.convertAndSend(QueueConsts.SEND_EMAIL_QUEUE, emailDTO);
}
return errorResultMap;
}
/**
* 捕获意料之外的异常Exception
* @param e
* @return
*/
@ExceptionHandler(Exception.class)
@ResponseBody
public Map<String, Object> otherErrorDispose(Exception e){
// 将错误记录在日志中。
Map<String, Object> errorResultMap = new HashMap<>();
errorResultMap.put("responseCode", 500);
errorResultMap.put("detailed", e.getMessage() + "\n" + GetNowUtil.getDateTime());
// 打印错误日志
log.error("错误代码({}),错误信息({})", 500, e.getMessage());
// 给发送邮件的队列 QueueConsts.SEND_EMAIL_QUEUE 生成消息
String emailTopic = "错误日志";
String emailContext = "尊敬的管理员,您好:\n项目发生bug,请尽快解决\n" + "错误代码("+500+"),错误信息("+e.getMessage()+")\n(这是一封自动发送的邮件,请不要直接回复)";
String emailEnd = applicationValue.getApplicationName() + "\n" + GetNowUtil.getDateTime();
EmailDTO emailDTO = new EmailDTO(applicationValue.getReceiveEmail(), emailTopic, emailContext, emailEnd);
super.rabbitTemplate.convertAndSend(QueueConsts.SEND_EMAIL_QUEUE, emailDTO);
return errorResultMap;
}
}
3、实际代码中应用
/**
* 用户登录
* @param loginUsername 登录用户名/电话/邮箱
* @param loginPassword 密码
* @return
* @throws InvalidKeySpecException
* @throws NoSuchAlgorithmException
*/
@Override
public Map userLogin(String loginUsername, String loginPassword) throws InvalidKeySpecException, NoSuchAlgorithmException, UserException {
Map<String, Object> serviceMap = new HashMap<>();
// 1. 先查询用户信息
UserDO user = userDAO.selectUserByNameOrEmailOrPhone(loginUsername);
String msg = ""; // 具体信息,默认登录用户不存在
String responseCode = "";
boolean passwordIsSame = false; // false 表示不能登录
// 用户存在
if (!ObjectUtils.isEmpty(user)) {
// 2.1.将密码 和用户的随机盐 加密
String salt = user.getSalt();
String password = user.getPassword();
// 判断密码是否相同
passwordIsSame = PasswordEncryptionUtil.authenticate(loginPassword, password, salt);
if(passwordIsSame){
// 给rabbitmq的QueueConsts.SEND_EMAIL_QUEUE发送消息
String emailTopic = "欢迎登录";
String emailContext = "尊敬的用户,您好:\n\n 欢迎登录XXX(这是一封自动发送的邮件,请不要直接回复)\n";
String emailEnd = applicationValue.getApplicationName() + "\n" + GetNowUtil.getDateTime();
EmailDTO emailDTO = new EmailDTO(user.getEmail(), emailTopic, emailContext, emailEnd);
super.rabbitTemplate.convertAndSend(QueueConsts.SEND_EMAIL_QUEUE, emailDTO);
// 生成Token
String token = TokenUtil.sign(loginUsername, loginPassword);
if (token != null) {
serviceMap.put("token", token);
}
} else {
// 密码不匹配
throw new UserException(UserExceptionEnum.USER_WRONG_PASSWORD);
}
} else {
// 下面这个就是一个错误 10 /0 会出现by /0 错误。
// System.out.println(10 /0);
// 注意:这里进行了手动抛异常。
throw new UserException(UserExceptionEnum.USERNAME_NOT_EXIST);
}
// 状态码
serviceMap.put("responseCode", responseCode);
// 错误信息
serviceMap.put("message", msg);
return serviceMap;
}
当用户登录时,如果检查到用户名不存在,那么我们就会在else里面进行手动抛出一个异常
throw new UserException(UserExceptionEnum.USERNAME_NOT_EXIST);
这里直接使用了枚举类,一目了然。此时程序会进入UserGlobalExceptionHandler.java 类中的customErrorDispose方法。如果我们将System.out.println(10 /0);放出来,那么接下来程序就会进入UserGlobalExceptionHandler.java 的otherErrorDispose中。
总结
整篇文章只介绍了异常枚举类(UserExceptionEnum)、异常模板类(BaseException)、用户异常类(UserException)、全局捕获异常类(UserGlobalExceptionHandler)
这四个文件的代码。由于篇幅原因,在这四个java类中,有一些类还继承了其它类,注入了其它对象,使用了其它工具类方法,这里就不再贴出来了,代码中都有详细注释。可以根据自己的需求进行替换,但是整篇文章的核心代码都已经列举出来了。如果有不恰当的地方评论区指出来,共同交流共同进步,谢谢。