异常统一处理(springboot)

现象:

当我们访问页面时,由于各种人工或系统出错导致返回一个错误页面Whitelabel Error Page,这与业务逻辑并不契合,我们不知道他错误的原因是什么,概念模糊

思路:

我们将它会出现的各种错误指定到各个不同的错误页面,如访问不存在的页面和接口返回404页面,访问账户没有权限的页面返回403页面,访问页面时系统内部出错,返回500页面。

实现:

  • 前端

    • 使用thymeleaf碎片组装器,一个组装页面,三个错误页面

      index2.html

      <!DOCTYPE html>
      <html
         lang="en"
         xmlns="http://www.w3.org/1999/xhtml"
         xmlns:th="http://www.thymeleaf.org"
         xmlns:v-bind="http://www.w3.org/1999/xhtml"
         xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"
         xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"
      >
         <head>
            <meta charset="UTF-8" />
            <title>SFAC</title>
      
            <!-- CSS -->
            <!-- Bootstrap -->
            <link href="/vendors/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet" />
            <!-- Font Awesome -->
            <link href="/vendors/font-awesome/css/font-awesome.min.css" rel="stylesheet" />
            <!-- Custom styling plus plugins -->
            <link href="/css/custom.min.css" rel="stylesheet" />
      
            <!-- JS -->
            <!-- jQuery -->
            <script src="/vendors/jquery/dist/jquery.min.js"></script>
            <!-- Bootstrap -->
            <script src="/vendors/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
         </head>
         <body class="nav-md">
            <div class="container body">
               <div class="main_container">
                  <div th:if="${template}">
                     <div th:replace="${template}"></div>
                  </div>
               </div>
            </div>
      
            <!-- JS -->
            <!-- Custom -->
            <script src="/js/custom.min.js"></script>
         </body>
      </html>
      

      404.html

      <!-- page content -->
      <div class="col-md-12">
          <div class="col-middle">
              <div class="text-center text-center">
                  <h1 class="error-number">404</h1>
                  <h2>Sorry but we couldn't find this page</h2>
                  <p>This page you are looking for does not exist <a href="#">Report this?</a>
                  </p>
                  <div class="mid_center">
                      <a href="javascript:history.back()" class="btn btn-secondary source">
                          <i class="ace-icon fa fa-arrow-left"></i>
                          Go Back
                      </a>
                      <a href="#" class="btn btn-secondary source">
                          <i class="ace-icon fa fa-tachometer"></i>
                          Dashboard
                      </a>
                  </div>
              </div>
          </div>
      </div>
      <!-- /page content -->
      

      403,500略

  • 后端

    • 页面控制器CommonController

      @Controller
      @RequestMapping("/common")
      public class CommonController {
      
         /**
          * 127.0.0.1/common/403 ---- get
          */
         @GetMapping("/403")
         public String errorPageFor403() {
            return "index2";
         }
      
         /**
          * 127.0.0.1/common/404 ---- get
          */
         @GetMapping("/404")
         public String errorPageFor404() {
            return "index2";
         }
      	/**
          * 127.0.0.1/common/500 ---- get
          */
         @GetMapping("/500")
         public String errorPageFor500() {
            return "index2";
         }
      }
      
    • 创建实体类,存储异常信息,使用jpa自动生成表在数据库

      @Entity
      @Table(name = "common_exception_log")
      public class ExceptionLog extends AbstractEntity {
         private String ip;
         private String path;
         private String className;
         private String methodName;
         private String exceptionType;
         private String exceptionMessage;
      
         public String getIp() {
            return ip;
         }
      
         public void setIp(String ip) {
            this.ip = ip;
         }
      
         public String getPath() {
            return path;
         }
      
         public void setPath(String path) {
            this.path = path;
         }
      
         public String getClassName() {
            return className;
         }
      
         public void setClassName(String className) {
            this.className = className;
         }
      
         public String getMethodName() {
            return methodName;
         }
      
         public void setMethodName(String methodName) {
            this.methodName = methodName;
         }
      
         public String getExceptionType() {
            return exceptionType;
         }
      
         public void setExceptionType(String exceptionType) {
            this.exceptionType = exceptionType;
         }
      
         public String getExceptionMessage() {
            return exceptionMessage;
         }
      
         public void setExceptionMessage(String exceptionMessage) {
            this.exceptionMessage = exceptionMessage;
         }
      }
      
    • 使用mybatis进行异常信息的插入操作

      dao层和service层接口

      @Repository
      @Mapper
      public interface ExceptionLogDao {
      
         @Insert("insert into common_exception_log (ip, path, class_name, method_name, " +
               "exception_type, exception_message) " +
               "values (#{ip}, #{path}, #{className}, #{methodName}, " +
               "#{exceptionType}, #{exceptionMessage})")
         @Options(useGeneratedKeys = true, keyColumn = "id", keyProperty = "id")
         void insertExceptionLog(ExceptionLog exceptionLog);
      
         @Select("select * from common_exception_log where path = #{path} and " +
               "method_name = #{methodName} and exception_type = #{exceptionType} limit 1")
         ExceptionLog getExceptionLogByParam(
               @Param("path") String path,
               @Param("methodName") String methodName,
               @Param("exceptionType") String exceptionType);
      }
      /**
       * ExceptionLogService
       */
      public interface ExceptionLogService {
      
         Result<ExceptionLog> insertExceptionLog(ExceptionLog exceptionLog);
      }
      
    • service实现类

      @Service
      public class ExceptionLogServiceImpl implements ExceptionLogService {
      
          //使用spring的依赖注入
         @Autowired
         private ExceptionLogDao exceptionLogDao;
      
         @Override
         @Transactional
         public Result<ExceptionLog> insertExceptionLog(ExceptionLog exceptionLog) {
            ExceptionLog temp = exceptionLogDao.getExceptionLogByParam(
                  exceptionLog.getPath(),
                  exceptionLog.getMethodName(),
                  exceptionLog.getExceptionType()
            );
            if (temp != null) {
               return new Result<>(Result.ResultStatus.SUCCESS.code, "异常已经记录.");
            }
      
            exceptionLog.setCreateDate(LocalDateTime.now());
            exceptionLog.setUpdateDate(LocalDateTime.now());
      
            exceptionLogDao.insertExceptionLog(exceptionLog);
      
            return new Result<>(Result.ResultStatus.SUCCESS.code, "插入成功", exceptionLog);
         }
      }
      
    • 控制器

      @RestController
      @RequestMapping("/api")
      public class ExceptionLogDaoController {
      
          @Autowired
          private ExceptionLogService exceptionLogService;
          @PostMapping("/exceptionLog")
          public Result<ExceptionLog> insertExceptionLog(@RequestBody ExceptionLog ex) {
              return exceptionLogService.insertExceptionLog(ex);
          }
      }
      
    • 异常控制器,当页面出错进入该控制器

      //该注解是为了出现异常进入该类做处理
      @ControllerAdvice
      public class ExceptionController {
          @Autowired
          private ExceptionLogService exceptionLogService;
      
          //引入日志
          private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionController.class);
      
          //抓取指定异常
          @ExceptionHandler(value = Exception.class)
          public ModelAndView exceptionHandle(HttpServletRequest request, Exception exception) {
              //打印异常的堆栈信息
              exception.printStackTrace();
              LOGGER.debug(exception.getMessage());
              int code = 200;
              String message = "";
              String data = "";
              //将算术异常作为没有权限,比较是否是指定异常
              if (exception instanceof ArithmeticException) {
                  code = 403;
                  message = "没有权限";
                  data = "/common/403";
              } else {
                  code = 500;
                  message = "服务器内部错误";
                  data = "/common/500";
              }
              //保存报错信息到数据库
              insertExceptionLog(request, exception);
              // 判断是否为接口
              // 包装数据返回不同的 modelAndView
              if (judgeInterface(request)) {
                  //是接口执行
                  ModelAndView mad = new ModelAndView(new MappingJackson2JsonView());
                  mad.addObject("status", code);
                  mad.addObject("message", message);
                  mad.addObject("data", data);
                  return mad;
              }
              //是页面执行
              return new ModelAndView("redirect:" + data);
          }
      
          //该类为内部类,判断是否是一个接口,即对应类上是否有RestController,ResponseBody,对应方法是否有ResponseBody注解
          private boolean judgeInterface(HttpServletRequest request) {
              HandlerMethod handlerMethod = (HandlerMethod) request.getAttribute
                      ("org.springframework.web.servlet.HandlerMapping.bestMatchingHandler");
              RestController[] rs1 = handlerMethod.getBeanType().getDeclaredAnnotationsByType(RestController.class);
              RestController rss = handlerMethod.getBeanType().getDeclaredAnnotation(RestController.class);
              ResponseBody[] rs2 = handlerMethod.getBeanType().getDeclaredAnnotationsByType(ResponseBody.class);
              ResponseBody[] rs3 = handlerMethod.getMethod().getDeclaredAnnotationsByType(ResponseBody.class);
              return rs1.length > 0 || rs2.length > 0 || rs3.length > 0;
          }
      
          //该类为增加报错信息的类
          private void insertExceptionLog(HttpServletRequest request, Exception exception) {
              ExceptionLog el = new ExceptionLog();
              el.setIp(request.getRemoteAddr());
              el.setPath(request.getServletPath());
              HandlerMethod handlerMethod = (HandlerMethod) request.getAttribute
                      ("org.springframework.web.servlet.HandlerMapping.bestMatchingHandler");
              el.setClassName(handlerMethod.getBeanType().getName());
              el.setMethodName(handlerMethod.getMethod().getName());
              el.setExceptionType(exception.getClass().getName());
              el.setExceptionMessage(exception.getMessage());
              exceptionLogService.insertExceptionLog(el);
          }
      }
      
    • 页面找不到或接口找不到处理

    • 自定义类,与源码BasicErrorController保持一致的类注解,重写errorHtml(处理页面)和error(处理接口)方法

      @Controller
      @RequestMapping("${server.error.path:${error.path:/error}}")
      public class MyBasicErrorController implements ErrorController {
      
          @RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
          public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
              //重定向到404
              return new ModelAndView("redirect:/common/404");
          }
      
          @RequestMapping
          public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
              Map<String, Object> body = new HashMap<>();
              body.put("status", 404);
              body.put("message", "NO FOUND");
              body.put("data", "/common/404");
              return new ResponseEntity<>(body, HttpStatus.NOT_FOUND);
          }
      }
      

2.猜想:页面或接口不存在是否可以和上面处理出错页面或接口的方式保持一致?

实现:不行(我没有实现),因为如果让ExceptionController可以捕获此异常(即页面找不到或接口),则需要在全局配置中配置以下两项

#for exception
#spring.mvc.throw-exception-if-no-handler-found=true
#spring.web.resources.add-mappings=false

但是会出现问题:访问静态资源将无法找到,博主做了很多尝试没能实现,希望有大佬可以指点一下。

因此如果要与第一种异常处理一致,则将只能MyBasicErrorController中的两个方法实现,在方法中做统一处理。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值