我们都知道,项目有两种异常,一种是ERROR,一种是Exception,ERROR导致项目直接崩盘,无法运行,且不能捕获,Exception可以捕获且不影响项目运行,今天要做的就是捕获Exception,当前项目开发使用SSM框架,我原本使用的方法是Controller控制层每一个类每一个方法都有一个try-catch捕获当前方法异常,虽然这种可用,但是会有几个问题:
- 向前端如何返回异常?
即如何保证发生异常时向前端返回的数据统一,因为是不同人员开发,很可能A人员直接将异常扔给前端,而B人员将异常处理之后返回前端一个标识 - 如何记录异常信息?
我们要考虑到系统中每一处代码都有发生异常的可能性(在这里先仅考虑Controller层),即使我们编写异常记录的工具类,难道我们在每处发生异常的地方都调用吗?(不谈资源浪费的情况下,如何确保每个开发人员记录格式相同?如何确保每处Exception都会被记录?)
基于以上两点,我基于SSM框架做了一个Exception的全局异常捕获,邮件提醒(亲测有效)
在这里即使用GlobalExceptionHandler类捕获控制层的异常,代码如下:
package com.single.cong.exception;
import java.io.PrintWriter;
import java.io.StringWriter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import lombok.extern.slf4j.Slf4j;
/**
* 全局捕获异常
*/
@Slf4j
@ControllerAdvice(basePackages = "com.single.cong.controller")
public class GlobalExceptionHandler {
@Autowired
private JavaMailSender javaMailSender;
/**
* 异常信息转化为String类型,等同于e.printStackTrace()输出参数
*
* @param t
* 异常
* @return 异常详细信息
* @author single-聪
* @date 2019年4月15日
* @version 1.0.0
*/
public String getTrace(Throwable t) {
StringWriter stringWriter = new StringWriter();
PrintWriter writer = new PrintWriter(stringWriter);
t.printStackTrace(writer);
StringBuffer buffer = stringWriter.getBuffer();
SimpleMailMessage mailMessage = new SimpleMailMessage();
mailMessage.setFrom("123456789@qq.com");
mailMessage.setTo("1234567890@qq.com");
mailMessage.setSubject("系统Bug,及时修复");
mailMessage.setText(buffer.toString());
javaMailSender.send(mailMessage);
return buffer.toString();
}
/**
* 全局异常捕获,暂时区分两种异常类型,所有运行时异常统一在此方法中
*
* @param e
* 异常
* @author single-聪
* @date 2019年4月15日
* @version 1.0.0
*
*/
@ExceptionHandler(RuntimeException.class)
@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
public void runtimeException(Exception e) {
log.info("全局捕获运行时异常,同时写入日志文件");
e.printStackTrace();
log.error(getTrace(e));
}
/**
* 账号权限信息不足
*
* @param e
* 异常
* @author single-聪
* @date 2019年4月15日
* @version 1.0.0
*
*/
@ExceptionHandler(AccessDeniedException.class)
@ResponseStatus(value = HttpStatus.UNAUTHORIZED)
public void accessDeniedException(Exception e) {
log.info("账号权限信息不足");
e.printStackTrace();
log.error(getTrace(e));
}
}
@ControllerAdvice注解将作用在所有注解了@RequestMapping的控制器的方法上,basePackages代表指定包下面的方法,可以使用不同的类处理不同的异常。
@ExceptionHandler(RuntimeException.class)用于全局处理控制器里的异常,在这里我们只处理运行时异常以及权限验证异常。
@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)返回状态码
参数里面加上Exception之后就可以使用e.printStackTrace()在控制台打印出异常,在这里将打印出的异常存储进日志文件(SpringBoot使用logback)方便查看,这样我们就不需要在每个控制层方法里面写多余的代码了。
注意,邮件发送部分需要更换成自己的账号信息,同时需要配置key。
在使用这个全局方法之前,我的控制层代码如下:
// 删除
@RequestMapping(value = "/delete.sose", consumes = "application/json;charset=UTF-8")
public Map<String, Object> delete(@RequestBody String c) throws ParseException {
java.util.Map<String, Object> map = new HashMap<>();
try {
// 获取前端传回删除数据Id
JSONObject strj = new JSONObject(c);
evolveService.delete(strj.getInt("id"));
map.put("info", "success");
} catch (Exception e) {
e.printStackTrace();
map.put("info", e);
}
return map;
}
使用异常捕获机制之后,只要你能够将系统中的异常罗列出来,那么就可以根据异常类型决定返回指定的状态码,前端只处理状态码为200的数据,一旦状态码不是200就可以认为服务器端数据处理失败。至于具体的错误信息需不需要返回前端可以根据业务决定。