
你用Spring Boot寫接口第一件事不該是打開IDE敲代碼而是先想清楚一個問題你寫的是接口不是Java類。很多新手甚至干了三五年的開發把Controller當成業務邏輯的收容所把Service當成萬能的數據庫操作層最后接口越寫越臃腫排錯越來越痛苦。根源在于他們對依賴和注解的理解停留在“能用就行”的層面從未深究這些工具在你和機器之間到底立下了什么規矩。先說依賴。Spring Boot最迷人的地方不是“自動配置”而是它幫你把“選擇”的權力提前剝奪了。你引入spring-boot-starter-web它自動帶進Tomcat、Jackson、Spring MVC你引入spring-boot-starter-validation它連Hibernate Validator都給你備好。這套組合拳看似方便但如果你不了解每個starter背后掛了哪些傳遞依賴遲早會在某個深夜被NoClassDefFoundError逼瘋。記住一個原則依賴不是越多越好而是越精確越好。你寫一個純粹返回JSON的接口就沒必要拖一個spring-boot-starter-thymeleaf進來你只做簡單的參數校驗也別沖動地引入整套spring-boot-starter-security。每多一個依賴就意味著多一層被黑、被拖慢、被埋雷的可能性。先拆解starter-web這根骨頭spring-boot-starter-web是所有HTTP接口的基石。它替你解決了三個核心問題接收請求、轉換參數、返回響應。但注意它默認攜帶的嵌入式容器是Tomcat這個選擇不是神圣不可侵犯的。如果你追求極致的啟動速度或內存占用完全可以用spring-boot-starter-webflux走響應式路線或者把Tomcat換成Undertow、Jetty。換容器不是炫技而是對資源利用率的尊重。一個簡單的做法在pom.xml里排除掉spring-boot-starter-tomcat再手動添加spring-boot-starter-undertow。別小看這個動作在高并發場景下Undertow對內存的吝嗇會讓Tomcat相形見絀。然后是Jackson。這個JSON神器默認參與所有序列化和反序列化但它不是萬能的。你寫一個LocalDateTime字段直接返回給前端Jackson會拋異常——除非你配置了JavaTimeModule。Spring Boot已經自動幫你注冊了這個模塊但默認的日期格式是ISO格式前端不一定認。你必須在application.yml里顯式指定spring.jackson.date-format或者用JsonFormat注解做局部覆蓋。這不是麻煩而是你對契約的敬畏。接口不是給你自己看的是給那些可能用Python、Go、甚至Excel調用你的人看的。字段叫userName還是user_name日期是yyyy-MM-dd HH:mm:ss還是時間戳這都得在類的設計階段就定死。注解的核心是定位而非裝飾很多人把注解當魔法其實注解就是標記和元數據。RestController告訴Spring這個類直接處理HTTP請求并且返回的對象會被序列化為JSON。它的兄弟Controller則返回視圖名配合ResponseBody才等價于RestController。用RestController是態度用Controller是情懷混著用是給自己挖坑。一個Controller里不要既有返回JSON的接口又有返回視圖的接口職責混亂會讓測試和維護都變成噩夢。RequestMapping是家族GetMapping、PostMapping、PutMapping、DeleteMapping是它的具體化表達。請務必使用后四個不要用寬泛的RequestMapping。為什么因為GetMapping不僅限定URL還強制限定HTTP方法你的接口意圖一目了然。如果你用RequestMapping默認支持所有方法別人GET也能進、POST也能進這不是開放是失控。接口的語義化是優雅的第一步。真正的深度陷阱在參數綁定上。RequestParam用于獲取查詢參數PathVariable用于路徑變量RequestBody用于JSON報文這三個必須分清。有人圖省事把所有參數都塞進一個Map接收這等于放棄了類型安全。寫接口不寫DTO就跟不系安全帶開車一樣遲早出事故。一個規規矩矩的RequestBody應當對應一個經過驗證的DTO類類里每個字段都有明確的類型、命名和驗證注解。別用Map偷懶你偷的每一次懶都是未來線上故障的伏筆。校驗注解是接口的安檢門既然引入spring-boot-starter-validation就別讓它吃灰。Valid或Validated加在參數前面然后配合NotNull、NotBlank、Size、Pattern等注解你的接口就擁有了第一道防線。校驗注解不是可有可無的裝飾而是你對調用方最冷酷的溫柔。試想一個POST /api/user接口如果不去校驗email字段的格式數據庫里就會混入一堆垃圾數據后續清洗成本遠高于你寫一行Email的成本。這里有個容易被忽視的坑Validated在Controller層只能做基礎校驗如果你要在Service層做跨字段的復雜邏輯校驗就需要把Validated加到Service實現類上并在方法參數前用Valid。分層校驗不是重復勞動而是職責分離的體現。Controller只管報文協議的正確性Service管業務規則的正確性兩者互不越界。很多人喜歡在Controller里寫一大堆if判斷然后拋異常這種做法不是不行但它把業務代碼和接口代碼揉在一起讓Controller變成一個臟亂差的加工廠。更好的做法是定義自己的業務異常類配合RestControllerAdvice做全局異常處理。這樣你的接口代碼里不會出現任何try-catch錯誤邏輯被收斂到一個地方清爽得讓人感動。依賴注入的三種姿勢你選哪種Spring的依賴注入有字段注入、構造器注入、Setter注入三種方式。如果你還在用Autowired直接在字段上加注解請捫心自問你的類真的需要被外部替換實現嗎字段注入導致類與容器緊密耦合單元測試時無法輕易地替換依賴而構造器注入則天然支持不可變性依賴在對象創建時就固定下來這種確定性讓人安心。所以新寫代碼一律用構造器注入用RequiredArgsConstructorLombok生成構造器既能減少樣板代碼又能保證依賴不可變。這不僅是習慣更是對SOLID原則的尊重。接下來是Component、Service、Repository、Controller這四個注解的區別。它們本質都是Component的衍生但語義不同Controller管HTTP層Service管業務邏輯Repository管數據訪問。別用Component一股腦地標注所有類那是把螺絲刀當成錘子用。Repository還額外提供持久化異常轉換這對Spring Data JPA來說尤為重要——你沒寫RepositorySQL異常就不會被轉換成Spring的DataAccessException你辛辛苦苦catch到的異常可能是一條讓人摸不著頭腦的SQLIntegrityConstraintViolationException而不是一個友好的業務錯誤提示。Autowired的歧義和解決之道當你有一個接口UserService和兩個實現類UserServiceImpl和AdminUserServiceImpl時Autowired靠什么選定答案是Primary或Qualifier。Primary定義默認優先Qualifier定義精確指定。但更好的做法是在構造器參數上直接用Qualifier或者干脆不要用接口注入直接注入具體實現類。很多人覺得面向接口編程就必須用接口注入那是走火入魔。接口的意義在于抽象但如果你的項目里這個接口只有一個實現接口注入只是徒增復雜度。不要為未來可能發生的擴展而犧牲當下的簡單等真正有兩個實現時再引入接口注入也不遲。你還得認識ConfigurationProperties。這個注解用于綁定配置文件中的自定義屬性比如app.token.expire-time。相比Value一個個字段注入ConfigurationProperties可以把一組相關配置封裝成一個POJO同時支持類型安全校驗和復雜嵌套結構。如果你有超過三個配置項需要讀取就別再用Value零散地往類里塞了那是對你自己的不負責任。一個ConfigurationProperties類配上Component或EnableConfigurationProperties再啟用spring-boot-configuration-processorIDE里還能自動提示配置項這體驗甩Value幾條街。Bean的生命周期與條件裝配PostConstruct和PreDestroy是管理Bean初始化和銷毀的鉤子。有些初始化邏輯比如加載緩存、連接外部服務可以放在PostConstruct里。但注意這個時機是在依賴注入完成之后構造方法執行之后但尚未對外提供服務。如果你在構造方法里直接去連數據庫很可能依賴還沒注入完畢會得到空指針。所以初始化邏輯務必放在PostConstruct或者InitializingBean接口里銷毀資源用PreDestroy。條件裝配的幾個注解也值得深究ConditionalOnProperty、ConditionalOnBean、ConditionalOnMissingBean。它們讓Spring Boot的自動配置成為可能。你自己寫組件時用ConditionalOnMissingBean來讓用戶能覆蓋你的默認配置這是一種開放姿態用ConditionalOnProperty根據配置項的值來決定是否啟用某個Bean這能讓你的組件靈活適配不同環境。這些注解的價值在于它們讓“配置即代碼”這一理念落地。當你看到ConditionalOnMissingBean時你該明白Spring官方在給你機會——它不強行占坑它把主動權交到你手上。終極主題當你從零開始寫一個接口依賴和注解的選擇應當是一氣呵成的。先確定你需不需要響應式再去選spring-boot-starter-web還是webflux接著定義你的DTO和返回結構想清楚錯誤碼體系再動手寫Controller。Controller層的注解就那么幾個但組合方式千變萬化而每個組合都對應一種協議。RestControllerPostMappingValidRequestBody這是一套完整的寫JSON接口的標準姿勢RestControllerGetMappingRequestParamMin又是另一套查詢接口的經典做法。沒有哪種注解組合是絕對的真理但有亂用注解的后果是絕對清晰——你的代碼會變得難以閱讀更難以測試。依賴的數量不代表項目的強大注解的堆砌也不說明代碼的優雅。每一次引入依賴都是一次信任投票每一個注解都是一條契約聲明。你寫接口時其實是你和未來的維護者、調用者、甚至網絡另一端某個陌生技術棧的人之間的對話。這場對話的語法就是這些依賴和注解。你用得越精準這場對話就越順暢。最后送你一個自查清單寫接口前問問自己這個接口是否依賴了不必要的starter是否每個注解都在表達確定的語義是否用DTO代替了散裝參數校驗是否覆蓋了所有必填項異常處理是否統一如果答案都是肯定的那你已經領悟了Spring Boot接口開發的精髓——不是把所有功能都塞進來而是在最小必要依賴中用精確的注解刻畫出清晰的邊界。這才是專業與業余的分水嶺。