我们都知道,项目有两种异常,一种是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就可以认为服务器端数据处理失败。至于具体的错误信息需不需要返回前端可以根据业务决定。