在写应用程序的时候遇到异常,我们自然而然会用try catch捕获异常处理,但这样到处捕获比较繁琐,代码也比较冗余,直接抛出异常又不大友好。这时就可以做个通用的异常处理。统一大部分的异常,也可以比较专注于业务。我们可以做个控制层切面,下层不断往上层抛,控制层上统一异常处理。
我们可以用@ControllerAdvice
和@ExceptionHandler
注解实现异常处理,应用抛出异常的时候跳转到一个友好的提示页面,以此规避页面上打印出大量异常堆栈信息,影响体验不说,还有可能被有心的骇客用来分析系统漏洞。如果前端Ajax异步化,也可以统一异常信息,返回json等格式的数据。@ControllerAdvice
注解捕获控制层的异常,所有对于DAO、Service层不想吃掉的异常都要往上抛。@ExceptionHandler
定义方法处理的异常类型,最后将Exception对象和请求URL映射到相应的异常界面中,这适应Spring Mvc控制层那一套,如果是异步,加个@ResponseBody
注解,返回异常对象。
定义一个全局异常处理类,捕获异常,将操作用户信息和异常信息插入异常日志表。
异常处理类
- /**
- * 统一异常处理
- *
- * @version V5.0
- * @author wenqy
- */
- @ControllerAdvice
- public class GlobalExceptionHandler {
- private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
- @Autowired
- ExceptionLogExecutors executors;
- /**
- * 403 异常
- */
- // @ExceptionHandler({ UnauthorizedException.class })
- // public String unauthorized(UnauthorizedException ue) {
- // return “error/403”;
- // }
- /**
- * 405 异常
- * @param ue
- * @return
- * @author wenqy
- */
- @ExceptionHandler({ HttpRequestMethodNotSupportedException.class })
- public String notSupportedException(HttpRequestMethodNotSupportedException ue) {
- return “error/405”;
- }
- @ExceptionHandler({ MyBusinessException.class })
- public String serviceException(MyBusinessException se, Model model) {
- logger.error(“业务异常:” + se.getMessage(), se);
- model.addAttribute(“message”, se.getMessage());
- return “error/businessception”;
- }
- /**
- * 服务器异常
- * @param throwable
- * @param model
- * @return
- * @author wenqy
- */
- @ExceptionHandler({ Error.class, Exception.class, RuntimeException.class, Throwable.class })
- @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
- public String globalException(Throwable throwable, Model model) {
- String clientIp = WebUtils.getClientIp();
- StringBuffer traceString = new StringBuffer(“ip:”).append(clientIp);
- String stackTraceAsString = ExceptionUtils.getStackTraceAsString(throwable);
- traceString.append(“exection:”).append(stackTraceAsString);
- logger.error(traceString.toString());
- ExceptionLog exceptionLog = new ExceptionLog();
- exceptionLog.setUserId(IdGenerator.randomLong());
- exceptionLog.setHostIp(clientIp);
- exceptionLog.setCreatedDate(new Date());
- exceptionLog.setExptTime(new Date());
- exceptionLog.setUuid(IdGenerator.randomString(11));
- exceptionLog.setStatckTrace(stackTraceAsString);
- // 扔到线程池执行
- this.executors.execute(new ExceptionLogThread(exceptionLog));
- // 抛出异常码
- model.addAttribute(“errCode”, exceptionLog.getUuid().substring(0,3)
- + ” “ + exceptionLog.getUuid().substring(3,7)
- + ” “ + exceptionLog.getUuid().substring(7,11));
- return “error/500”;
- }
- }
自定义异常
我们可以自定义些业务异常,用短语标识。
- /**
- * 自定义业务异常
- *
- * @version V5.0
- * @author wenqy
- */
- @SuppressWarnings(“serial”)
- public class MyBusinessException extends RuntimeException {
- private String errCode;
- private String message;
- private String detail;
- public MyBusinessException() {
- super();
- }
- public MyBusinessException(String message) {
- this(null,message,null);
- }
- public MyBusinessException(String errorCode, String message) {
- this(errorCode,message,null);
- }
- public MyBusinessException(String errorCode, String message, String detail) {
- super();
- this.errCode = errorCode;
- this.message = message;
- this.detail = detail;
- }
- public String getErrCode() {
- return errCode;
- }
- public void setErrCode(String errCode) {
- this.errCode = errCode;
- }
- public String getMessage() {
- return message;
- }
- public void setMessage(String message) {
- this.message = message;
- }
- public String getDetail() {
- return detail;
- }
- public void setDetail(String detail) {
- this.detail = detail;
- }
- }
异常日志
异常日志的保存应该是异步的,用工作线程处理。
- /**
- *
- * 异常日志保存线程
- * @version V5.0
- * @author wenqy
- */
- public class ExceptionLogThread implements Runnable {
- private ExceptionLog exceptionLog;
- public ExceptionLogThread(ExceptionLog log) {
- this.exceptionLog = log;
- }
- @Override
- public void run() {
- // 加载异常日志处理服务
- ExceptionLogService logService = (ExceptionLogService) SpringAppContextHolder
- .getBean(ExceptionLogService.class);
- logService.saveExceptionLog(exceptionLog);
- }
- }
避免线程的开销太大,定义线程池。
- /**
- * 异常日志处理线程池
- *
- * @version V5.0
- * @author wenqy
- */
- @SuppressWarnings(“serial”)
- @Component
- public class ExceptionLogExecutors extends ThreadPoolTaskExecutor {
- public ExceptionLogExecutors() {
- super.setCorePoolSize(10);
- super.setMaxPoolSize(20);
- }
- }
定义Spring 应用上下文持有者,以便注入bean。
- /**
- *
- * Spring ApplicationContext 持有者
- * @version V5.0
- * @author wenqy
- */
- @Component
- public class SpringAppContextHolder implements ApplicationContextAware {
- /**
- * Spring 自动注入
- */
- private static ApplicationContext applicationContext = null;
- @Override
- public void setApplicationContext(ApplicationContext context) throws BeansException {
- if (applicationContext == null) {
- applicationContext = context;
- }
- }
- public static ApplicationContext getApplicationContext() {
- return applicationContext;
- }
- /**
- * getBeanByName
- * @param name
- * @return
- * @author wenqy
- */
- public static Object getBean(String name){
- return getApplicationContext().getBean(name);
- }
- /**
- * getBeanByClazz
- * @param clazz
- * @return
- * @author wenqy
- */
- public static <T> T getBean(Class<T> clazz){
- return getApplicationContext().getBean(clazz);
- }
- public static <T> T getBean(String name,Class<T> clazz){
- return getApplicationContext().getBean(name, clazz);
- }
- }
分层处理
然后是分层的那一套。。。
异常日志VO
- /**
- * 异常日志VO
- *
- * @version V5.0
- * @author wenqy
- */
- @SuppressWarnings(“serial”)
- public class ExceptionLog implements Serializable {
- private Long id;
- private String uuid; // 系统异常码
- private String hostIp; // ip
- private Date exptTime; // 异常时间
- private String statckTrace; // 异常栈
- private Date createdDate;
- private Long userId; // 操作用户Id
- // 省略setter、getter方法
- }
Mybatis Mapper定义DAO操作
- /**
- * 异常日志Mapper
- *
- * @version V5.0
- * @author wenqy
- */
- @Repository
- public interface ExceptionLogMapper {
- /**
- * 保存日志日志
- * @param exceptionLog
- * @author wenqy
- */
- public void saveExceptionLog(@Param(“exceptionLog”) ExceptionLog exceptionLog);
- }
配置文件 ExceptionLogMapper.xml
- <!– namespace必须指向Dao接口 –>
- <mapper namespace=“com.wenqy.mapper.one.ExceptionLogMapper”>
- <!– 保存日志 –>
- <insert id=“saveExceptionLog”>
- insert into exception_log(uuid,host_ip,expt_time,created_date,stacktrace,user_id)
- values(#{exceptionLog.uuid},#{exceptionLog.hostIp},#{exceptionLog.exptTime},
- #{exceptionLog.createdDate},#{exceptionLog.statckTrace},#{exceptionLog.userId})
- </insert>
- </mapper>
业务逻辑层 Service
- /**
- * 异常日志 Service
- *
- * @version V5.0
- * @author wenqy
- * @date 2017年12月23日
- */
- @Service
- public class ExceptionLogServiceImpl implements ExceptionLogService {
- @Autowired
- private ExceptionLogMapper exceptionLogMapper;
- @Override
- public void saveExceptionLog(ExceptionLog exceptionLog) {
- exceptionLogMapper.saveExceptionLog(exceptionLog);
- }
- }
定义服务器异常页面模板 500.ftl
- <#assign webRoot=request.contextPath />
- <!DOCTYPE html>
- <html lang=“en”>
- <head>
- <meta charset=“utf-8”>
- <meta http-equiv=“X-UA-Compatible” content=“IE=edge”>
- <meta name=“viewport” content=“width=device-width, initial-scale=1”>
- <!– The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags –>
- <meta name=“description” content=“”>
- <meta name=“author” content=“”>
- <link rel=“icon” href=“${webRoot}/static/favicon.ico”>
- <title>Signin Template for Bootstrap</title>
- <!– Bootstrap core CSS –>
- <link href=“${webRoot}/static/css/bootstrap/bootstrap.min.css” rel=“stylesheet”>
- <!– IE10 viewport hack for Surface/desktop Windows 8 bug –>
- <link href=“${webRoot}/static/css/bootstrap/ie10-viewport-bug-workaround.css” rel=“stylesheet”>
- <!– Custom styles for this template –>
- <link href=“${webRoot}/static/css/custom/signin.css” rel=“stylesheet”>
- <!– Just for debugging purposes. Don’t actually copy these 2 lines! –>
- <!–[if lt IE 9]><script src=”../../assets/js/ie8-responsive-file-warning.js”></script><![endif]–>
- <script src=“${webRoot}/static/js/bootstrap/ie-emulation-modes-warning.js”></script>
- <!– HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries –>
- <!–[if lt IE 9]>
- <script src=“${webRoot}/static/js/bootstrap/html5shiv.min.js”></script>
- <script src=“${webRoot}/static/js/bootstrap/respond.min.js”></script>
- <![endif]–>
- </head>
- <body>
- <div class=“container”>
- <div class=“panel panel-success”>
- <div class=“panel-heading”>
- <input id=“backBtn” class=“btn btn-primary” onclick=“history.go(-1);” type=“button” value=“返回上一页” >
- </div>
- <div class=“panel-body”>
- <span>系统繁忙,请稍后重试,若仍有问题,请联系客服,电话:XXX-XXXX-XXXX</span>
- <span>请将状态码 <strong>${ errCode }</strong> 告知客服</span>
- <br/>
- </div>
- </div>
- </div>
- </body>
- </html>
业务异常的界面,常用HTTP异常状态相应的异常界面等等,不一一列举了。
还有一些异常工具类,ID生成器、Web工具类等等。。。
查看效果
发生业务异常测试。。。
测试发生500异常
插入异常日志,ip记录有问题。。。
大致统一异常处理就这样,当然还有其他实现方式,但这种切面方式胜在侵入性低,解耦强。。。
网易云音乐就不插了,伤不起。。。
One thought on “spring boot学习系列之统一异常处理6”