全局异常处理与发生异常时的邮件通知
一、前言
在任何一个SpringBoot项目中,Controlle层遍布异常捕获的代码,是不是觉得特别的不舒服呢。其实SpringBoot给我们提供了全局异常处理机制,使用ControllerAdvice与ExceptionHandler这两个注解即可。
我们现在的需求是,在任何Controller层出现代码,首先通过全局异常处理机制,捕获到该异常,然后用日志输出该异常出现的时间、异常种类、请求路径与参数等信息,最后通过邮件的方式通知开发者。
二、全局异常处理
ControllerAdvice:使用在全局异常处理类,当然也可以使用RestControllerAdvice,这样请求的响应是json类型的。
ExceptionHandler:标记该方法处理的异常类型,当发生的异常种类与ExceptionHandler中的值匹配时,便会进入处理方法中。
先把异常处理类贴出来
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
@Value("${spring.mail.username}")
private String from;
@Autowired
JavaMailSender javaMailSender;
@ExceptionHandler(Exception.class)
public Result<String> handlerException(Exception e, HttpServletRequest request) {
Result<String> result = Result.exception(e, request);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
String time = sdf.format(new Date());
String exceptionStr = result.getMsg();
String requestStr = result.getData();
log.error("Time:{} Exception:{} Request:{}", time, exceptionStr, requestStr);
sendSimpleMail(time, result);
return result;
}
/**
* 出现异常则发送邮件通知
*
* @param time
* @param result
*/
public void sendSimpleMail(String time, Result<String> result) {
SimpleMailMessage message = new SimpleMailMessage();
message.setFrom(from);
message.setTo("767638734@qq.com");
message.setSubject("plm异常通知");
StringBuffer text = new StringBuffer();
text.append("发生时间: " + time);
text.append("\n");
text.append("异常种类: " + result.getMsg());
text.append("\n");
text.append("详细请求: " + result.getData());
message.setText(text.toString());
javaMailSender.send(message);
}
}
用到的工具类Result
@Data
public class Result<T> {
private int code;
private String msg;
private T data;
public static <T> Result<T> success(String msg) {
Integer code = ResultCodeEnum.SUCCESS.getCode();
return result(code, msg, null);
}
public static <T> Result<T> success(String msg, T data) {
Integer code = ResultCodeEnum.SUCCESS.getCode();
return result(code, msg, data);
}
public static <T> Result<T> fail(String msg) {
Integer code = ResultCodeEnum.FAILED.getCode();
return result(code, msg, null);
}
public static <T> Result<T> fail(String msg, T data) {
Integer code = ResultCodeEnum.FAILED.getCode();
return result(code, msg, data);
}
public static Result<String> exception(Exception e, HttpServletRequest request) {
Integer code = ResultCodeEnum.FAILED.getCode();
String msg = e.toString();
StringBuffer requestStr = new StringBuffer();
//获取请求方法
String requestMethod = request.getMethod();
requestStr.append(requestMethod + " ");
//请求路径
StringBuffer requestURL = request.getRequestURL();
requestStr.append(requestURL);
if (requestMethod.equals("GET")) {
//GET请求参数
String queryString = request.getQueryString();
if (null != queryString) {
requestStr.append("?");
requestStr.append(queryString);
}
} else {
requestStr.append(" ");
String parametersFromPost = RequestUtil.getParametersFromPost(request);
requestStr.append(parametersFromPost);
}
return result(code, msg, requestStr.toString());
}
public static <T> Result<T> result(Integer code, String msg, T data) {
Result<T> result = new Result<>();
result.setCode(code);
result.setMsg(msg);
result.setData(data);
return result;
}
}
RequestUtil工具类
public class RequestUtil {
/**
* 获取POST请求中Body参数
*
* @param request
* @return 字符串
*/
public static String getParametersFromPost(HttpServletRequest request) {
BufferedReader br = null;
try {
br = new BufferedReader(new InputStreamReader(request.getInputStream(), "UTF-8"));
} catch (IOException e) {
e.printStackTrace();
}
String line = "";
StringBuilder sb = new StringBuilder();
try {
while ((line = br.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
e.printStackTrace();
}
return sb.toString();
}
}
编写一个Controller
@RestController
@RequestMapping("/category")
public class CategoryController {
@Autowired
CategoryService categoryService;
@GetMapping("/getAllCategory")
public List<Category> getAllCategory() {
return categoryService.getAllCategory();
}
@PostMapping("/add")
public Result<Object> add(Category category) {
throw new NullPointerException();
// int affect = categoryService.add(category);
// if (affect == 1) {
// return Result.success("类目插入成功");
// } else {
// return Result.fail("类目插入失败");
// }
}
}
三、发送邮件
(1)导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
(2)配置yaml
spring:
mail:
host: smtp.qq.com
username: 你的邮箱地址
password: 从邮箱官网申请到的密码,不是登录邮箱的密码
default-encoding: UTF-8
我用的是qq邮箱,需要开启SMTP协议,然后生成授权码
四、演示
在第二部分中,我们已经在Controller埋了一个空指针异常,现在我们请求该路径。
(1)请求
(2)日志显示
2019-09-04 16:15:55.339 ERROR 152984 --- [p-nio-80-exec-3] c.y.plm.handler.GlobalExceptionHandler :
Time:2019/09/04 16:15:55
Exception:java.lang.NullPointerException
Request:POST http://localhost/food/add {"name":"红烧鲫鱼"}
(3)邮件通知
大功告成!