[Day - 19] - Spring 例外处理之优雅化客制化错误讯息原理与设计

Abstract

相信许多人都有遇过例外处理的事件,但又不想要让内部核心的错误资讯呈现在前端上,仅记录在LOG档中,并呈现一段漂漂亮亮的讯息呈现至前端,小编今天将延续昨天的架构再多上一层错误分析处理给大家进行学习,为了防止骇客的破坏,为了守护系统平台的和平,贯彻错误防止与守护的力量,我是小编威斯丁,现在带领你们一起进去REST API最佳的建议控制器注解模式(@RestControllerAdvice)的世界,可提供开发者进行告知使用者哪些是必备条件,带领你快速达到防呆效用及提升系统的保障性质罗。

Principle Introduction

Spring 框架中提供了错误例外回覆的功能,会在系统启动中,会启动一个SpringApplicationRunListeners,进行一个错误分析器的观察器,最终都会都过HandlerExceptionResolver元件进行分析处理,若没有预设此例外处理控制器模式注解(@ControllerAdvice),则会统一回覆一个JSON格式,范例请参照下方格式。

{
    "timestamp": "2021-09-17T17:54:26.677+00:00",
    "status": 500,
    "error": "Internal Server Error",
    "path": "/sea/v1/taiwan/update"
}

错误讯息可能会列出所有错误的程序码片段,故Spring框架为优雅及客制化错误讯息,为加强化服务导向架构(SOA,Service-Oriented Architecture)开发结构,则提供了一个注解模式元件称为例外处理控制器(@ControllerAdvice),我们通过以下范例来解释此注解模式运用。

范例一、配置例外处理程序(@ExceptionHandler),并透过受限制类别(assignableType)进行限制支援的控制器类别。

@RestControllerAdvice(assignableTypes= {ProductController.class})
public class SeaControllerAdvice {

