戰(zhàn):從自動(dòng)裝配到最小鏈路跑通)
【SpringBoot香樟轉(zhuǎn)轉(zhuǎn)】debugDay01從零搭一個(gè)校園二手交易平臺(tái)第一天全在跟報(bào)錯(cuò)較勁。在做“香樟轉(zhuǎn)轉(zhuǎn)”這個(gè) SpringBoot 項(xiàng)目之前我其實(shí)有過(guò)心理準(zhǔn)備但真到了寫代碼調(diào)試的階段才發(fā)現(xiàn)問(wèn)題的密度遠(yuǎn)超預(yù)期。這篇文章就把 Day01 這天的 debug 過(guò)程完整記錄下來(lái)包括版本搭配、IDEA 建項(xiàng)目的坑、自動(dòng)裝配原理、Redis 連接、MyBatis-Plus 日志配置這些內(nèi)容希望能給同樣在用 SpringBoot 做項(xiàng)目、尤其是卡在 debug 階段的朋友一點(diǎn)參考。整個(gè)項(xiàng)目是 SpringBoot Vue 前后端分離的校園閑置流轉(zhuǎn)平臺(tái)核心功能有用戶注冊(cè)登錄、閑置商品發(fā)布、商品瀏覽檢索和留言互動(dòng)第一天的目標(biāo)很明確把項(xiàng)目骨架搭起來(lái)跑通“用戶注冊(cè) - 登錄 - 發(fā)布一件閑置商品”這條最小鏈路。1. 項(xiàng)目定位與 Day01 目標(biāo)拆解1.1 香樟轉(zhuǎn)轉(zhuǎn)到底要做什么“香樟轉(zhuǎn)轉(zhuǎn)”這個(gè)名字源于校園里常見的香樟樹取“閑置流轉(zhuǎn)”的意思定位是面向高校學(xué)生的校內(nèi)二手交易平臺(tái)。學(xué)生手里有閑置的專業(yè)書、小風(fēng)扇、自行車、宿舍小鍋這類東西扔了可惜放宿舍占地方通過(guò)“香樟轉(zhuǎn)轉(zhuǎn)”可以快速發(fā)布、檢索、聯(lián)系同校的買家比去綜合二手平臺(tái)更有信任優(yōu)勢(shì)。技術(shù)棧選型上我沒(méi)有太多糾結(jié)SpringBoot 負(fù)責(zé)后端接口Vue3 Element Plus 負(fù)責(zé)前端頁(yè)面MySQL 存業(yè)務(wù)數(shù)據(jù)Redis 做登錄態(tài)緩存和熱點(diǎn)數(shù)據(jù)緩存MyBatis-Plus 做數(shù)據(jù)庫(kù)操作。選 SpringBoot 的理由很簡(jiǎn)單生態(tài)成熟、資料多、自動(dòng)裝配能省掉一堆繁瑣的 XML 配置市面上絕大多數(shù)中小型項(xiàng)目都在用它遇到問(wèn)題基本都能搜到解決方案不會(huì)卡死。第一天的開發(fā)規(guī)劃是這樣的用 IDEA 創(chuàng)建一個(gè) SpringBoot 工程確認(rèn)依賴能正常拉取配置 application.yml連上本地 MySQL設(shè)計(jì) user 表和 goods 表的基礎(chǔ)結(jié)構(gòu)寫一個(gè)注冊(cè)接口和一個(gè)登錄接口寫一個(gè)發(fā)布商品的接口用 Postman 完成一次完整鏈路測(cè)試整個(gè)規(guī)劃看起來(lái)不復(fù)雜但實(shí)際執(zhí)行時(shí)每一步都踩了坑下面按時(shí)間線拆開細(xì)說(shuō)。1.2 為什么第一天死磕 debug很多新手容易犯一個(gè)錯(cuò)誤上來(lái)就想著把整個(gè)系統(tǒng)一口氣寫完結(jié)果寫到最后全是報(bào)錯(cuò)也分不清是哪里出的問(wèn)題?!跋阏赁D(zhuǎn)轉(zhuǎn)”第一天我只做最小閉環(huán)就是為了把地基打穩(wěn)讓后面每一個(gè)業(yè)務(wù)模塊都建立在一個(gè)跑通的基礎(chǔ)上。debug 本身不是浪費(fèi)時(shí)間而是項(xiàng)目開發(fā)里占比很大的正常環(huán)節(jié)。我見過(guò)太多人遇到報(bào)錯(cuò)就慌要么直接百度復(fù)制粘貼一段代碼要么把報(bào)錯(cuò)發(fā)給 AI 讓它猜自己根本不看錯(cuò)誤信息。這種習(xí)慣一旦養(yǎng)成越到后面越難改。第一天的價(jià)值就在于用最基礎(chǔ)的功能把 debug 的基本節(jié)奏跑熟后面遇到復(fù)雜業(yè)務(wù)才能有條不紊地排查。2. 環(huán)境準(zhǔn)備與 IDEA 創(chuàng)建 SpringBoot 項(xiàng)目的關(guān)鍵選擇2.1 SpringBoot 版本和 JDK 版本怎么搭配開寫之前最繞不開的問(wèn)題就是版本?!皊pringboot版本太高”這個(gè)熱搜詞我太有感觸了網(wǎng)上很多教程是基于 SpringBoot 2.x 寫的你一打開卻是 3.x照著敲都會(huì)報(bào)錯(cuò)。還有“現(xiàn)在的版本是21想回退到1.8”這類問(wèn)題其實(shí)就是 JDK 版本與 SpringBoot 版本之間的兼容性沒(méi)理清。先說(shuō)結(jié)論如果是做類似“香樟轉(zhuǎn)轉(zhuǎn)”這樣需要快速上線、以業(yè)務(wù)為核心的項(xiàng)目首推穩(wěn)定組合SpringBoot 2.7.13 JDK 8。這套組合的社區(qū)資料最多遇到問(wèn)題基本能搜到現(xiàn)成答案各類 Starter 的兼容性也最好。如果確實(shí)想嘗鮮上 SpringBoot 3.x JDK 17 也可以但要注意一個(gè)關(guān)鍵差異SpringBoot 3.0 開始把 javax 包名換成了 jakartaSpring 官方文檔和老教程里的很多代碼直接復(fù)制會(huì)編譯報(bào)錯(cuò)。此外一些第三方 Starter 可能還沒(méi)適配 3.x容易卡在依賴兼容上。組合方案適用場(chǎng)景需要留意的點(diǎn)SpringBoot 2.7.x JDK 8存量項(xiàng)目、畢業(yè)設(shè)計(jì)、大多數(shù)業(yè)務(wù)系統(tǒng)javax 命名空間教程資料最齊全SpringBoot 3.x JDK 17新項(xiàng)目、追求新特性jakarta 命名空間部分第三方包需確認(rèn)是否適配SpringBoot 2.7.x JDK 17公司指定版本的情況需要手動(dòng)確認(rèn) starter 兼容性網(wǎng)上資料較少“香樟轉(zhuǎn)轉(zhuǎn)”我選的是 SpringBoot 2.7.13 JDK 8沒(méi)有為什么就是求穩(wěn)。2.2 創(chuàng)建項(xiàng)目?jī)煞N方式與網(wǎng)絡(luò)問(wèn)題的處理創(chuàng)建 SpringBoot 工程有兩種常見方式第一種是用 IDEA 自帶的 Spring Initializr直接在 New Project 里選 Spring Boot 版本和需要引入的依賴第二種是去 start.spring.io 官網(wǎng)生成壓縮包再導(dǎo)入到 IDEA 里。實(shí)操的時(shí)候你大概率會(huì)遇到一個(gè)問(wèn)題IDEA 連不上 Spring Initializr 的服務(wù)一直轉(zhuǎn)圈最后提示連接超時(shí)。這不是電腦壞了是默認(rèn)服務(wù)地址在國(guó)內(nèi)訪問(wèn)不穩(wěn)定。解決方法是換成阿里云的鏡像地址把創(chuàng)建項(xiàng)目時(shí)的服務(wù) URL 從 start.spring.io 改成 start.aliyun.com。阿里云鏡像創(chuàng)建出來(lái)的工程默認(rèn)用的也是 Maven建議順手把 Maven 倉(cāng)庫(kù)改成阿里云的 central 鏡像不然 Maven 拉依賴能把你心態(tài)拉崩。settings.xml 里 mirror 節(jié)點(diǎn)直接加這段mirror idaliyunmaven/id mirrorOf*/mirrorOf name阿里云公共倉(cāng)庫(kù)/name urlhttps://maven.aliyun.com/repository/public/url /mirror依賴下載速度會(huì)從“等半天loading”變成“秒下”這個(gè)不改是真的難受。2.3 單模塊起步別一上來(lái)就玩多模塊“香樟轉(zhuǎn)轉(zhuǎn)”的后端工程結(jié)構(gòu)Day01 我用的是單模塊沒(méi)有像很多企業(yè)項(xiàng)目那樣拆分成 common、system、business 多個(gè) Maven 模塊。原因是第一天還在驗(yàn)證鏈路單模塊結(jié)構(gòu)足夠清晰改代碼定位問(wèn)題都快很多。等后面用戶體系、商品體系、留言體系都發(fā)展壯大了再按業(yè)務(wù)邊界拆模塊也不遲。工程內(nèi)部的包結(jié)構(gòu)我按照用戶、商品、通用三層劃分沒(méi)有嚴(yán)格追求 DDD 分層但保證 Controller、Service、Mapper 各司其職。如果一上來(lái)就搞微服務(wù)、多模塊光依賴的傳遞關(guān)系和啟動(dòng)順序就能讓新手debug到懷疑人生這屬于把復(fù)雜度提前加載了沒(méi)有意義。3. 實(shí)戰(zhàn) debug從啟動(dòng)失敗到接口跑通3.1 啟動(dòng)直接報(bào)錯(cuò)Failed to configure a DataSource我當(dāng)時(shí)建完工程興沖沖寫了一個(gè)最簡(jiǎn)單的 Controller想先啟動(dòng)試試結(jié)果 SpringBoot 啟動(dòng)不到三秒就報(bào)錯(cuò)了。核心報(bào)錯(cuò)信息是Failed to configure a DataSource: url attribute is not specified and no embedded datasource could be configured.這個(gè)報(bào)錯(cuò)出現(xiàn)的原因很簡(jiǎn)單我在創(chuàng)建項(xiàng)目的時(shí)候選了 Spring Web、MyBatis-Plus、MySQL Driver、Redis 這些依賴其中 MyBatis-Plus 和 MySQL Driver 會(huì)讓 SpringBoot 在啟動(dòng)時(shí)嘗試自動(dòng)配置一個(gè)數(shù)據(jù)源。自動(dòng)裝配機(jī)制發(fā)現(xiàn) classpath 里有連接數(shù)據(jù)庫(kù)相關(guān)的類卻沒(méi)有讀取到任何數(shù)據(jù)庫(kù)連接配置就直接罷工了。解決方式有兩個(gè)方向方向一在 application.yml 里配置數(shù)據(jù)源信息讓自動(dòng)裝配能拿到它需要的東西。我這里用的是 MySQL 8.x配置如下spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/xiangzhang?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456方向二如果當(dāng)前階段不涉及數(shù)據(jù)庫(kù)操作可以手動(dòng)排除 DataSourceAutoConfiguration告訴 SpringBoot“你別自動(dòng)配數(shù)據(jù)源了”SpringBootApplication(exclude {DataSourceAutoConfiguration.class})我在“香樟轉(zhuǎn)轉(zhuǎn)”里用了方向一因?yàn)榈卿浐桶l(fā)布商品都要操作數(shù)據(jù)庫(kù)。這里還有一個(gè)特別容易踩的坑注意 url 后面的參數(shù)useSSL 建議設(shè)為 false 并指定 serverTimezone否則會(huì)報(bào)時(shí)區(qū)相關(guān)的錯(cuò)誤或者 SSL 連接警告。第一次配置數(shù)據(jù)庫(kù)的人很容易漏掉這些尾部參數(shù)然后又是新一輪 debug。3.2 寫 SQL 沒(méi)打印MyBatis-Plus 日志配置數(shù)據(jù)源配好之后項(xiàng)目終于可以啟動(dòng)了。我接著寫了一個(gè)根據(jù)用戶名查詢用戶的 Mapper 接口測(cè)試登錄接口時(shí)發(fā)現(xiàn)一個(gè)奇怪的現(xiàn)象接口能跑通數(shù)據(jù)也能查出來(lái)但控制臺(tái)里看不到任何 SQL 日志。這個(gè)問(wèn)題看起來(lái)小但影響很麻煩——你無(wú)法直觀確認(rèn) SQL 是否真的經(jīng)過(guò)了某個(gè)表、傳進(jìn)去的參數(shù)值是什么。在復(fù)雜排查場(chǎng)景里SQL 日志是不可或缺的輔助信息。MyBatis-Plus 的 SQL 日志需要手動(dòng)開啟在 application.yml 里加這么一段logging: level: com.xiangzhang.mapper: debugcom.xiangzhang.mapper 要替換成你自己項(xiàng)目里 Mapper 接口所在的包路徑。配置完成后每次執(zhí)行數(shù)據(jù)庫(kù)操作控制臺(tái)都會(huì)打印類似這樣的語(yǔ)句 Preparing: SELECT id, username, password, nickname, create_time FROM user WHERE username ? Parameters: xiaochen(String) Total: 1有了這個(gè)日志你能清楚看到 MyBatis-Plus 執(zhí)行的 SQL 和參數(shù)綁定排查“查不到數(shù)據(jù)”“參數(shù)沒(méi)傳進(jìn)去”這類問(wèn)題會(huì)輕松很多。3.3 登錄接口返回 500控制臺(tái)一行 NPE鏈路基本打通后我信心滿滿地測(cè)試登錄接口結(jié)果 Postman 直接給你一個(gè)紅色 500卡在發(fā)送請(qǐng)求的位置??刂婆_(tái)的報(bào)錯(cuò)信息如下java.lang.NullPointerException: null at com.xiangzhang.service.impl.UserServiceImpl.login(UserServiceImpl.java:38)這個(gè)報(bào)錯(cuò)信息其實(shí)已經(jīng)很友好了精準(zhǔn)定位到了 UserServiceImpl 的 login 方法第 38 行。我打開代碼一看問(wèn)題出在用戶不存在時(shí)MyBatis-Plus 的 selectOne 方法返回了 null我卻直接調(diào)用了 user.getPassword() 去比對(duì)密碼。這種問(wèn)題核心原因是代碼里缺少空值判斷。修復(fù)邏輯很簡(jiǎn)單先判斷查詢結(jié)果是否為 null如果是就拋業(yè)務(wù)異常讓前端知道“用戶不存在”而不是一個(gè)模糊的 500。User user userMapper.selectOne( new LambdaQueryWrapperUser() .eq(User::getUsername, username) ); if (user null) { throw new BizException(用戶名或密碼錯(cuò)誤); } if (!BCrypt.checkPassword(rawPassword, user.getPassword())) { throw new BizException(用戶名或密碼錯(cuò)誤); }這里還有個(gè)細(xì)節(jié)值得提一下登錄失敗時(shí)的提示信息最好統(tǒng)一為“用戶名或密碼錯(cuò)誤”不要直接返回“該用戶不存在”或“密碼錯(cuò)誤”否則別人可以借此探測(cè)你的系統(tǒng)里存在哪些用戶名。這也是安全實(shí)踐的一部分。3.4 Redis 連接報(bào)錯(cuò)Connection refused登錄功能還要配合 Redis 存登錄態(tài)我往項(xiàng)目里添加了 Spring Data Redis 依賴并按默認(rèn)配置啟動(dòng)后一調(diào)用相關(guān)服務(wù)就報(bào)Unable to connect to Redis; nested exception is io.lettuce.core.RedisConnectionException: Unable to connect to localhost/127.0.0.1:6379我看到這個(gè)報(bào)錯(cuò)的第一反應(yīng)是指向 Redis 服務(wù)本身于是在本地命令行執(zhí)行redis-serverRedis 服務(wù)啟動(dòng)后再試接口就通了。這個(gè)坑屬于環(huán)境依賴問(wèn)題不是代碼問(wèn)題但在開發(fā)階段很容易被忽略。如果你使用的是云 Redis 或其他遠(yuǎn)程實(shí)例還需要補(bǔ)充連接配置spring: redis: host: 127.0.0.1 port: 6379 password: database: 0 timeout: 5000ms lettuce: pool: max-active: 8 max-idle: 8 min-idle: 0lettuce 連接池參數(shù)在高并發(fā)場(chǎng)景下非常重要不加的話默認(rèn)連接數(shù)可能不夠接口壓力一大就拋連接異常。Day01 主要驗(yàn)證鏈路連通性連接池參數(shù)可以先不調(diào)但要知道是干嘛用的。3.5 用斷點(diǎn)調(diào)試替代到處打印上面幾個(gè)問(wèn)題都是靠日志和分析解決的實(shí)際開發(fā)中更常用的 debug 是斷點(diǎn)調(diào)試。在 IDEA 中點(diǎn)擊代碼左側(cè)行號(hào)區(qū)域可以打斷點(diǎn)用 Debug 模式啟動(dòng)項(xiàng)目程序執(zhí)行到斷點(diǎn)位置就會(huì)停下來(lái)然后你可以用 F8 步過(guò)、F7 步入、F9 跳到下一個(gè)斷點(diǎn)實(shí)時(shí)查看每個(gè)變量的值。我調(diào)試“用戶不存在導(dǎo)致 NPE”問(wèn)題時(shí)就是在 UserServiceImpl 的 login 方法打斷了點(diǎn)一步步看 selectOne 返回的值到底是 null 還是一個(gè) User 對(duì)象。斷點(diǎn)調(diào)試為什么重要因?yàn)楹芏鄦?wèn)題不是邏輯復(fù)雜而是程序跑得太快你根本跟不上數(shù)據(jù)的流轉(zhuǎn)。斷點(diǎn)相當(dāng)于給程序按下暫停鍵給開發(fā)者一個(gè)觀察內(nèi)存里真實(shí)數(shù)據(jù)的機(jī)會(huì)。要注意的是Debug 模式啟動(dòng)會(huì)比正常啟動(dòng)慢一點(diǎn)打斷點(diǎn)也會(huì)暫停整個(gè)請(qǐng)求線程線上環(huán)境不要用斷點(diǎn)調(diào)試否則線程全部掛住會(huì)造成嚴(yán)重后果。調(diào)試完記得把斷點(diǎn)清掉。4. SpringBoot 自動(dòng)裝配與配置優(yōu)先級(jí)理解透徹才能少踩坑4.1 自動(dòng)裝配到底做了什么“springboot自動(dòng)裝配原理”是被問(wèn)得最多的面試題也是日常 debug 繞不開的知識(shí)點(diǎn)?!跋阏赁D(zhuǎn)轉(zhuǎn)”第一次啟動(dòng)報(bào)數(shù)據(jù)源錯(cuò)誤本質(zhì)上就是自動(dòng)裝配機(jī)制在起作用。SpringBoot 自動(dòng)裝配的核心是 SpringBootApplication 這個(gè)組合注解它其實(shí)是由三個(gè)注解拼起來(lái)的SpringBootConfiguration標(biāo)記這是一個(gè) Spring Boot 配置類EnableAutoConfiguration開啟自動(dòng)裝配ComponentScan掃描當(dāng)前包及其子包下的組件其中最關(guān)鍵的是 EnableAutoConfiguration。這個(gè)注解會(huì)去讀取 META-INF/spring.factories 文件里面羅列了一大批自動(dòng)配置類比如 DataSourceAutoConfiguration、RedisAutoConfiguration、JacksonAutoConfiguration 等。SpringBoot 會(huì)根據(jù) classpath 中是否存在某個(gè)類來(lái)判斷是否啟用對(duì)應(yīng)的自動(dòng)配置。舉個(gè)例子當(dāng) classpath 里出現(xiàn) com.mysql.cj.jdbc.Driver 和 javax.sql.DataSource 時(shí)DataSourceAutoConfiguration 就會(huì)生效嘗試幫助你自動(dòng)創(chuàng)建數(shù)據(jù)源 Bean。我的配置里沒(méi)有指定任何 url自動(dòng)配置就會(huì)報(bào)錯(cuò)。這就是為什么 3.1 會(huì)報(bào) Failed to configure a DataSource。排除自動(dòng)配置類可以繞過(guò)但真正解決問(wèn)題的思路應(yīng)該是理解自動(dòng)配置需要哪些前置條件然后正確補(bǔ)齊這些條件。4.2 配置文件優(yōu)先級(jí)與常見誤區(qū)自動(dòng)裝配幫你做好默認(rèn)配置可如果你想要定制行為就需要通過(guò) application.yml 覆蓋。SpringBoot 的配置優(yōu)先級(jí)順序從高到低大概是命令行參數(shù)Java 系統(tǒng)屬性application-{profile}.ymlapplication.yml自動(dòng)配置類里的默認(rèn)值這個(gè)優(yōu)先級(jí)在實(shí)際開發(fā)里很有用比如本地用 application-dev.yml 連接本地?cái)?shù)據(jù)庫(kù)線上部署時(shí)通過(guò)命令行參數(shù)或者是 application-prod.yml 切換生產(chǎn)環(huán)境的配置不用改動(dòng)代碼就能實(shí)現(xiàn)環(huán)境切換?!跋阏赁D(zhuǎn)轉(zhuǎn)”第一天我就直接建了 application.yml 和 application-dev.yml開發(fā)時(shí)用 dev 配置文件線上部署時(shí)用 prod 配置切換方式是在啟動(dòng)命令里加--spring.profiles.activeprod或者直接在 application.yml 里寫spring: profiles: active: dev配置文件里最容易犯的錯(cuò)是縮進(jìn)問(wèn)題YAML 對(duì)縮進(jìn)非常敏感一個(gè)空格不對(duì)整個(gè)配置解析就會(huì)失敗。而且有些配置項(xiàng)錯(cuò)誤不會(huì)在啟動(dòng)時(shí)立刻暴露只有調(diào)用相關(guān)接口時(shí)才爆出來(lái)。寫 YAML 時(shí)推薦使用 IDEA 自帶的格式化功能每次都規(guī)范縮進(jìn)能少很多無(wú)謂的 debug 時(shí)間。4.3 常用注解速查與 ConfigurationProperties寫“香樟轉(zhuǎn)轉(zhuǎn)”的第一批接口時(shí)用到了一批高頻注解我把它們整理成一張速查表方便對(duì)照。注解作用使用位置RestController聲明一個(gè)控制器并直接返回 JSONController 類上RequestMapping映射 URL 到處理方法Controller 類或方法上GetMapping / PostMapping限定 HTTP 方法并映射 URLController 方法上RequestBody將前端 JSON 自動(dòng)綁定到 Java 對(duì)象Controller 方法參數(shù)上Service聲明業(yè)務(wù)層組件Service 實(shí)現(xiàn)類上Mapper / MapperScan注冊(cè) MyBatis 的 Mapper 接口Mapper 接口或啟動(dòng)類上ConfigurationProperties綁定配置項(xiàng)到 Java 類配置屬性類上Autowired / Resource依賴注入需要注入的成員變量或構(gòu)造器上ConfigurationProperties 是很多人用得少但很實(shí)用的注解。比如前后端分離項(xiàng)目通常有自定義的 JWT 密鑰、文件上傳路徑等配置散落在業(yè)務(wù)代碼里讀取會(huì)很亂不如建一個(gè)配置屬性類統(tǒng)一管理Component ConfigurationProperties(prefix app.jwt) public class JwtProperties { private String secret; private Long expireSeconds; public String getSecret() { return secret; } public void setSecret(String secret) { this.secret secret; } public Long getExpireSeconds() { return expireSeconds; } public void setExpireSeconds(Long expireSeconds) { this.expireSeconds expireSeconds; } }然后在 application.yml 里配置app: jwt: secret: xiangzhang-super-secret-key expire-seconds: 86400這樣代碼里就能用 Spring 注入這個(gè)屬性類后續(xù)想改配置只需要?jiǎng)?YAML 文件不用重新編譯代碼。5. 常見問(wèn)題與排查技巧實(shí)錄5.1 Day01 高頻問(wèn)題速查表“香樟轉(zhuǎn)轉(zhuǎn)”Day01 遇到的這些問(wèn)題其實(shí)都是 SpringBoot 入門階段的典型問(wèn)題我整理成了一張速查表按“現(xiàn)象 - 原因 - 處理建議”列出方便你以后快速對(duì)照?,F(xiàn)象大概率原因處理建議啟動(dòng)報(bào) Failed to configure a DataSource有數(shù)據(jù)源相關(guān)依賴但沒(méi)有配置連接配置 datasource 或排除自動(dòng)配置類數(shù)據(jù)庫(kù)連接時(shí)區(qū)錯(cuò)誤、SSL 警告JDBC URL 參數(shù)不全加上 serverTimezone、useSSLfalse控制臺(tái)看不到 SQL 日志MyBatis-Plus 日志級(jí)別未開配置 logging.level 指定 mapper 包為 debug登錄接口 NPEselectOne 返回 null 未判空增加空值判斷并拋出業(yè)務(wù)異常Redis 連接被拒絕Redis 服務(wù)未啟動(dòng)或配置不對(duì)啟動(dòng) redis-server檢查 host/port端口 8080 被占用其他進(jìn)程占用了同端口殺掉進(jìn)程或改 server.portMaven 依賴下載慢默認(rèn)中央倉(cāng)庫(kù)訪問(wèn)慢換阿里云鏡像YAML 配置生效但取不到值縮進(jìn)錯(cuò)誤或路徑名不匹配檢查縮進(jìn)和 key 層級(jí)是否和代碼一致5.2 Debug 效率提升的三條實(shí)用經(jīng)驗(yàn)Day01 調(diào)試下來(lái)我對(duì) debug 方法論有了更深的體會(huì)分享幾條對(duì)新手特別實(shí)用的經(jīng)驗(yàn)。第一條日志先行。遇到問(wèn)題先看控制臺(tái)完整報(bào)錯(cuò)從頂部開始一行行讀。很多人只看報(bào)錯(cuò)的最下面一行或者復(fù)制一段就去搜這樣效率很低。報(bào)錯(cuò)的棧信息里包含了完整的調(diào)用鏈能直接告訴你問(wèn)題出在哪個(gè)類的哪個(gè)方法這是定位問(wèn)題的第一手線索。第二條小步驗(yàn)證。每寫完一個(gè)接口立即啟動(dòng)項(xiàng)目用 Postman 或者 curl 測(cè)試一次。不要攢了一堆代碼才一次性啟動(dòng)一旦報(bào)錯(cuò)你不知道是哪塊代碼引入的問(wèn)題排錯(cuò)范圍變大。第一天我就是每完成一個(gè)接口就啟動(dòng)一次雖然啟動(dòng)次數(shù)很多但問(wèn)題每次都能在一個(gè)小范圍內(nèi)被鎖定。第三條用 Git 做備份點(diǎn)大膽改代碼。很多新手 debug 的時(shí)候總是不敢改代碼怕把原本能跑的部分改壞。建議建一個(gè) Git 倉(cāng)庫(kù)每次跑通一個(gè)功能就提交一次代碼后面無(wú)論怎么改都能回退到穩(wěn)定版本。敢于試錯(cuò)才是 debug 效率提升的關(guān)鍵。我記得還有一次因?yàn)?Maven 本地倉(cāng)庫(kù)緩存了舊版本的依賴代碼里明明新增了一個(gè)方法編譯卻一直報(bào)找不到。后來(lái)把本地倉(cāng)庫(kù)的對(duì)應(yīng)依賴刪掉重新拉取問(wèn)題才解決。依賴版本不一致導(dǎo)致的詭異問(wèn)題非常耗費(fèi)時(shí)間遇到解釋不通的現(xiàn)象可以考慮 clean 一下 ~/.m2 下的緩存文件。5.3 必須養(yǎng)成的好習(xí)慣寫“香樟轉(zhuǎn)轉(zhuǎn)”第一天項(xiàng)目里每一步我都要求自己保持代碼整潔、配置清晰。配置文件盡量分層寫數(shù)據(jù)庫(kù)配置、Redis 配置、業(yè)務(wù)配置分塊加注釋這樣項(xiàng)目交到別人手里別人也能快速看懂。給類和方法的命名也要有意義UserService 就是處理用戶邏輯的GoodsService 就是處理商品邏輯的不要出現(xiàn) Test1、Utils2 這類名字。另外SpringBoot 社區(qū)有一個(gè)很好的學(xué)習(xí)資源“狂神說(shuō)”系列我在第一天遇到自動(dòng)裝配概念不清楚時(shí)翻了一下他的筆記確實(shí)能幫你把那些零散的知識(shí)點(diǎn)串起來(lái)。這不是廣告是真心推薦的入門路徑。寫在 Day01 結(jié)束后Day01 從清晨搭環(huán)境到深夜跑通最小鏈路中間踩過(guò)的坑應(yīng)該能算一副撲克牌了。但最有價(jià)值的不是“解決了某個(gè)報(bào)錯(cuò)”而是掌握了一套 debug 節(jié)奏看報(bào)錯(cuò)、查日志、斷點(diǎn)跟蹤、小步驗(yàn)證。這套節(jié)奏在后面的用戶模塊、商品模塊才會(huì)真正發(fā)揮作用。如果你也在用 SpringBoot 做項(xiàng)目或者準(zhǔn)備開始做希望“香樟轉(zhuǎn)轉(zhuǎn)”的 Day01 記錄能讓你少浪費(fèi)幾小時(shí)。Debug 是開發(fā)過(guò)程中再正常不過(guò)的一環(huán)不要煩躁也不要繞開它把報(bào)錯(cuò)當(dāng)成產(chǎn)品給你留的提示逐條處理掉就好。明天開始我會(huì)繼續(xù)記錄商品模塊和文件上傳相關(guān)的調(diào)試過(guò)程如果對(duì)這塊感興趣可以持續(xù)關(guān)注這個(gè)項(xiàng)目系列。