这几年做 Java 后端我接手过不少“年龄不小”的 Maven 项目pom.xml 里堆了几十个依赖WEB-INF 下面挂着 web.xml、spring-mvc.xml、applicationContext.xml改个接口要启动外部 Tomcat部署时先把 war 丢进 webapps 再手工重启。每次打开这种工程我都忍不住想能不能赶紧变成 Spring Boot把 Maven 项目改成 Spring Boot 项目表面上看只是改个 pom、加个入口类实际上牵扯到依赖管理、配置迁移、打包部署方式的一整套变化。这篇文章不聊“新建一个 Spring Boot 项目再重新写一遍业务代码”那种偷懒路子而是分享如何在现有 Maven 工程上做最小侵入的改造让老代码继续跑同时真正享受到 Spring Boot 的自动装配、内嵌容器、一键打包这些红利。适合两类人看手头有老 Maven 项目要改造的、以及刚接触 Spring Boot 想搞明白它和 Maven 项目到底是什么关系的同学。1. 先搞清楚Maven 项目和 Spring Boot 项目到底差在哪很多刚接触这套东西的人会把“Maven 项目”和“Spring Boot 项目”当成两个对立的东西其实这是个误会。Maven 是构建工具管的是依赖下载、编译、打包、生命周期Spring Boot 是开发框架管的是应用怎么启动、Bean 怎么装配、Web 容器怎么跑。Spring Boot 项目本来就是一个 Maven 项目它只是在 pom.xml 里引入了 Spring Boot 相关的依赖和插件换了个玩法而已。1.1 从构建工具到运行框架的关系拆解你可以把 Maven 理解成装修公司的施工队它负责按图纸pom.xml把材料依赖 jar买齐、把房子项目盖好、最后交付package。而 Spring Boot 是另一套“精装修标准”它规定了房子里的强弱电怎么走、开关面板用什么型号帮你把水电全做好你只需要往墙上挂家具就行。所以 Maven 项目变成 Spring Boot 项目不是推翻施工队而是让施工队换一套更省事的标准去干活。传统 Maven 项目里Spring 的启动靠的是 web.xml 里的 ContextLoaderListener 和 DispatcherServletSpring 容器要靠你手动写配置去扫描包、装配 Bean。Spring Boot 则把这个过程完全替换掉它用 spring-boot-starter-web 这个依赖把 Spring MVC 和内嵌 Tomcat 一起拉进来你只需要写一个带 SpringBootApplication 注解的入口类main 方法一跑内嵌容器就起来了不用再装外部 Tomcat也不用再配 web.xml。这个转变本质上是把“写一堆 XML 让容器理解你”变成“用约定和注解让框架帮你干活”。1.2 同样的 Maven 骨架不同的依赖矩阵从文件结构上看改造前后的项目骨架几乎没有变化还是 src/main/java、src/main/resources、src/test/java 那套标准 Maven 目录。真正变的是 pom.xml 里的依赖矩阵。传统项目通常长这样spring-core、spring-context、spring-web、spring-webmvc、mybatis、mysql-connector-java、druid 等一个个手动指定版本还要小心翼翼维护版本兼容关系。Spring Boot 项目则引入了“starter”机制。你要用 Web 功能就加一个 spring-boot-starter-web它会把 spring-webmvc、内嵌 Tomcat、jackson 这些一整套相关依赖全部管理好你要用持久层就加 mybatis-spring-boot-starter 或者 spring-boot-starter-jdbc。版本也不用你再操心了spring-boot-starter-parent 这个父 POM 已经锁定了一批经过测试的兼容版本。这套机制省掉的不只是几行依赖声明而是大把的版本冲突排查时间。1.3 为什么要折腾着改直接新建不行吗我知道有人会问既然 Spring Boot 这么好为什么不新建一个项目把代码拷过去说实话对于几十个类的小项目新建其实更快。但现实里很多 Maven 项目改造时已经跑了几年里面有复杂的系统间依赖、定制的 Spring XML 配置、特定的打包流程甚至还有老旧的 Servlet Filter。新建项目把这些全部重新翻一遍出问题的面太大。在原有 Maven 项目上改造最大的好处是可以用“灰度”的思路推进先让项目能按 Spring Boot 的方式启动、打包、运行业务代码尽量不动等跑稳了再逐步把 XML 配置迁移到注解和 Java Config。这种改造方式风险小、可回退也不影响正在进行的业务迭代。2. 动手前的准备环境检查与依赖梳理改造最忌讳的就是上来就改 pom.xml。改完一启动报一堆红然后开始盲目地删依赖、加依赖最后项目是跑起来了但你根本不知道哪步改动起了作用。我习惯的做法是动手前先花半小时把家底盘清楚后面能省一整天的排查时间。2.1 环境版本三件套JDK、Maven、IDE先把基础环境确认好。Spring Boot 2.x 要求 JDK 8 起步Spring Boot 3.x 则要求 JDK 17 及以上这是个硬门槛。你手头的 Maven 项目如果是老古董可能还跑在 JDK 7 甚至 6 上那就先别想 Spring Boot 了第一步是升级 JDK。Maven 本身也建议用 3.6.3 以上的版本太老的 Maven 在解析 Spring Boot 父 POM 时可能会出现一些奇怪的问题。IDE 这边IDEA 是主力但要注意 IDEA 里配置的 Maven 是不是你命令行用的同一个 Maven。很多坑都出在“命令行打包好好的IDEA 里启动就报错”原因就是两者用了不同的 Maven 或者不同的 settings.xml。我一般在改造前先把 Maven 的配置文件统一好比如阿里云镜像仓库就是一个很常用的加速配置放一组 mirror 指向阿里云的 maven 仓库地址下载 spring-boot 相关依赖会快很多。这个配置写在 settings.xml 的 mirrors 节点里改完记得在 IDEA 的 Maven Settings 里也指定同一份 settings.xml。2.2 盘点 pom.xml 里的依赖家底接下来是把现有依赖整理成一张清单。我习惯建一个简单的表格列三列坐标、用途、对应 Spring Boot 的替代依赖。比如 spring-webmvc 对应 spring-boot-starter-weborg.mybatis 的 mybatis-spring 对应 mybatis-spring-boot-starterdruid 通常对应 druid-spring-boot-starter。有些依赖找不到对应 starter比如一些公司内部的 SDK那就原样保留这也完全没问题——不要为了“统一风格”强行删掉能跑的依赖。清单列完之后你还能顺带发现不少“陈年旧账”pom 里明明引了 commons-lang3 但代码里根本没用、多个模块重复引入了同一个依赖、版本号写死的老依赖和 Spring Boot 锁定的版本冲突等。这些旧账不用一次性全清但你心里得有数知道哪些依赖在改造后可能引发冲突。2.3 摸清配置文件的位置与作用另一件重要的准备是走一遍 src/main/resources 和 WEB-INF 目录找出所有 Spring 相关的 XML 配置。常见的有这几类applicationContext.xml、spring-mvc.xml、mybatis-config.xml、dubbo 或 dubbo 之外的 RPC 框架配置文件。每找到一个配置想清楚它是干什么的是配了包扫描路径还是配了数据源还是配了视图解析器这些信息后面迁移到 Java Config 或 application.yml 时都用得上。web.xml 是重中之重。它决定了你有哪些 Servlet、Filter、监听器。改造成 Spring Boot 后web.xml 会被废弃所有的 Filter 要么改成 Component 加 WebFilter要么注册成 FilterRegistrationBean。如果项目里有自定义的 Filter 且配置了复杂的初始化参数这一步尤其容易漏漏掉之后表现通常很诡异某些请求没走你预期的过滤器链。3. pom.xml 改造引入 Spring Boot 依赖矩阵准备工作做完了正式动手的第一步就是改 pom.xml。这一步是整个改造的骨架它不动业务代码但决定了项目接下来能不能用 Spring Boot 的方式跑起来。3.1 引入 spring-boot-starter-parent 父 POM大多数 Spring Boot 项目的 pom.xml 里第一个大改动就是加一个 parentparent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version relativePath/ /parent这里我特意写的是 2.7.18而不是 3.x。原因很现实很多老项目里用的第三方库比如某些版本的 mybatis、shiro、activemq在 Spring Boot 3.x 下还没有合适的兼容版本。改造的第一目标是“能用”不是“用上最新版”。如果你手里的项目本来就是新项目业务代码不多可以直接上 3.x但要确认 JDK 17 已就位。版本号是钉死的我建议优先选 2.7.x 最后一个维护版本它能兼容大部分 2.x 生态里的第三方 starter坑最少。需要注意如果你现在这个 Maven 项目本身已经有一个 parent比如公司内部的 parent POM 统一管依赖版本那就不能直接加 spring-boot-starter-parent 了否则会报“Parent POM 冲突”。这种情况有两个解法一个是在自己 pom 的 properties 里声明一堆 spring-boot 相关的版本号不引入 parent更推荐的是把 spring-boot-dependencies 作为 import 类型的 dependencyManagement 引入这样既不影响原 parent又能让 Spring Boot 来管理版本dependencyManagement dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-dependencies/artifactId version2.7.18/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagement这个 import 方式优先级要明显高于依赖里自己声明的 version所以引入之后建议检查一下现有依赖有没有显式的版本号和 Spring Boot 锁定的版本冲突有冲突的要么删掉显式 version 交给 Spring Boot 管要么在 properties 里覆盖。3.2 用 starter 替换传统依赖父 POM 加好之后接下来就是依赖的大扫除。原来的 spring-webmvc 可以删了换成下面的 starterdependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency如果项目要写测试加 spring-boot-starter-test要用数据库mybatis 相关的老依赖换掉用 mybatis-spring-boot-starter注意 GroupId 是 org.mybatis.spring.boot。原来配置 druid 的如果用的是原生 druid 加手动创建 DataSource 的方式可以先保留后面再换 druid-spring-boot-starter。这里的关键原则是能换成 starter 的依赖尽量换因为 starter 不仅拉依赖还会触发对应的自动配置不能换的依赖就留着只要版本别和 Spring Boot 冲突就行。除了加依赖还有一件事要留意把原来 pom 里维护几十个 spring 系列依赖版本号的地方整个删掉。spring-core、spring-context、spring-webmvc 这些在引入 starter 之后都会由 Spring Boot 统一管理你再写一个 4.x 的旧版本号覆盖上去反而可能造成启动时 ClassNotFound 或 Bean 注入异常。这是改造现场最常犯的一个错误。3.3 加 spring-boot-maven-plugin 并理解 repackagepom.xml 的最后一步是加构建插件build plugins plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId /plugin /plugins /build这个插件干了一件非常关键的事repackage。普通的 Maven package 打出的 jar 里没有内嵌容器相关配置直接 java -jar 跑不起来只能靠外部 Tomcat 读取。spring-boot-maven-plugin 会在 package 阶段把原有的 jar 重新打包把内嵌容器和所有依赖塞进去做成一罐可执行的 fat jar。如果你没有加这个插件就算代码改得再好打包出来还是跑不起来这是改造后最常见的问题之一。3.4 顺手把 Maven 仓库镜像配置好改造过程中要下载大量 Spring Boot 相关的依赖网络慢的话会非常煎熬。我一般会在 settings.xml 里配置一组镜像把中央仓库的流量转到更快的地方。用阿里云 Maven 镜像是最常见的做法配置一个 mirror 节点mirrorOf 设为 central然后把仓库地址指到阿里云的 maven-public 仓库。这个配置对团队协作也很有帮助统一之后大家下载的依赖都走同样的通道不容易出现某些人下不下来包的情况。注意 mirrorOf 不要写成 *否则会把你自己私服的流量也劫持过去私服里特有的构件就找不到了。4. 代码与配置迁移从 web.xml 到自动装配pom 改完之后项目还是一个没有主心骨的 Maven 工程它缺少一个“启动入口”。Spring Boot 项目的启动入口就是一个带 main 方法的类所以这一步的关键是创建这个入口类然后把原来各种 XML 配置里干的事情一件一件搬过来。4.1 创建 SpringBootApplication 主类在某个基础包路径下新建一个主类比如叫 Application.javapackage com.example.project; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }SpringBootApplication 是三个注解的组合SpringBootConfiguration标记这是一个配置类、EnableAutoConfiguration开动自动装配机制、ComponentScan扫描当前包及其子包下的 Component 等组件。这个包路径的摆放位置非常讲究它必须在所有业务包的最外层否则它只会扫描自己所在的包和子包业务代码里的 Controller、Service 全都不会被加载。我在实际改造时踩过这个坑项目里原来的包的风格是 com.company.biz.controller、com.company.biz.service入口类如果随手放在 com.company 下面没问题如果放在 com.company.biz.controller 下面启动的时候日志全部显示正常但访问接口全部 404。所以放入口类之前先看一眼现有 Java 文件的最顶层包是什么入口类放到最顶层。4.2 把 XML 配置迁移到 Java Config 和 application.yml原来的 applicationContext.xml 里通常会写包扫描context:component-scan base-packagecom.company.biz/这个在 Spring Boot 里已经被 SpringBootApplication 的组件扫描覆盖了。如果你原来的扫描范围比主类所在包更大、或者指向了别的包就得在主类上额外加 ComponentScan 指定。原来的 spring-mvc.xml 里通常会配视图解析器、静态资源处理、消息转换器这些。视图解析器如果只是简单的 InternalResourceViewResolver 指向 /WEB-INF/jsp/可以在 yml 里配spring: mvc: view: prefix: /WEB-INF/jsp/ suffix: .jsp但注意JSP 在 Spring Boot 的可执行 jar 里支持度一直不太好官方虽然没完全抛弃但用起来繁琐。如果老项目是 JSP 那套我建议改造的第二阶段就考虑换成 Thymeleaf 或模板改静态页面如果业务页面少直接用 Ajax 加接口的方式更快。第一阶段的改造可以先让项目跑起来JSP 的问题放到后面专项处理。静态资源方面原来的mvc:resources mapping/static/** location/static//在 Spring Boot 里默认就配好了只要把静态文件放在 src/main/resources/static 目录下无需任何配置就能访问。数据源配置是迁移的重头。原来的 dataSource Bean、SqlSessionFactoryBean、MapperScannerConfigurer 这种配置在 Spring Boot 里大部分都可以用自动配置替代。你只需要在 yml 里写spring: datasource: url: jdbc:mysql://localhost:3306/yourdb?useUnicodetruecharacterEncodingutf8 username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver然后 MyBatis 这边配一个 mybatis.mapper-locations 指向你的 mapper XML 文件位置接口上的 Mapper 注解标注好剩下的 SqlSessionFactory 和 MapperScannerConfigurer 都由 mybatis-spring-boot-starter 的自动配置替你做掉了。这是整个改造中最能感受“原来写一堆 XML现在配置全自动”的部分。4.3 Configuration Bean 处理遗留 Bean不是所有 XML 里的 Bean 都能找到对应的自动配置比如你原来自定义了一个拦截器、一个消息转换器、或者一个定时任务线程池这些就得手工搬到 Java Config 里。做法很简单新建一个配置类package com.company.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; Configuration public class WebConfig { Bean public MyBizService myBizService() { return new MyBizService(); } }原来 XML 里怎么写 BeanJava Config 里就用 Configuration 加 Bean 如法炮制一遍。原来property namexxx refyyy/的属性注入在 Java Config 里要么在方法上声明参数让 Spring 注入要么用 Value 把 properties 里的值读进来。这一步其实很快一般半天能完成。最怕的是把 Bean 忘掉了启动时 NoSuchBeanDefinitionException 直接报出来顺着堆栈找就行。4.4 自定义 Filter、拦截器与 Servlet 的替代方案改造前 web.xml 里如果配了 Filter这一步得专门处理。最省事的方案是给 Filter 类加 WebFilter 注解然后在主类上加 ServletComponentScan 让它生效。更 Spring Boot 味的做法是用 FilterRegistrationBeanBean public FilterRegistrationBeanMyFilter myFilterFilterRegistrationBean() { FilterRegistrationBeanMyFilter registration new FilterRegistrationBean(); registration.setFilter(new MyFilter()); registration.addUrlPatterns(/*); registration.setOrder(1); return registration; }这两种方式都可以。但要注意顺序问题多个 Filter 的执行顺序在 web.xml 年代靠 filter-mapping 里的先后顺序现在靠 setOrder 控制数字越小越先执行。如果你原来 Filter 链对顺序有依赖改造后一定记得用 Order 显式声明不然会出现登录校验没执行、但权限校验先跑了的诡异情况。Spring Boot 默认自带一个 CharacterEncodingFilter 什么的Order 比较靠前你的自定义 Filter 如果也想在最早执行设置 Order 时通常给负数或比较小的值去抢占。4.5 定制启动端口与应用信息改造完成后第一次启动Spring Boot 默认会用 8080 端口。如果原来的项目挂在别的端口上需要在 application.yml 里显式写出来server: port: 8088 servlet: context-path: /your-appcontext-path 对应原来的 Tomcat 部署路径。很多老项目在 Tomcat 下访问地址是http://ip:8080/your-app/xxx启动 Spring Boot 后如果不配 context-path接口路径会少一段前缀前端全得改。所以我每次改造都习惯先把这一段写进去确保老的访问路径不变业务侧零改动。日志、编码等配置也趁着这次一并补上。这些小事看起来不起眼但决定了改造上线后运维和前端同事对你的评价到底是“靠谱”还是“坑爹”。5. 打包运行实战与典型问题排查代码和配置搬完最激动人心的时刻就是启动项目。我第一次改老项目的时候启动日志刷了十几行就崩了当时差点以为自己改了个寂寞。后来经验多了才明白那些报错信息全是“老朋友”挨个打过照面之后再见到它们就知道怎么治了。5.1 开发态运行spring-boot:run 与 IDEA命令行下直接用 Maven 启动mvn spring-boot:runIDEA 里则直接运行主类的 main 方法。注意IDEA 运行之前要确认 Project Structure 里 Language Level 和 SDK 版本匹配你的 Spring Boot 版本要求。比如 Spring Boot 2.7 要求 JDK 8 以上你如果只有 JDK 8就用 2.7 没问题Spring Boot 3.x 强制 JDK 17版本不匹配时启动会直接报 UnsupportedClassVersionError这个看报错就能识别。如果你在 IDEA 里改了端口、改了 JVM 参数可以在 Run Configuration 里的 VM options 加-Dserver.port8089这种临时参数或者直接交给 yml 里的偏好配置。平时我更喜欢直接把参数写在 yml 里因为这样命令行和 IDE 行为一致不容易出现“本地能跑、服务器上跑不了”的差异。5.2 生产态运行打 jar 还是打 war改造完成的 Maven 项目我强烈建议打可执行 jarmvn clean install java -jar your-app.jar当年老项目打 war 要部署到外部 Tomcat现在一台机器上只要装了 JDK 就能直接跑 jar部署成本降了一个量级。但有的团队仍然要求打 war——可能是运维流程没变或者容器里用的还是老的 Tomcat。打 war 也简单spring-boot-starter-tomcat 的 scope 改成 provided主类继承 SpringBootServletInitializer 并重写 configure 方法package 出来的就是 war 包。这个 war 包既可以被外部 Tomcat 识别也能用 java -jar 直接运行兼容两种场景。5.3 常见问题速查表我把这些年改造过程中碰到的问题整理成了一张表按出现频率排的遇到问题先照着表排查一遍多数都能解决。问题现象根本原因解决思路启动报 ClassNotFoundException 或 NoClassDefFoundError依赖缺失或版本冲突导致部分类被 exclude用mvn dependency:tree查看依赖树定位缺失的 jar必要时用 mvn dependency:analyze 检查未声明依赖访问接口全 404日志无异常主类包路径不对组件扫描不到 Controller检查 SpringBootApplication 主类是否在最顶层包或者用 ComponentScan 显式指定扫描包启动后端口占用有别的进程占了 8080 或你配置的端口yml 里显式改 server.portWindows 用 netstat -ano 查 PIDLinux 用 lsof -i:端口或直接换端口静态资源 404静态文件还在老的 webapp 目录下把静态资源移动到 src/main/resources/static 下Spring Boot 默认映射JSP 页面渲染不了可执行 jar 对 JSP 支持有限短期用 war 包兼容长期替换成 Thymeleaf 或纯接口模式mybatis mapper XML 找不到 SQLmapper-locations 路径没配对yml 里配置mybatis.mapper-locations: classpath:mapper/*.xml同时确认 resources 打包时包含了 xml 文件Bean 注入报错NoSuchBeanDefinitionExceptionXML 里的 Bean 没搬进 Java Config对照原 XML逐一把 Bean 迁移到 Configuration 类用 Autowired 标注的字段重点检查类型与包名项目原有 parent 被顶掉parent 只能有一个改用 spring-boot-dependencies 的 BOM import 方式保留原 parent启动极其缓慢卡在初始化大量 Bean 扫描或数据库连接不可达先看数据库配置对不对再看是否有网络请求超时打包时排除没用的依赖能明显提速中文乱码新旧编码配置不一致yml 里配 server.servlet.encoding 和 spring.http.encoding或者 JVM 参数 -Dfile.encodingUTF-8上表里的“访问接口全 404”和“mapper XML 找不到”是我见过最多的问题这两个问题各有个共同的特点启动日志不报错一测接口就露馅。所以改造完建议大家做的第一件事不是我刚才说的直接让前端来看而是自己拿 Postman 或 curl 把几个核心接口全过一遍尤其是带登录、带参数、带文件上传的类型这个验证的覆盖率比启动日志高得多。5.4 日志乱、版本玄学与依赖冲突的排查技巧日志配置在 Spring Boot 里也非常简单默认走 logback你只要在 resources 下放一个 logback-spring.xml 就能完全接管。老项目如果用的是 log4j 或 log4j2改造的时候把日志相关的旧依赖删干净尤其是 log4j-over-slf4j 和 slf4j-log4j12 这种桥接包由于依赖传递可能来路不明直接导致日志完全没输出或输出到不同位置。排查日志问题时mvn dependency:tree -Dincludesorg.slf4j:slf4j-log4j12这种命令很管用一眼就能看到哪个依赖把你不需要的日志实现带上来了。依赖冲突的排查套路我总结了一句口诀先看依赖树再查版本最后看排除。Spring Boot 团队对版本锁得很死但你自己的显式版本号优先级更高一旦写了旧版本自动装配的类可能就没有了。试过好几次前端同学反馈某个接口数据不对查半天是 jackson 被我无意中引了一个旧版本序列化规则全变了。所以所有依赖的版本号能交给 Spring Boot 管的就千万别自己写死。5.5 改造成功之后别忘了做一次全量回归项目能启动、接口能通不等于改造彻底结束了。我每次都会专门留出一天时间做回归重点盯那些老项目里“特殊”的部分文件上传接口、导出 Excel、定时任务、WebSocket、权限过滤器链、跨域配置。这些功能在传统 web.xml 里通常有一堆手工配置Spring Boot 虽然提供了对应的自动配置但两者的默认行为有差异。比如跨域老项目里可能配的是 CorsFilterSpring Boot 2.7 之前和之后的跨域配置方式又完全不一样一不小心就给前端同事制造一堆跨域问题。回归测试时拿一个完整的接口清单逐一过比自己觉得“应该没问题”要稳得多。6. 改造后的持续演进与踩坑复盘项目跑起来了但这件事远没结束。从一个“能跑的 Spring Boot 项目”到一个“合格的 Spring Boot 项目”中间还差几步。6.1 分阶段迁移别追求一步到位我的建议是第一阶段先保启动、保打包、保老路径第二阶段再迁移核心配置比如数据源、缓存、消息队列第三阶段才做架构层面的事情比如把 Controller 里的老代码抽取成 Service、引入统一异常处理、加 actuator 监控。比例上大概遵循“一半时间改构造一半时间做验证”宁可慢一点也不要改完上线就炸。我印象最深的一次改造是一个带 ActiveMQ 的项目。原来的 Maven 项目里 ActiveMQ 是手动 new 的工厂、手动启动一个 Listener全在 XML 里。我第一阶段只让它能跑起来第二步再用 spring-boot-starter-activemq 对接第三步把硬编码的队列名挪到 yml。每一步都有清晰的分界出问题能立刻知道是哪一步引入的。反过来如果你晚上加班把所有东西一次改完第二天凌晨出问题了你根本不知道从哪儿查起。6.2 自动装配原理值得花时间吃透用 Spring Boot 的人越来越多但很多人是“会用不原理”。改造过程中遇到的那些问题十有八九和自动装配机制有关。Spring Boot 启动时会把你引入的 starter 里的 META-INF/spring.factories 文件读一遍把所有标注了自动配置的类加载进来再根据条件注解比如 ConditionalOnClass、ConditionalOnProperty判断哪些配置生效。你加了 spring-boot-starter-web只要 classpath 里有 Spring MVC 相关的类DispatcherServlet 和内嵌容器就自动接上加了 mybatis-spring-boot-starter只要 classpath 里有 SqlSessionFactory它就自动创建数据源相关 Bean。这套机制给改造带来了极大的便利但也让很多开发者变成了“抄配置工程师”不知道为什么要写配置只知道不写就报错。既然改造过程逼着你拆开了旧项目的壳我真心建议你顺带把自动装配的原理读一遍结合 debug 日志里的条件评估结果能把很多模糊的认知一次性打通。6.3 改造过程中的几个“反直觉”经验最后分享几个复盘时记下的反直觉经验都是拿生产问题的教训换的。第一个不要迷信“删除 XML 就是 Spring Boot 化”。有些 XML Bean 迁移到 Java Config 后代码反而更丑了。尤其是那种配置了七八个参数的第三方 Bean配置类里洋洋洒洒写了一大堆 Value可读性并不比 XML 好太多。这时候实事求是地讲如果项目后续要走 Kubernetes 那套配置就应该集中在配置中心或者环境变量而不是纠结于到底是 XML 还是 Java 代码写。第二个不要随意升级 Spring Boot 版本。很多团队改造完一个版本就“顺手”接上了最新版结果第三方 starter 没跟上全是兼容性问题。我给团队的建议是改造时用一个保守但还在维护的版本然后半年到一年做一次评估确认依赖里没有缺兼容后再升级。稳定压倒一切长期的版本漂移比暂时不升版危害更大。第三个改造后的部署流程一定要写文档。Maven 项目变 Spring Boot 项目部署方式从“拷 war 到 webapps”变成了“java -jar 配置环境变量”。这一变化对运维和刚接手的同事冲击不小。我吃过一次亏改造完了运维还按老路子把 jar 当 war 丢进 Tomcat 里应用起不来折腾了半小时发现部署方式没同步。后来我养成了习惯每次大改动都在项目的 README 里单独写一段“部署变更说明”版本号、端口、启动参数、静态资源路径全都写清楚。这种文档虽然不值钱但关键时刻能救命。第四个小经验善用 Spring Boot 的 actuator。改造后加一个 spring-boot-starter-actuator 依赖再配好暴露端点的权限你就能通过 /actuator/health、/actuator/metrics 这些入口实时看到应用的存活状态、JVM 内存、线程池情况。老项目时代你只能看 Tomcat 日志和 jstackSpring Boot 时代这些信息伸手就能拿到。这个依赖不是必须的但对运行期排障的帮助非常大。每次做完一次 Maven 到 Spring Boot 的改造我对“项目是慢慢长出来的”这句话体会就更深一层。老项目不是废墟它承载着很多业务逻辑和历史决策改造的价值在于把底层基础设施换成更现代的方式的同时让业务代码不经受无谓的撕裂。从一个需要手工装 Tomcat、改 XML 才能启动的 Maven 工程变成一个 mvn spring-boot:run 就起来、java -jar 就能部署的 Spring Boot 应用这套操作我前前后后做了不下十次每次都会碰到几个新问题但核心思路始终没变先摸清楚家底再改骨架然后一点点把旧的配置换掉最后让验证回归跑在新地基上。如果你正要对自己的老项目动手希望上面这些经验能帮你少踩几个坑。