从SSM转型SpringBoot:常见的踩坑记录与解决思路

发布时间:2026/6/30 15:23:20

从SSM转型SpringBoot:常见的踩坑记录与解决思路 去年这个时候我接手了一个被三任维护者遗弃的SSMSpringSpringMVCMyBatis老项目。配置文件散落成一片XML的海洋每次新增一个接口都得手动注册拦截器、声明事务增强、配置视图解析器——调试环境搭建就要耗费半天。团队终于痛下决心启动向SpringBoot的迁移。你以为故事到这里就是“一键启动、天下太平”转型的痛苦远比你想象的更具体。下面这些坑是我和团队花了大几个月时间用发际线和凌晨三点钟的咖啡换来的真实记录。一、依赖管理的“隐形地雷”你可能不知道的版本战争SSM时代我们习惯手动引入spring-webmvc、spring-jdbc、mybatis-spring每个版本号都得靠“考古”确定。SpringBoot自带BOMBill of Materials理论上能自动对齐版本——但现实是当你强行塞入老项目遗留的第三方依赖SpringBoot的版本仲裁会直接暴毙。例如老项目用了druid-1.1.10而SpringBoot的starter中内置了HikariCP且不兼容某些旧版连接池的配置。启动时你看到的不是熟悉的重启按钮而是NoClassDefFoundError关于com.alibaba.druid.pool.DruidDataSource。解决方案很简单在pom.xml中显式声明dependency并优先使用spring-boot-starter-parent提供的版本号若必须覆盖请用properties节点锁定。但千万别在同一个项目中既spring-boot-starter-xxx又自己乱加一堆同类型库——自动配置会好心办坏事。排除一坨“自动配置”陷阱SpringBoot的自动配置机制AutoConfiguration是双刃剑。当你移入MyBatis时它自动帮你配置了SqlSessionFactory和DataSource。但如果你老项目里还有一个自己写的DataSourceConfig类且标注了Configuration那么你会在启动日志看到“Failed to configure a DataSource”。原因很简单你手动的Bean和自动配置的Bean冲突了。我的经验先关掉所有自动配置一手一脚地注入等测试通过后再逐步开启。在SpringBootApplication里加上exclude参数或使用spring.autoconfigure.exclude配置能救你一命。二、ApplicationContext的“失踪案”你以为的SpringMVC配置在Boot里全变了SSM里你习惯写springmvc-servlet.xml里面配置mvc:annotation-driven /、视图解析器、拦截器、静态资源映射。迁移到Boot后如果你还想在resources/下放个springmvc-config.xml然后通过ImportResource引入——这没问题。但要注意Boot的默认DispatcherServlet路径是/和你原来的.do后缀根本对不上。一个致命坑老项目里所有请求都带.do比如/user/list.do。Boot默认路径模式是/直接导致404。解决在application.yml里设置spring.mvc.servlet.path/或者干脆改掉后端代码去除.do。推荐后者因为.do这种历史遗迹早该入土了。静态资源访问404的经典噩梦SSM里我们用mvc:resources mapping/static/ location/static/ /。Boot默认将静态资源放在classpath:/static/、classpath:/public/等目录。如果你习惯把图片放到webapp/下Maven war包模式那么打包成jar后webapp目录根本不会被打入。这是最坑的一点SSM项目多打成warBoot推荐jar路径体系彻底变天。我的处理把静态资源统一放入src/main/resources/static/并且在application.yml里配置spring.web.resources.static-locations指向外部路径如果开发者需要本地调试。记住一句话不要在SpringBoot里再幻想webapp它只属于Servlet容器时代。三、数据源与事务的“薛定谔状态”多数据源分布式事务的坑越挖越深SSM时代我们用bean iddataSourceA ...和bean iddataSourceB ...然后用tx:advice指定每个DataSource。SpringBoot转型后如果你直接加PrimaryDataSourceBean配合EnableTransactionManagement看起来没问题。但当你需要在一个方法里同时操作两个数据源并希望使用JTAJava Transaction API实现分布式事务时——恭喜你踩进了自动配置和手动配置的夹缝里。SpringBoot默认只配置一个TransactionManager而你手动注入两个后Transactional会随机选择一个使用。解决方法必须显式声明Primary的TransactionManager并且对于非主数据源的方法在Transactional注解里加上transactionManager secondTransactionManager。更严重的是如果你使用了spring-boot-starter-data-jpa它还会自动再创建一个JPA事务管理器与MyBatis的冲突。最终我被迫完全关闭自动配置手写三个DataSource、三个SqlSessionFactory、三个TxManager代码量翻了一倍但避免了运行时才暴露的“事务回滚失败”。连接池参数不生效老项目用Druid配置了filters: stat,wall等。迁移到Boot后你在application.yml里写了spring.datasource.druid.filters: stat结果启动报错Unknown DataSource filter。原因是spring-boot-starter可能自动引入了HikariCP而你的Druid配置被Ignore了。解决办法在pom里明确dependency用druid-spring-boot-starter并且排除spring-boot-starter-jdbc里的HikariCP。并且注意如果用到Druid监控页面还要单独配置ServletRegistrationBean。不要相信“一行配置搞定一切”的宣传Boot的自动配置只覆盖80%的常见场景剩下的20%必须手工干预。四、拦截器与过滤器的“错位战”你注册的WebMvcConfigurer可能废了SSM里拦截器配置在xml里mvc:interceptorsbean class... //mvc:interceptors。在Boot里你实现WebMvcConfigurer接口重写addInterceptors方法——然后发现拦截器怎么不生效不是代码错了而是你的Configuration类没有被Spring扫描到。很多人把WebMvcConfigurer写在和SpringBootApplication同一包下但SSM老项目习惯把配置类放在com.xxx.config如果ComponentScan没有主动扫描这个包因为Boot默认只扫描启动类所在包及子包拦截器就变空气。另一个坑你注册了LoginInterceptor但发现静态资源也被拦截了。Boot默认静态资源路径不受拦截器管辖但如果你重写了addInterceptors并调用了excludePathPatterns(/static/)却依然被拦截检查一下excludePathPatterns的路径写法必须匹配Spring资源映射模式。使用/static/时SpringBoot会把当成通配符但如果你之前又自定义了addResourceHandlers就可能覆盖默认映射。建议统一资源路径用/static/拦截器排除用同样的模式。Filter和Interceptor的优先级混乱SSM里Filter在web.xml中声明Interceptor在Spring容器中顺序清晰。Boot里你可以用WebFilterServletComponentScan注册Filter也可以用FilterRegistrationBean。但如果你同时使用了二者且依赖了某些第三方Filter比如Shiro、Spring SecurityFilter的执行顺序就会变得随机。我曾经遇到一个Filter把请求body读了一次导致后续的RequestBody无法获取参数——这其实是Filter默认在普通Servlet和Spring DispatcherServlet之间执行如果你的Filter在Dispatcher之前读取了流且没有wrapRequest数据就没了。解决在Filter中使用HttpServletRequestWrapper重新包装或者把Filter优先级调高在doFilter里调用chain.doFilter(new CachedBodyRequestWrapper(request), response)。五、日志系统的“文化冲突”Log4j vs Logback谁才是正宫SSM老项目几乎清一色Log4j1.x或2.xSpringBoot却默认用Logback SLF4J。如果不做任何处理你会发现控制台一堆红色警告“SLF4J: Class path contains multiple SLF4J bindings”。这是最轻的症状严重的会导致日志完全丢失。更隐蔽的是你配置了一个log4j.properties文件里面写了log4j.rootLoggerINFO, console但Boot根本不读它。因为Boot会自动寻找logback-spring.xml或logback.xml。我的做法直接抛弃Log4j全面改用Logback。你可以保留Log4j的配置文件然后通过logback-spring.xml中的include resourcelog4j.properties/吗不行。必须重写一个Logback配置将原有的Appender、Logger等用Logback的语法翻译。这一步看似无趣但非常值得——Logback的性能和优雅配置甩Log4j几条街。日志文件路径找不到老项目把日志写在/var/log/yourApp/而Boot默认加上了一个spring.log的文件并且路径默认是logging.file.path./。当你部署在Linux上使用Systemd或Docker当前目录往往不是你想的那样。更坑的是如果你用logging.file.namelogs/app.log日志文件会出现在运行该Jar的工作目录下。如果目录权限不够启动时只会在控制台看到日志输出但文件始终不生成。最佳实践用绝对路径或通过${HOME}环境变量并在脚本中创建目录并赋权。你可以用logging.file.path/data/logs/app然后在项目中通过LogManager动态设置但记得在application.yml里加上logging.file.name: ${LOG_PATH:./logs}/spring.log并确保LOG_PATH在启动参数中传给JVM。六、单元测试与集成测试的“轮回”SpringBootTest 比你想的更慢SSM测试时我们通常用RunWith(SpringJUnit4ClassRunner.class)ContextConfiguration(locationsclasspath:spring-.xml)启动速度约5-10秒。切换到Boot后SpringBootTest默认会加载整个应用的上下文——如果你调用了SpringBootApplication所在类它会启动所有自动配置包括连接真正的数据库、初始化Redis连接池等。一旦数据库宕机测试全部失败。我亲眼见过一个同事的CI任务因为数据库未启动导致所有测试挂了被迫在测试类上加AutoConfigureTestDatabase(replace Replace.NONE)才能跳过。推荐使用WebMvcTest或MybatisTest这种切片测试只加载你需要的Bean对于数据库相关测试用DataJpaTest或JdbcTest替代并配合AutoConfigureTestDatabase使用内嵌H2。断言时忽略BaseEntity字段SSM项目中很多POJO继承自一个BaseEntity内含createTime、updateTime等这些字段在数据库里由触发器或MyBatis插件自动填充。测试时你插入一条记录然后查询断言assertEquals结果发现createTime为null而查询结果里有值——期待和实际永远对不上。解决方法在测试断言中只对比业务字段忽略时间戳等动态字段或者使用JPA Auditing让SpringBoot自己填充时间戳这样测试环境行为和线上一致。七、打包部署的“最后一公里”maven-war vs maven-jarSSM项目几乎都是war包扔到Tomcat的webapps下。SpringBoot推荐jar包通过java -jar运行。很多人迁移后仍然保留packagingwar/packaging导致启动时找不到嵌入式Servlet容器。正确做法改成jar然后添加spring-boot-maven-plugin。但如果你部署场景要求必须用外置Tomcat比如已有的运维管理平台也可以继续用war模式但此时必须将SpringBootServletInitializer子类作为入口并且排除spring-boot-starter-tomcat。最惨的坑是你改了war但忘记排除嵌入式Tomcat导致外置Tomcat和嵌入式Tomcat冲突应用无法部署。日志报错LifecycleException: Failed to start component [StandardEngine[Catalina]。你第一反应是Tomcat配置问题实际上只需加一行exclusion。多环境配置文件的“冒号”陷阱SSM时代我们通过-Dspring.profiles.activeprod指定配置文件。Boot里用application-{profile}.yml。但当你手动在application.yml里写了spring.profiles.active: profile.activeMaven过滤并且IDE里Run Configuration中VM options也加了-Dspring.profiles.activedev时——到底是哪个生效答案命令行参数优先级最高Maven过滤优先级很低。所以如果你在开发时想在IDE里指定dev却在pom里把profile.active写成了“dev”两者互斥时不会出错但一旦你部署时忘记传参就会默认使用空的profile。推荐只用一种方式部署脚本统一传-Dspring.profiles.active而把application.yml里的spring.profiles.active留空或注释掉避免混淆。最后的忠告别把SSM的配置思维带进SpringBoot回顾整个转型过程最大的阻力不是技术难点而是思维惯性。我们习惯了“显式声明、层层包装、XML遍地”而SpringBoot提倡“约定优于配置、自动化简化”。请记住转型不是复制粘贴而是重构认知。每当你遇到一个坑先问自己“SSM时代我为什么要这么写Boot的默认行为是什么”大多数情况下Boot的默认行为是正确的而我们只是不肯相信。加粗十个核心金句自动配置是福也是祸别让它和你的手工Bean打架。静态资源路径是第一个你会摔跤的台阶——丢掉webapp拥抱/static/。事务管理器的版本战争明确Primary否则Transactional就是抽奖。拦截器不生效检查你的Configuration类是否在启动类的子包中。日志文件路径要绝对化别让工作目录成为你的噩梦。切片测试比SpringBootTest快十倍它还能帮你隔离真实基础设施。打包选jar除非你的运维团队只认Tomcat选war就别忘排除嵌入式容器。多环境配置只用一个入口别让Maven过滤和命令行参数互相拆台。Druid连接池参数不生效先确认你引对了starter并排除了HikariCP。转型最大的障碍是你自己的习惯——先信任SpringBoot的默认行为再追求个性。转型不是一蹴而就的你可能会像我一样在凌晨三点看着堆栈里闪过的IllegalStateException想砸键盘。但这些坑每一个都让我对SpringBoot的理解更深一层。希望这份踩坑记录能让你少熬几天夜多喝几口茶。如果你也刚刚踏上这条转型路建议你先从最小的模块开始做一个微型的SpringBootMyBatis原型跑通完整的CRUD然后再逐模块替换。记住重构之路稳比快重要。

相关新闻