徹底解決SpringBoot 介面404異常響應~

碼農談IT發表於2023-10-09

來源:JAVA日知錄

大家好,我是飄渺。今天,我們將繼續探討DDD與微服務架構的系列主題。

在前一篇文章 優雅遠端呼叫,微服務中ACL與OpenFeign的絕佳配合!,一位粉絲提到在DailyMart中,關於Feign的error解碼器對404錯誤處理不夠友好,會返回空物件而不報錯。

徹底解決SpringBoot 介面404異常響應~

實際上,這個問題並不是Feign解碼器的問題。

眾所周知,在Spring Boot中,當我們訪問一個不存在的介面時,會觸發404異常,介面呼叫會返回如下JSON格式的錯誤訊息:

{
  "timestamp""2023-09-23T03:36:50.503+00:00",
  "status"404,
  "error""Not Found",
  "path""/api/inventory/xxxx"
}

但是在DailyMart中,這個錯誤訊息會被全域性包裝類GlobalResponseBodyAdvice處理,因此出現了以下奇怪的錯誤資訊:code碼為OK表示操作正常,但data訊息體卻表示404異常。由於這種情況,OpenFeign的異常解碼器無法正確處理。

{
    "code""OK",
    "message"null,
    "data": {
        "timestamp""2023-09-23T03:36:50.503+00:00",
        "status"404,
        "error""Not Found",
        "path""/api/inventory/xxxx"
    },
    "timestamp"1695440210514
}

因此,問題的關鍵在於解決Spring Boot中404異常處理的問題,以便能夠返回正確的響應結果。

1. SpringBoot 的預設錯誤處理機制

Spring Boot預設為我們提供了BasicErrorController來處理全域性/error請求。BasicErrorController提供兩種錯誤響應方式,一種是針對頁面請求的錯誤響應,另一種是針對JSON請求的錯誤響應。

徹底解決SpringBoot 介面404異常響應~

2. 自定義錯誤響應

在SpringBoot中,BasicErrorController是由自動配置類ErrorMvcAutoConfiguration負責載入。

@AutoConfiguration(before = WebMvcAutoConfiguration.class)
@ConditionalOnWebApplication(type 
= Type.SERVLET)
@ConditionalOnClass({ Servlet.classDispatcherServlet.class })
@EnableConfigurationProperties(
{ ServerProperties.classWebMvcProperties.class })
public class ErrorMvcAutoConfiguration 
{
  ...

 @Bean
 @ConditionalOnMissingBean(value = ErrorController.classsearch = SearchStrategy.CURRENT)
 public BasicErrorController basicErrorController(ErrorAttributes errorAttributes,
   ObjectProvider<ErrorViewResolver> errorViewResolvers)
 
{
  return new BasicErrorController(errorAttributes, this.serverProperties.getError(),
    errorViewResolvers.orderedStream().toList());
 }
}

根據上述配置,只要我們自己配置一個ErrorController,就可以覆蓋掉BasicErrorController的預設行為。下面的程式碼示例展示瞭如何透過繼承AbstractErrorController並重寫error方法來自定義錯誤響應,以使其與專案整體風格一致:

@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class CustomErrorController extends AbstractErrorController {
 ...

    @RequestMapping
    public Result<Void> error(HttpServletRequest request) {
   
        return ResultFactory.fail(String.valueOf(status),message);
    
    }

    @RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
    public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
        ...
    }
}

透過這種方式,當我們訪問不存在的方法時,將會返回符合預期的JSON響應結果:

{
    "code""404 NOT_FOUND",
    "message""No message available: /api/inventory/verify",
    "data"null,
    "timestamp""1696598118098"
}

3. 微服務中的解決方案

上述方案在單服務中表現良好,但在微服務體系中,每個服務都需要建立一個獨立的CustomErrorController,這顯然違反了DRY(Don't Repeat Yourself)原則。

在DailyMart中,我們封裝了一個公共模組dailymart-web-spring-boot-starter,專門處理Web請求的邏輯,包括全域性響應包裝和異常處理。我們可以將CustomErrorController放置在此模組中,並在配置類中載入該Controller。

/**
 *  註冊SpringBoot預設異常處理器
 */

@Bean
public CusotmErrorController globalErrorController(ErrorAttributes errorAttributes){
  return new CusotmErrorController(errorAttributes);
}

然而,這種做法在啟動服務時可能會引發以下異常:

Caused by: java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'customerErrorController' method 
com.jianzh5.dailymart.springboot.starter.web.CustomerErrorController#errorHtml(HttpServletRequest, HttpServletResponse)
to { [/error], produces [text/html]}: There is already 'basicErrorController' bean method

這是因為Spring Boot本身已經有一個處理/error請求的控制器BasicErrorController,而我們又定義了一個CustomErrorController來處理/error請求。

3.1 解決方案

這個問題有兩種解決方案。

第一種解決方案是確保系統在啟動時能夠優先載入自定義的CustomErrorController

根據前文提到的BasicErrorController配置的條件註解,一旦定義了ErrorController就不會再載入BasicErrorController,所以只需要在自動配置類上加上@AutoConfiguration(before = ErrorMvcAutoConfiguration.class)註解,確保CustomErrorController在BasicErrorController之前載入。

第二種解決方案是在載入CustomErrorController時修改預設的BasicErrorController的攔截路徑。

如前所述,BasicErrorController的攔截路徑為@RequestMapping("${server.error.path:${error.path:/error}}"),如果我們定義了error.path的值,它就會優先使用。因此,只需設定error.path的值即可解決問題。

@PostConstruct
public void customizeErrorPath() {
  // 修改 server.error.path 的值為自定義值
  System.setProperty("error.path""/deprecated/error");
}

小結

透過本文,我們探討了Spring Boot中的404異常處理問題,以及如何透過自定義ErrorController來解決這一問題。特別是在微服務架構中,我們介紹瞭如何利用共享模組的方法來更有效地管理異常處理。希望這些實踐經驗對您有所幫助,提高了您在微服務開發中處理異常情況的能力。我們非常歡迎各位讀者提出寶貴的意見和建議,以進一步完善這個系列文章。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70024924/viewspace-2987463/,如需轉載,請註明出處,否則將追究法律責任。

相關文章