尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

从零搭建SpringBoot项目:我踩过的五个坑

从零搭建SpringBoot项目:我踩过的五个坑 我记得那个周末亲手删掉自己写了两天的“屎山代码”时反而如释重负。从零搭建一个SpringBoot项目远不是新建一个Spring Initializr项目那么简单。表面上是几次鼠标点击其实是无数个夜不能寐的调试是反复阅读过时博客后撞得满头包的过程。今天把这五个坑摊开来讲不是为了证明我比你聪明而是让你知道这些坑每一个都是手把手把你从“只会用框架”推向“理解设计”的残酷导师。一、版本之坑黑暗森林里的隐形枪手在IDEA的初始化向导里我点了SpringBoot 3.2.1心里想着这应该是目前最稳的版本了吧。结果第一条报错就让我傻眼ClassNotFoundException: javax.servlet.Filter。要知道SpringBoot 3.x采用的是Jakarta EE 9规范所有原本以javax开头的类全部要替换成jakarta。我的第一个坑不是代码没写好而是连“引入依赖”这个动作本身都暗藏杀机。更加令人崩溃的是不同版本的SpringBoot对Spring Cloud的兼容性要求极其苛刻。假如你选择了SpringBoot 3.2.1而你的Spring Cloud版本还停留在2021.0.x那么恭喜你你会亲眼目睹一场全球最大的API失踪案。依赖版本的正确排列组合决定了你的项目第一天是顺滑入海还是搁浅沙滩。从那以后我给自己立下铁律先定SpringBoot版本再根据官方文档找到对应的Spring Cloud发行版本号然后锁定所有子依赖的版本清单。不要相信任何博客里所谓“latest”那个词是开发环境里最危险的咒语。与此同时Maven仓库的孤岛效应也让我吃尽苦头。某个内网环境死活拉不下来spring-boot-starter-parent的依赖。我最后手动上传了POM文件到私有Nexus仓库才勉强解决。实际上绝大多数启动失败不是代码问题而是仓库源没配好。你要花几个小时排查业务逻辑到头来却是一句Could not transfer artifact。建议你从第一天起就配置阿里云镜像并且彻底放弃对Default中央仓库的执念。二、时区之坑时间在流转代码永不变这是个让你抓狂的坑。如果你只做了本地开发永远不会发现一旦项目上线部署到云服务器数据库里存的时间比实际北京时间少了整整8个小时。我之前写了一个用户注册接口插入create_time字段用的是LocalDateTime.now()建表时直接DEFAULT CURRENT_TIMESTAMP。在本地一切都是那么完美。部署上线之后所有用户都在凌晨4点注册看着后台那张用户增长曲线我一度以为产品在欧美地区爆发了。问题根源在于JVM的默认时区从始至终都是UTC而并非你的业务时区。正确做法是在启动类的main方法中强制写入TimeZone.setDefault(TimeZone.getTimeZone(Asia/Shanghai))此时你以为万事大吉直到你发现MySQL连接串中还藏着玄机。在application.yml中的JDBC URL后面你必须显式追加serverTimezoneAsia/Shanghai否则MySQL驱动可能自行采用世界标准时间。但即便你处理好了这两个地方还有更隐蔽的陷阱Redis的TTL计算也是基于UTC的而定时任务框架Quartz也自带了一套基于服务器时区的调度策略。我最后把时区配置抽成了独立的配置类并用环境变量覆盖实现在不同环境间平滑切换。记住这句话如果你不主动定义时间时间就会以最荒诞的方式定义你的bug。三、配置文件之坑多环境切换的地狱与救赎从零开始我们通常会有application-dev.yml和application-prod.yml。但真正令你头疼的不是文件拆分而是配置项的覆盖逻辑。我第一次配置的时候把数据库密码硬编码在默认的application.yml里然后又建了一个application-prod.yml尝试覆盖它结果投产时发现还是连了开发库。原因是SpringBoot的配置加载机制是分层的主配置文件在置后加载会覆盖前置文件中的同名属性。你以为是按文件名决定优先级实际上它是按照固定的加载顺序来的。我不信邪想去配置中心解决问题又踩入了另一个更深的坑。你去引入Nacos或者Consul意味着你的项目从第一天开始就多了一个关键基础设施节点。这个节点下线了整个服务只能“盲跑”或者直接拒绝启动。你会陷入一种荒谬的困境明明写了漂亮优雅的配置管理方案却被网络连通性掐住了脖子。我最后的方案是把配置项按可变性分层有敏感信息的放在环境变量中有环境差异的放在profile文件中纯固定不变的放在共用配置里。每次改动配置必须走发布流程而不是直连服务器手改application.yml。你要把配置也当成代码来对待否则迟早有一天你会发现正运行的生产环境配置和Git仓库里的版本差了好几百行。四、热更新之坑DevTools的甜蜜与陷阱所有的教程都告诉你加入spring-boot-devtools后改一行代码就能自动重启一切都快得惊人。但他们没告诉你这个组件在IDE里会引起无休止的自动重启循环尤其是在后台编译配置不当的前提下你的项目会在“构建-重启-报错-构建”的死循环中耗尽你的耐心。而且最致命的是DevTools的重启并不是全量重启而是使用了自定义类加载器来加载变化后的类这导致某些静态变量、单例对象会失效或变成多份实例。有一次我在调试WebSocket的长连接每次一触发DevTools的重启所有的Session对象全部被清空。我花了一整天排查——以为是连接池配置问题后来在日志里发现LiveReload频繁触发这才意识到是热更新搞的鬼。DevTools是一个开发期玩具却伪装成了生产环境的救星。它的默认配置直接自动重启根本不考虑你的连接状态或者缓存对象是否该被持久化。如果你非要用热更新提高效率建议你只调用构建工具的build按钮然后通过spring-boot:run的进程间调试JVM的HotSwap功能来加载代码变更。哦还有就是开发时养成一段时间手动重启的习惯远比让热更新替你决定何时重新加载Class要安全得多。五、依赖注入之坑循环依赖的温柔陷阱你以为你写的代码天衣无缝结果在启动时收到一条The dependencies of some of the beans in the application context form a cycle的报错。英文提示还算友好但当你面对一个具有15个业务类的复杂项目时你根本没思路知道谁是那个“罪魁祸首”。我的第二个项目里服务层的A调BB调CC又重新循环调用A。起初一切正常直到某个业务代码里加了一个Transactional注解项目直接启动失败。这个坑的根源是SpringBoot 2.6版本开始默认禁止循环依赖。而在之前的版本虽然允许但它会让整个系统的依赖关系变得一团乱麻。你问我为什么代码里会有循环依赖因为我为了“急于完成功能”把逻辑写给A、B、C三层却没有抽象出独立的XxxService来隔离公共业务。有时候循环依赖不是安全漏洞而是一面照妖镜照出你代码设计上的惰性。你要做的是通过构造器注入来重构而不是图省事用Lazy注解粉饰太平。还曾在多线程环境里遇到过另一种匪夷所思的依赖注入失败你在Async异步线程里注入的Mapper竟然报空指针排查后发现是因为我直接在工具类的静态方法中使用了Autowired注入的字段而Spring是无法给静态字段注入实例的。依赖注入只对有容器管理的Bean生效你非要把Spring的东西塞进静态方法里那就只能得到一片NullPointerException。痛定思痛后我把所有的工具类都重构为Spring管理的Bean再也未犯此错。五点半日志之坑差点让我放弃排查额外提一个很多人都会中招的隐藏坑日志配置。你以为添加了spring-boot-starter-web日志就会乖乖输出到控制台。但一旦你尝试记录业务日志却怎么都看不到自定义路径下的日志文件。原因在于你没配置logback-spring.xmlSpringBoot默认只输出到控制台不会自动写入文件。我曾经为了排查一个支付回调整整三天没找到失败信息最后发现日志根本没落盘全被堆在了控制台缓冲里服务一重启就永久丢失。最优秀的调试工具永远不会是你脑子里的回忆而是结构化的、可检索的日志系统。给日志加上完整的traceId跟踪链路让整个请求跨越多个Service时都有统一标识。从今往后排查诡异问题的时间至少缩短一半。不要嫌日志配置繁琐将来的某个深夜你会感谢当时敲下那几百行配置的自己。总结但不抽象如果你问我搭建SpringBoot项目最难的是什么我的回答不是背诵各种注解也不是熟悉各种框架组件。而是你要有足够的耐心在接受这些崩溃细节的同时仍然能够抽身而出看到一切设计背后的因果链条。任何一个莫名其妙的问题背后都藏着一个还没被理解的执行机制。这些坑没有杀死你的项目反而把它的地基浇灌得更坚固。你踩过的每一个版本的坑、时区的坑、配置的坑最终都会转化为你对整个生态系统的直觉判断力。什么叫经验经验就是你知道那些让人摔跤的石头长什么样子以及它们藏在了哪片草丛里。当我五年后再看那个初学时的项目发现所有踩过的坑都变成了系统架构里的“深思熟虑”。这便是成长的代价也是成长的馈赠。你也会如此。从零起跑披荆斩棘最终搭建出一个可以坦然交付、甚至经得起生产环境考验的项目。记住那份焦头烂额当别人还在茫然地翻堆栈的时候你已经能微微一笑伸手指向那个早已埋伏已久的坑洞——然后优雅地绕开它。
返回列表