1. 程式人生 > >十分鐘通過一個實際問題,真正教會大家如何解決Bug

十分鐘通過一個實際問題,真正教會大家如何解決Bug

## 前言 這篇文章從實際問題 -> 問題解決步驟 -> 問題解決思路,幫助大家能夠明白如何在程式中發現問題,定位問題,解決問題。並真正理解那些問題解決思路。 首先說說這個實際問題是什麼,又是怎麼遇到的。 我這邊做了一個操作日誌模組,需要提供獨立查詢頁面。正好集團內部有一個xxx前端產品,可以簡單配置就生成一個報表頁面。 但是由於該產品請求http介面時,會自動加入一個“sorts=[]”的引數,作為報表排序的依據。但是悲劇發生了。請求後,後端伺服器返回一個400-bad request。 而xxx前端產品的mock資料就沒問題。說明是後端差異造成的。所以需要後端優先解決。 ## 定位問題 ### 斷點定位 通過遠端debug(因為是部署到遠端預發環境的),進行斷點檢視。 首先將斷點打在對應Controller上,發現沒用。 緊接著將斷點打在了DispatcherServlet的doService方法上,發現還是沒用。 那麼再往前就不是SpringBoot這樣的應用服務範圍了,而是屬於Tomcat這樣的Web服務範圍了。 於此同時,我們發現400請求的頁面與tomcat的錯誤頁面很相似。由於很久沒有看到tomcat錯誤頁面,所以並沒有在第一時間察覺。 ### 資料查詢 這時候,已經很難通過斷點方式進行查詢了。但是我們已經有一定的把握將問題定位在tomcat這一web容器了。並且通過之前的介面測試,確定問題發生在“[”這樣字元上。 所以,直接通過百度查詢“tomcat 非法字元 400“這樣的關鍵字,找到了如下的部落格: [解決springboot專案請求出現非法字元問題 java.lang.IllegalArgumentException:Invalid character found in the request target. The valid characters are defined in RFC 7230 and RFC 3986](https://www.cnblogs.com/saltiest/p/11213976.html) 根據部落格中給出的方法,加入TomcatConfiguration這一配置類: ```java /** * @author: zw * @create: 2019-06-27 11:19 **/ @Configuration public class TomcatConfig { @Bean public TomcatServletWebServerFactory webServerFactory() { TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory(); factory.addConnectorCustomizers((Connector connector) -> { connector.setProperty("relaxedPathChars", "\"<>[\\]^`{|}"); connector.setProperty("relaxedQueryChars", "\"<>[\\]^`{|}"); }); return factory; } } ``` 這裡說一下,為什麼沒有采用其他部落格所說的修改tomcat的配置檔案。因為這種偏底層的改變,在大公司裡面實現是非常麻煩的。所以優先考慮通過應用服務自身的設定完成。 但是,問題到這裡就真的結束了嘛? 當然沒有。如果只是如此,我只需要轉載上面那篇部落格就OK了。 ## 版本衝突 ### 問題發生 當時,通過上述方法,在本地的demo已經解決了問題。 但是,集團內部的xxx平臺無法成功將程式碼部署到日常環境。檢視錯誤日誌,並沒有看到有效的資訊。只是一些ClassNotFound等異常,但並沒有具體資訊。 ### 問題定位 簡單搜尋一下相關異常,看到一個關鍵字眼-版本衝突。 結合之前TomcatConfiguration引入的依賴,唯一可能存在問題的就是下面這條依賴: import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; 該依賴,來自SpringBoot 2.x。之前引入該依賴時,比較擔心的是系統採用的是集團內部的xxxboot,可能不相容。但是後來發現系統有引入SpringBoot,所以就沒有擔心了。 現在看來,這裡還是存在問題。 通過maven,發現:原專案採用的是SpringBoot1.x,而不是SpringBoot2.x,所以才會在啟動時,產生版本衝突問題。 ### 問題解決 確定問題位置後,接下來就是解決問題。 但是比較尷尬的是,SpringBoot1.x沒有TomcatConfiguration所需要的TomcatServletWebServerFactory。 所以,接下來就是尋找SpringBoot1.x版本的解決方案。 首先谷歌”SpringBoot1.x tomcat configuration“(這種偏原理的,優先考慮谷歌。尤其是有這個網路條件,並且英文看得懂),找到以下部落格: [How to Configure Spring Boot Tomcat](https://www.baeldung.com/spring-boot-configure-tomcat) 但是並沒有找到直接的解決方案(相信這也是大家經常遇到的情況)。 這個時候,看到tomcat存在一個application.properties配置項: server.tomcat.accesslog.enabled=true 因為TomcatConfiguration是@Configuration修飾的配置類,所以直接在實際專案程式碼(SpringBoot1.x)的application.properties增加該屬性,通過屬性跳轉,跳到ServerProperties(位於org.springframework.boot.autoconfigure.web下)。 直接搜尋tomcat,發現了TomcatEmbeddedServletContainerFactory,而這與SpringBoot2.x所使用的TomcatServletWebServerFactory很類似。 開啟程式碼,發現兩者都繼承自AbstractEmbeddedServletContainerFactory,並實現了ResourceLoaderAware介面。 所以針對之前的解決方案,修改成下面樣子就OK了。 ```java package tech.jarry.learning.birdlog.birdlog.config; import org.apache.catalina.connector.Connector; //import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * Tomcat配置類:SpringBoot1.x下,解決Tomcat對非法字元返回400問題 * @author: jarry **/ @Configuration public class TomcatConfiguration { @Bean public TomcatEmbeddedServletContainerFactory webServerFactory() { TomcatEmbeddedServletContainerFactory factory = new TomcatEmbeddedServletContainerFactory(); factory.addConnectorCustomizers((Connector connector) -> { connector.setProperty("relaxedPathChars", "\"<>[\\]^`{|}"); connector.setProperty("relaxedQueryChars", "\"<>[\\]^`{|}"); }); return factory; } } ``` ## 總結 至此,tomcat非法字元請求直接返回400的問題,就在SpringBoot1.x版本下解決了。 但是,如果只是到此為止,不再向前一步就太可惜了。就如同百米賽跑,跑到99米為止,放棄了。 問題的解決固然重要,但是更重要的是解決問題的思路。因為問題千千萬萬,每個問題的解決方法可能都不一樣。而解決問題的思路才是有限的,才有最為寶貴的。 所以,讓我們覆盤一下上述問題的解決思路: ### tomcat對非法字元返回400 1. 通過遠端debug的斷點,將問題範圍確定在應用服務之前,進而判斷大概率在tomcat(當然也可能是在應用服務前的nginx等。不過在當前場景優先排查tomcat)。 2. 通過400錯誤頁面的樣式,聯想到tomcat其他錯誤頁面,猜測問題發生在tomcat。 3. 通過資料查詢,網站搜尋,找到初步解決方案(這裡需要重視搜尋關鍵詞,關鍵詞的準確性決定了查詢效率) ### SpringBoot版本衝突問題 1. 通過日誌中的錯誤資訊,配合搜尋引擎,猜測錯誤是由於版本衝突造成。 2. 通過程式碼的增量,判斷問題發生在SpringBoot2.x的依賴上。 3. [可選] 這裡可以通過demo,確定問題是否版本衝突造成(這裡的demo必須精準,否則就多準備幾個)。 4. 通過maven檢視專案依賴樹,確定是由於SpringBoot2.x與SpringBoot1.x的版本衝突造成。 5. 查詢資料,沒有明確解決方案直接指向。 6. 通過提示的tomcat配置資訊,找到ServerProperties類 7. 在ServerProperties類中,尋找Tomcat相關的類。 8. 最終找到TomcatEmbeddedServletContainerFactory,兩者父類與介面吻合,並且可以直接替換原有的TomcatServletWebServerFactory 事後思考了一下,發現上述5-8可以更加簡單。那就是直接查詢SpringBoot1.x中類似TomcatServletWebServerFactory的類。 具體方法就是明確TomcatServletWebServerFactory的父類AbstractEmbeddedServletContainerFactory(實際功能)與介面ResourceLoaderAware(資源注入)。 在SpringBoot1.x中直接查詢AbstractEmbeddedServletContainerFactory子類即可(發現有三個,分別對應Tomcat,Netty與Undertow,都實現了ResourceLoaderAware介面)。不過需要確認是否可以直接使用,還是需要再進行轉化等。 ### 小結 再將上述步驟濃縮一下,就是大家常見的: * 查詢資料 * debug,打斷點 * 程式碼跳轉 * 聯想 * 單一變數進行篩選 * ... 相信上面這類總結,大家見得多了。但是真正落實後,效率確實差別很大的。 這篇文章,從實際問題 -> 問題解決步驟 -> 問題解決思路,幫助大家能夠真正明白如何在程式中發現問題,定位問題,解決問題,理解問題解決思路。如果可以的話,希望大家更進一步,學到如何進行這樣的總結。 願與諸君共