    @ExceptionHandler(ResourceNotFoundException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    ResponseData resourceNotFound(ResourceNotFoundException ex) {
        return new ResponseData().setCode(ResponseStatusEnum.RESOURCES_NOT_FOUND.getCode())
                                .setStatus(ResponseStatusEnum.RESOURCES_NOT_FOUND.getStatus())
                                .setData(ex.toString());
    }
    
    @ExceptionHandler(SeaFoodRetailerGenericException.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    ResponseData retailerGenericException(SeaFoodRetailerGenericException ex) {
        return new ResponseData().setCode(ResponseStatusEnum.SERVICE_INTERNAL_ERROR.getCode())
                .setStatus(ResponseStatusEnum.SERVICE_INTERNAL_ERROR.getStatus())
                .setData(ex.toString());
    }
    
 }
 
 public interface SeaFoodRetailerService {

   .....
   .....
   .....
   
    default void validateNullId(SeaFood entity) throws SeaFoodRetailerGenericException {
        if (entity.getId() ==null )
            throw new SeaFoodRetailerGenericException("Sea Food Id is REQUIRED ! ");
    }
    
    default void validateResuouceNotFound(SeaFood bodyEntity) throws ResourceNotFoundException {
        if ( !this.listSeaFoodProducts()
                .stream()
                .anyMatch(seaFood -> seaFood.getId().equalsIgnoreCase(bodyEntity.getId())) )
            throw new ResourceNotFoundException();

    }
}

范例一 测试结果,可看出taiwan API有支援错误回覆格式,China API未支援。

// {host:port}/sea/v1/taiwan/update
{
    "code": 503,
    "status": "AH ! Oops ! Oops ! Sea Food retailer service is BROKEN ! BROKEN ! BROKEN !",
    "data": "sw.spring.sample.exception.SeaFoodRetailerGenericException: Sea Food Id is REQUIRED ! "
}

// {host:port}/sea/v1/china/update
{
    "timestamp": "2021-09-17T18:58:38.965+00:00",
    "status": 500,
    "error": "Internal Server Error",
    "path": "/sea/v1/china/update"
}

范例二、透过basePackages进行配置专案路径,限制哪些package 路径下才有支援错误解析,范例为整个soa目录下控制器受@ControllerAdvice保护。

@RestControllerAdvice(assignableTypes= {ProductController.class},basePackages = {"sw.spring.sample.services" })
public class SeaControllerAdvice {

  ...
  ...
  ...
  
}

范例二、可看出原先未支援的/sea/v1/china/update已呈现我们客制化的错误讯息。

{
    "code": 503,
    "status": "AH ! Oops ! Oops ! Sea Food retailer service is BROKEN ! BROKEN ! BROKEN !",
    "data": "sw.spring.sample.exception.SeaFoodRetailerGenericException: Sea Food Id is REQUIRED ! "
}

透过以上的多元化的例外处理控制器可看出不仅可弹性配置限制范围,亦可支援客制化多种错误处理回覆格式。

Structure

由下图可看出,每个系统服务的入口点为ApplicationRun此启动点,在此执行绪端建立一个监听器实体(SpringApplicationRunListeners)进行监听各种错误讯息,其中所有错误会透过一个推播器(EventPublishingRunListener)进行发送例外事件,这是一个多点事件发送器(MulticastEvent),为一种观察设计者模式(Observer Pattern),当例外事件发送出去後,最终透过DispatcherServlet中进行分析与处理各式例外事件(HandlerExceptionResolver)。

图一、Spring 错误监听方法流程图
image

图二流程图为例外事件控制器在Restful API上的运作流程,首先须注意,若有配置例外事件控制器(ControllerAdvice),请开发者别再配置异常处理程序(ExceptionHandler),不然API则会转出预设的错误例外JSON资讯,异常处理程序(@ExceptionHandler)与回覆状态(@ResponseStatus)需一同配置於例外事件控制器(ControllerAdvice)中,才能够接取开发者所配置的客制化错误回覆讯息,不然皆产生预设的错误回覆格式喔。

图二、Spring 控制器例外事件回覆讯息流程图
image

以上为Spring 核心例外处理事件流程,提供各外开发者做参考。

Sample Source

java sample source - spring-sample-controller

PostMan API Trigger sample source

Reference Url

tabnine-DispatcherServlet

Complete Guide to Exception Handling in Spring Boot

Conclusion

後面我们会一直延续这个范例成长成一个系统。小编在前端小有研究,因Spring有提供一个GUI监测介面采用Angular开发,不过使用起来不是很顺畅,而且配置太麻烦,不是小编想要的简单概念,故最後小编选择整合入一个Vue的前端框架进行开发,搭配Vuetify GUI设计框架,这套小编目前觉得用起来还不错,比Creative-Tim、Element-UI或Framework-7好用的多,是2018/02才开发出的稳定版本,方便於转导出我们所概述的领域驱动设计(DDD,Domain Driven Design)服务概念,为了在明年的主题留下一颗小彩蛋,有兴趣的全端开发者们,欢迎一起来探讨,小编哪边说得不好请多多来指导小编。


<<:  EP19 - [TDD] 订单 API 串接 (2/2)

>>:  Day21 - 用 Ruby on Rails 抓台湾证券交易所资料-除权除息计算结果表

Golang 转生到web世界 - Gin HTML渲染

Golang Gin HTML渲染 首先我们需要在程序码所在的资料夹下,建立一个view的资料夹,并...

DAY30 - [React] useMemo 与 後续

今日文章目录 前言 useMemo() 实作纪录 参考资料 後续 今天要练习 useMemo(),...

D18 文件修改页 Modify doc

文件创建後可能要修改标记或是更改上传的档案 只能修改自己发的文件 先看使用者是否登入以及要修改的文件...

[Day24] HTB Devel

URL : https://app.hackthebox.eu/machines/3 IP : 1...

Day1.认识GUI和Tkinter

图形使用者介面(Graphical User Interface,GUI) 指透过点击图示执行隐含的...