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