拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Spring Boot实战指南:从架构设计到微服务与避坑

1. 先把架构这件事掰开揉碎Spring Boot 到底解决什么问题1.1 为什么Spring Boot能成为一种事实标准做Java后端这些年我见过太多项目死在开局选择困难症上。Tomcat装哪个版本Spring Framework和第三方库版本怎么对齐配置文件写一堆XML打war包部署到外部容器每个环节都能让人折腾半天。Spring Boot把这些事情全收了它把一个Web项目从搭环境变成写业务这也是它能成为Java后端事实标准的核心原因。我对Spring Boot的理解就一句话它不是一个新框架而是一套约定优于配置的整合方案。你不用再手动装配Spring MVC、Jackson、Tomcat、MyBatis这些组件Spring Boot通过起步依赖和自动配置把常用组件的组合方式固定下来。你引入一个spring-boot-starter-web内嵌Tomcat有了Spring MVC有了JSON序列化也有了写个Controller就能跑起来。很多人容易忽略自动配置背后的条件装配机制。Spring Boot的EnableAutoConfiguration会扫描META-INF/spring.factories或者AutoConfiguration.imports文件里的配置类再用ConditionalOnClass、ConditionalOnProperty、ConditionalOnMissingBean这一系列条件注解去判断当前环境里有没有这个类用户有没有自己定义过同类Bean只有满足条件才生效。这套机制保证了它的灵活度你引入什么依赖它就装配什么能力你自己定义了Bean它就退位让贤。理解了这个项目里出现怎么配置不生效的问题时你才有排查的方向而不是瞎重启。结合热词里反复出现的spring boot 2.1spring boot 2.6这些版本我多说一句Spring Boot从2.x到3.x是一次大跨越2.4版本之后配置文件处理方式变了2.6之后默认禁止了循环引用SpringFox不兼容了Spring Boot 3更是直接要求JDK 17和Jakarta EE命名空间。如果你现在还在用2.1建议至少升到2.7团队有条件的话新项目直接上3.2以上Java 17的收益还是很明显的。不要觉得升版本是运维的事它直接影响你今天学的内容能不能落地上线。1.2 分层架构与微服务架构之间的关系热词里有分层式架构微服务架构分布式架构这三个词经常被混着说我得把它们的边界讲清楚。分层架构是单体应用内部的组织方式经典三层就是Controller、Service、MapperController收参数做校验Service处理业务逻辑Mapper跟数据库打交道。微服务架构则把不同业务域拆成独立进程彼此通过网络通信。分布式架构更宽泛它描述的是多节点协作的系统形态微服务是分布式的一种实现方式消息队列、分布式缓存、分库分表也都属于分布式范畴。Spring Boot在分层架构里的角色是地基它帮你把每一层串起来。Controller层用RestController处理HTTP协议Service层用Service注册业务Bean持久层用Repository整合MyBatis或JPA。这套分层最大的好处是职责边界清晰改数据库连接不影响接口签名换业务实现不影响Controller。但它也有个隐形问题很多人把分层做成包分层代码放进controller、service、mapper三个包就完了实际业务逻辑全堆在Service里Service变成几千行的上帝类。我建议配合分包模块一起做甚至用DDD的思路去约束边界。说到DDD热词里有ddd架构。我在实际项目里的体会是DDD的核心不是那套战术模式而是限界上下文先想清楚用户订单商品这些核心领域概念应该归哪个模块管模块之间怎么通信。你完全可以在Spring Boot项目里引入DDD的轻量实践一个业务域一个包包内自己分controller、service、repository域之间通过接口交互不要跨包直接new对象。这样做出来的单体项目后面拆微服务时痛苦会小很多。微服务架构和Spring Boot的关系要再说透一点。Spring Boot本身不提供微服务能力它只是微服务里每个服务的底座。服务注册发现、负载均衡、配置中心、分布式事务这些是Spring Cloud或者Dubbo那层解决的事。所以别指望学完Spring Boot就等于会微服务了Spring Boot是微服务这棵树的树根树根扎得稳上面长出来的Spring Cloud组件才能立得住。反过来说如果单体架构和Spring Boot的分层都搞不明白直接冲微服务大概率是给自己挖坑。1.3 多模块项目到底是过度设计还是必要设计热词里springboot多模块项目搭建出现了好多次。我一直认为多模块不是目的是手段。一个demo项目或者二百行的工具类项目用单模块就够强行拆模块只会增加构建复杂度。但当项目开始有多个业务域、有公共组件、需要给其他团队复用接口时单模块的弊病就出来了所有代码混在一起每次改动都可能影响到不相关的功能构建和测试的时间也越来越长。多模块的标准做法是Maven聚合工程父pom用 pom 声明只负责管理依赖版本和公共插件子模块各自独立。我常用的拆分方式是bootstrap或admin做启动入口common放公共工具类和统一返回体framework放配置和安全这类基础能力system和order这些按业务域拆。这里有个关键点子模块之间的依赖方向必须是单向的比如order依赖common但common绝不能反过来依赖order否则依赖环会让Maven直接报错架构上也是灾难。多模块带来的最大收益不是代码量减少而是依赖关系变得可治理。你可以在父pom统一声明Spring Boot的BOM所有子模块的版本都归它管再用dependencyManagement统一管住其他第三方库版本这样不会出现这个模块用Jackson 2.12那个模块用Jackson 2.15的混沌局面。我见过太多单模块项目依赖版本乱七八糟每次升级都要全局排查很折磨。但我也提醒一句多模块的粒度要控制。业务域模块不是越多越好模块拆到十几个以后编译时间、IDE刷新速度、团队协作成本都会上来。对大多数中后台项目四到五个模块已经是合理的上限。拆之前先问自己这个模块有没有被独立复用的需求有没有独立的生命周期如果答案都是否那别拆老老实实单模块加良好分包就够了。2. 从零搭建一个能打的项目目录、依赖、骨架2.1 初始化方式的选择脚手架还是手动搭搭建Spring Boot项目现在主要有三种方式。第一种是去Spring Initializr网站start.spring.io选择版本和依赖生成后直接导入IDE这是最省事的方式。第二种是在IDE里头操作IDEA的Spring Initializr集成和网页版等价还能顺便初始化Git仓库。第三种是纯手工写pom.xml适合需要特别定制或者网络受限的场景。我的建议很简单新项目一律用脚手架生成别自己从空pom开始敲。脚手架生成的工程自带标准的Maven目录结构、maven-surefire-plugin测试插件、spring-boot-maven-plugin打包插件这些玩意自己配一遍没有意义还容易配错。生成之后你要做的第一件事不是急着写代码而是把pom.xml打开逐个确认依赖。很多新手从一开始就依赖混乱后面全是在还技术债。选依赖有几个原则要记住。第一Spring Boot官方starter优先比如spring-boot-starter-web、spring-boot-starter-validation这些都是官方维护的版本由BOM统一管理。第二第三方库要选有自动配置支持的比如MyBatis就选mybatis-spring-boot-starter不要自己手动引入mybatis和spring-mybatis再手动配SqlSessionFactory纯属浪费生命。第三数据库驱动、连接池这类基础依赖版本跟着BOM走不要自己写死版本号。生成完项目我习惯先把目录结构整理成下面这样com.example.project ├── ProjectApplication.java ├── common │ ├── result │ ├── exception │ └── utils ├── config │ ├── WebMvcConfig.java │ ├── MybatisConfig.java │ └── SecurityConfig.java ├── module │ ├── system │ │ ├── controller │ │ ├── service │ │ ├── mapper │ │ └── entity │ └── order │ ├── controller │ ├── service │ ├── mapper │ └── entity └── ...按模块分包而不是按技术分层分包是我这几年比较推崇的做法。按module/domain拆每个业务域内自己再分controller、service、mapper这样后续如果要拆微服务直接把这个域迁移出去就行不用在一堆controller包、service包之间反复横跳。2.2 统一返回体与全局异常处理接口规范的第一步接口设计最怕混乱。有的接口返回{code:0}有的返回{success:true}有的出错时直接抛个堆栈给前端前端拿到一堆英文异常信息完全没法渲染。所以项目搭建的第一步先做统一返回体。我的写法是定义一个Result 类内部包含code、message、data这3个字段。code用int表示业务状态码200表示成功其他数字表示不同错误类型。message是给前端看的提示语data是真正的业务数据。再加一个静态方法ok(T data)和一个静态方法error(String message)Controller里直接return Result.ok(userService.findById(id))即可。代码长这样public class ResultT { private int code; private String message; private T data; public static T ResultT ok(T data) { ResultT r new Result(); r.setCode(200); r.setMessage(success); r.setData(data); return r; } public static T ResultT error(int code, String message) { ResultT r new Result(); r.setCode(code); r.setMessage(message); return r; } // getter / setter 省略 }但光有返回体还不够异常没处理一个NullPointerException打出去前端照样收到500堆栈。所以要配全局异常处理器。用RestControllerAdvice加ExceptionHandler按照异常类型分类处理方式参数校验异常返回400业务异常返回500兜底异常返回500并记录日志。这里有个我踩过的坑异常处理器的返回信息一定要设计成面向用户的不能直接把异常message抛给前端可能把表结构或SQL都泄露出去安全上很糟糕。正确做法是统一返回系统繁忙请稍后重试真正的异常详情记到日志里。2.3 配置管理多环境与外部化别把配置写死在代码里配置管理是项目搭建时最容易被忽略的环节。新手经常把数据库地址、用户名密码直接写在application.yml里然后代码里再写个常量类存一些杂七杂八的值等要部署到不同环境就傻眼了。Spring Boot天然支持多环境配置只需要把配置文件命名成application-{profile}.yml然后在application.yml里用spring.profiles.active指定当前生效的profile。我一般会建三套application-dev.yml、application-test.yml、application-prod.yml分别对应开发、测试、生产环境里面放着不同的数据库地址、日志级别、中间件地址。启动时用--spring.profiles.activedev这种参数覆盖默认值部署时Jenkins或容器平台传参即可。配置外部化是更进阶的一步。生产环境的配置不应该打进jar包里而应该通过环境变量或者配置中心下发。Spring Boot支持环境变量优先于配置文件你可以把生产数据库密码配成环境变量SPRING_DATASOURCE_PASSWORD代码和配置文件里都不出现明文。对于敏感配置比如密钥、支付证书密码、短信平台密码我现在的习惯是用jasypt-spring-boot这个库做加密配置里存密文启动时用环境变量里的解密密钥去还原。简单说application.yml里写ENC(xxx)jasypt会自动解密成明文后交给Spring使用。这样就算配置文件泄露出去对方也拿不到真正的密钥。另外一个容易被忽略的配置项是spring.application.name项目启动类所在工程的artifactId千万别忘了给它起一个有意义的名字。微服务场景下服务名就是注册中心的标识命名不规范后面找服务都费劲。2.4 日志和开发期热更新提升日常开发体验的两个小配置日志不是pom里加个依赖就完事。Spring Boot默认用Logback你只管在resources里放一份logback-spring.xml把控制台输出格式、文件滚动策略、日志保留天数配好。我通常按天滚动保留30天单个文件上限100MB超过就压缩归档。开发环境日志级别设INFO测试和线上设WARN或ERROR加关键业务INFO避免线上日志被大量无关信息刷爆。热更新方面spring-boot-devtools提供了开发期的自动重启能力改完代码IDE里触发自动编译应用自动重启省去手动CtrlF5的重复劳动。但它有个坑重启是基于ClassLoader的如果项目里用了比较重的静态缓存或者自定义ClassLoader可能会出现改了不生效或者莫名其妙的状态丢失的问题。所以生产环境绝对不能带devtools一个简单的做法是在pom里把它的scope设成runtime并配合spring.devtools.restart.enabledfalse来控制。不过说实话现在IDEA的Hot Swap能力和Spring Boot官方推荐的spring-boot-devtools体验已经不错实在不行还有JRebel这类商业工具看个人预算吧。3. 进阶技巧实际业务里真正高频的特性3.1 MyBatis字段级加密需求来了怎么设计查询热词里有一条spring boot mybatis实现数据库字段级加密了怎么做查询这个需求在实际业务里很常见比如手机号、身份证号、银行卡号这些敏感字段。我讲一个最简单也最常见的方案用MyBatis的TypeHandler做字段级透明加解密。思路是什么样的呢数据库某个字段的值存的是密文但你的Java实体类里对应的字段是明文字符串。在写入时TypeHandler的setNonNullParameter方法里做加密查出来时TypeHandler的getNullableResult方法里做解密。这样业务代码完全感觉不到加解密的逻辑Controller层拿到的是明文数据库里存的还是密文最小化改造范围。一个通用的AesTypeHandler可以这样写MappedTypes(String.class) MappedJdbcTypes(JdbcType.VARCHAR) public class AesTypeHandler extends BaseTypeHandlerString { private static final String KEY your-16-length-key; Override public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException { ps.setString(i, AesUtil.encrypt(parameter)); } Override public String getNullableResult(ResultSet rs, String columnName) throws SQLException { String value rs.getString(columnName); return value null ? null : AesUtil.decrypt(value); } Override public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException { String value rs.getString(columnIndex); return value null ? null : AesUtil.decrypt(value); } Override public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { String value cs.getString(columnIndex); return value null ? null : AesUtil.decrypt(value); } }使用的时候在实体类对应的字段上标注TableName(user) public class User { TableId(type IdType.AUTO) private Long id; TableField(typeHandler AesTypeHandler.class) private String phone; }MyBatis-Plus里用TableField(typeHandler ...)就行纯MyBatis则在Mapper XML的resultMap里配置typeHandler。这个方案能解决写入加密、查询解密的基本需求。但热词里问的是怎么查。难点就在这里如果phone字段加密存储你在SQL里WHERE phone #{phone}肯定是查不到的因为数据库里存的是密文。这个问题有几种解法。第一种全表扫描逐行解密再比对这么做性能极差只有在数据量几百条的字典表里才敢用。第二种为手机号单独加一列明文索引比如md5_hash列查询时先用明文算出md5值去匹配这一列取到主键后再按主键读密文。第三种用数据库层面的加密函数比如MySQL的AES_ENCRYPT/AES_DECRYPT查询时也走函数索引但这样等于把密钥暴露给数据库得在数据库安全上下更多功夫。我得给大家一个明确推荐大部分系统尤其是对老系统的改造建议用第二方案新增一个hash索引列用不可逆的摘要比如SHA-256对敏感字段生成固定长度的值查询时WHERE md5_phone #{md5Phone}这样根本不用解密直接走普通索引。我做过几个实际项目数据量在百万级别时这种方案查询耗时稳定在毫秒级。而那些声称全字段加密也支持模糊查询的中间件方案很多依赖数据库底层特性部署和应用成本并不低先把简单方案跑通更重要。3.2 SQL执行10秒自动关闭吗聊聊连接池和超时配置热词里有个问题很有意思jvm或者spring boot会设置一个sql执行10秒自动关闭吗。这个问题说明很多人对MySQL连接和Spring Boot的超时关系有误解。我明确告诉你Spring Boot默认不会给SQL执行设置10秒自动关闭。真正影响SQL执行超时的是数据库驱动、连接池和MySQL服务端自身的几个配置。Spring Boot 2.x默认的数据库连接池是HikariCP它有一个connectionTimeout配置默认是30000毫秒也就是从连接池获取连接的最长等待时间超过这个时间会抛SQLTransientConnectionException。它还有一个validationTimeout默认5000毫秒是连接检测的超时。但这些都不是SQL执行超时。真正限制SQL执行时间的是JDBC驱动层面的Statement Query Timeout和MySQL的等待超时。MySQL驱动器上可以设置socketTimeoutjava里还可以用Statement.setQueryTimeout()MyBatis里可以通过Options(timeout 10)或者在XML的里设置单位是秒。如果执行超过指定时间数据库会抛QueryTimeoutException但它并不会自动关闭整个数据库连接只是中断这条SQL操作连接本身还活着可以被继续复用。所以如果你需要解决有个慢SQL卡住了整个服务的问题正确的措施是第一在MyBatis的SQL配置里给关键查询设置合理的queryTimeout第二连接池的connectionTimeout要设成能接受的值比如30000毫秒第三MySQL服务端的wait_timeout和interactive_timeout只是空闲连接超时跟SQL执行时长无关别搞混第四用慢查询日志慢查询日志配置和监控工具找出那些持续执行的SQL从SQL索引和业务逻辑层面优化。我遇到过很多团队一有慢SQL就先怀疑Spring Boot会不会自动断开连接然后到处改连接池参数结果该慢的还是慢。因为超时和慢查询本来就是两回事。超时是保护机制慢查询是性能问题保护好慢查询靠优化SQL。先把概念理清再动手改配置方向不对的话参数调得再激进也是白搭。3.3 定时任务从Scheduled到分布式锁的升级路径Spring Boot里做定时任务最基础的方式是Scheduled注解。在启动类上加EnableScheduling然后在任意一个方法上写Scheduled(cron 0 0/5 * * * ?)这个方法就会被调度执行。这种方式在单体项目里非常方便代码量几乎为零。但Scheduled有个硬伤它只在本进程内生效而且不保证同一时刻只有一个实例在跑。假设你做了多实例部署微服务或负载均衡同一个定时任务会在每个实例上都执行一遍。对清理过期数据这类幂等任务来说重复执行只是浪费资源但对给用户发奖励月末结算这类非幂等任务重复执行会直接造成数据错乱。解决思路有两条。第一条是让任务获取分布式锁抢到锁的实例才执行其他实例直接跳过。可以用Redis实现一个简单的锁伪代码如下String lockKey job:settle:lock; Boolean locked stringRedisTemplate.opsForValue() .setIfAbsent(lockKey, 1, Duration.ofMinutes(30)); if (Boolean.TRUE.equals(locked)) { try { // 执行业务逻辑 } finally { stringRedisTemplate.delete(lockKey); } }注意setIfAbsent一定要设置过期时间防止任务执行过程中实例宕机锁永远不释放。更稳妥的方案是用ShedLock或者Quartz集成数据库/Redis分布式锁让框架来管理锁的获取、续期和释放避免自己手写锁时踩到各种边界问题。第二条思路是把定时任务从应用里剥离出来交给独立的调度平台比如XXL-JOB、PowerJob。这类平台本身就是为分布式调度设计的能调应用里的HTTP接口能存储任务调度记录支持手动执行、失败重试、任务依赖。我个人的建议是任务量少且实例数不超过2个用ShedLock或手写Redis锁够了任务多了尽早引入调度平台别自己在应用层硬撑。3.4 WebSocket集成实时通知与Spring Boot版本的那些事热词里spring boot 2.1集成websocket是个高频搜索说明不少人都在做实时推送。Spring Boot对WebSocket的支持其实很好起步依赖里引入spring-boot-starter-websocket然后写一个类继承TextWebSocketHandler重写afterConnectionEstablished和handleTextMessage方法就行。我用一个简化示例Configuration EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(myHandler(), /ws/message) .addInterceptors(new MyHandshakeInterceptor()) .setAllowedOrigins(*); } Bean public WebSocketHandler myHandler() { return new MyWebSocketHandler(); } }加拦截器可以做握手阶段的认证比如从URL参数里带token然后校验。如果不做认证任何人都能连到你的WebSocket端点线上是要出事的。另外一个坑是setAllowedOrigins在Spring Boot 2.6之后发生了变化原先写*允许所有来源的方式可能不生效需要明确指定允许的来源或者用AllowedOriginPattern这个细节很多人会在升级版本后踩到。WebSocket连接的会话管理需要自己维护。最常见的方式是用一个ConcurrentHashMap把userId和WebSocketSession关联起来在afterConnectionEstablished时put进去在afterConnectionClosed时remove掉。推送时从map里取出sessionsession.isOpen()判断一下再sendMessage。这里要注意线程安全普通HashMap在高并发下扩容会死循环必须用ConcurrentHashMap。Spring Boot版本和WebSocket的兼容性我实际测过2.1和2.7在WebSocket这块几乎没有差别代码可以平滑升级Spring Boot 3.0之后因为Java EE迁移到Jakarta EEwebsocket的依赖坐标变了如果是从2.x升3.x需要把javax.websocket相关包全部改掉这个工作量不大但容易遗漏。4. 从单体到微服务Spring Boot之后的进阶方向4.1 微服务需要补齐哪些基础设施Spring Boot把单个服务做得很舒服但它解决不了分布式场景下的服务发现、配置管理、负载均衡、链路追踪、熔断降级这些问题。你从单体往微服务走至少要补齐几块基础设施。服务注册与发现是第一步。服务启动时把自己的地址注册到注册中心调用方从注册中心获取服务列表再发起调用这样每个服务实例可以在多个机器上横向扩展。常见方案有Nacos、Eureka已停更、Consul。国内企业用Nacos的很多主要是它同时提供注册中心和配置中心一个组件顶两个用。配置中心解决的是配置文件外置和动态刷新问题改配置不用重新发版Spring Cloud Config和Nacos都行我更建议Nacos操作界面友好文档也全。网关也是必备组件。Spring Cloud Gateway作为所有请求的统一入口负责路由转发、鉴权、限流、日志记录。比如前端只访问网关的一个地址网关根据路径把请求路由到不同微服务上。这么做的好处是客户端不用知道后端服务拆成了多少个也更方便做统一的安全策略。链路追踪在微服务架构里几乎必须。当一个请求经过网关、订单服务、库存服务、支付服务任何一个环节慢了你都要能定位。市面上主流是Micrometer Tracing Zipkin或者SkyWalking。我个人体验是SkyWalking对Java系的侵入最小通过agent方式接入就行控制台里的拓扑图能直观展示服务之间的调用关系定位问题特别方便。4.2 接口文档为什么从SpringFox迁移到SpringDoc热词里有springfox 3.0.0 与 spring boot 2.6这个话题我太有感触了。SpringFox是过去几年Spring Boot项目里最常用的Swagger解决方案但它的更新太滞后了Spring Boot 2.6之后引入的路径匹配策略从AntPathMatcher改为PathPatternParser导致SpringFox 3.0.0在启动时会直接抛出空指针异常。换句话说Spring Boot 2.6的用户如果继续用SpringFox不是要不要配的问题而是根本跑不起来的问题。当时网上有很多解决方案比如在application.yml里把spring.mvc.pathmatch.matching-strategy改回ant_path_matcher能启动但总感觉是在用旧配置兼容老库治标不治本。后来SpringDoc逐渐成了主流它的坐标是springdoc-openapi-starter-webmvc-ui能自动把Spring Boot的接口扫描成OpenAPI 3.0文档同时自带Swagger UI界面体验上比SpringFox还要顺滑。我现在的建议是新项目直接上SpringDoc老项目如果被SpringFox卡着升不了Boot版本就先在配置里兼容但把它记进技术债清单后面找时间统一迁移。SpringDoc的OpenAPI支持比SpringFox更标准团队协作时接口文档的维护成本会低一些。而且SpringDoc对Spring Boot 3的支持很及时你升到Boot 3.x之后文档这块不会拖后腿。4.3 从反应式架构和事件驱动里能偷学什么热词里还有反应式架构事件驱动。从超级大循环到事件驱动嵌入式架构升级的分水岭这些词猛一看和Spring Boot关系不大但它们在架构思想上很有参考价值。WebFlux体系在Spring Boot里也能跑对高并发IO密集场景有用但问题是对开发者心智模型要求高调式不方便我一般还是建议绝大多数业务系统先用传统的Servlet模型把业务逻辑分清楚比引进一种新编程模型更重要。事件驱动是更实用的思路。Spring Boot里可以用Spring的事件发布机制ApplicationEventPublisher做同一进程内的事件监听也可以用Spring Integration或Maven依赖引入一个消息中间件比如RocketMQ、RabbitMQ、Kafka来打通不同服务或者同一服务内的异步流程。比如用户下单成功后需要发短信、更新库存、记录积分如果这些逻辑全部同步执行接口响应时间会被拖慢。用事件驱动的方式主流程只做核心业务发短信、积分这些作为事件监听器异步处理用户响应时间能明显下降系统也能更好地应对流量高峰。事件的优点是业务间解耦缺点是链路变长排查问题更难所以一定要把消息内容的traceId设计好全链路串起来才能快速定位问题。5. 实战排雷那些年我们踩过的常见坑5.1 Spring Boot 2.6之后SpringFox不可用怎么办这个问题前面已经提到了深挖一下解决细节。如果你一定想继续用SpringFox可以在配置里改回旧的路径匹配策略spring: mvc: pathmatch: matching-strategy: ant_path_matcher但注意这个配置只解决了启动空指针不代表SpringFox的其他兼容问题解决了。在Spring Boot 2.6上Swagger UI里有些接口的请求参数展示依然可能不正确尤其是一些泛型返回类型Swagger文档渲染出来的model会缺字段。更干净的方案是迁移到SpringDoc。步骤如下pom.xml里移除springfox相关的两个依赖加上springdoc-openapi-uiBoot 2.x或springdoc-openapi-starter-webmvc-uiBoot 3.x然后启动项目访问/v3/api-docs和/swagger-ui.html确认文档正常加载。如果Controller上有自定义注解比如ApiOperation、ApiParam这些要改成Operation、Parameter注解包从io.swagger.annotations换到io.swagger.v3.oas.annotations。对于老项目来说这一步改动量不算小但换来的是后续持续可用我觉得这笔账值得算。5.2 Ajax参数进不来前端传参和后端接收的对应关系热词原文是spring boot无法通过ajax的参数前端,后台已取得数据我猜它的意思是后台已经拿到数据了但响应没有被前端正确处理或者反过来前端传的参数后台接收不到。这两个场景我都遇到过无数次先说最常见的原因。前端用jQuery的$.ajax或axios发请求默认Content-Type是application/x-www-form-urlencoded这种格式的参数名和值会放在请求体里后端用RequestParam或直接Controller方法参数名接收没问题。但如果前端设置了application/json请求体是JSON字符串后端就必须用RequestBody去接收而且对应的DTO字段名要和JSON里的key完全一致。注意RequestParam和RequestBody不能混用很多人把这两个混在一起结果参数一直为null。再看另一个极常见的问题前端传了JSON后端用RequestBody接收但Date类型字段的格式对不上。比如前端传的是2025-06-01 10:00:00后端LocalDateTime默认的JSON反序列化格式只接收ISO格式就会变成400错误。解决办法是在字段上写JsonFormat(pattern yyyy-MM-dd HH:mm:ss)或者全局配置Jackson的ObjectMapper。前端传参这块核心就一句话你的Content-Type和你的参数注解要对得上。响应侧的问题也很典型。后端返回Result对象前端拿到的是{code:200,message:success,data:{...}}但有的前端会在axios拦截器里直接判断response.status如果HTTP状态码是200他就把整个Result对象拿出来了不会去看data里的字段。所以很多后端同学会习惯性地在接口上不返回HTTP 200以外的状态码错误也返回200业务code让前端统一根据code判断。这种方式有争议但在前后端分离的团队里用好了也很顺关键是约定要写清楚接口文档里别含糊。5.3 端口占用和随机端口开发和DevOps里的细节端口被占用是开发期最常见的问题之一。Spring Boot默认端口8080如果本地起了多个项目很容易冲突。解决办法有三个层次一个是直接改配置server.port一个是用SpringBootTest(webEnvironment RANDOM_PORT)让测试用例随机选端口避免测试和本地服务冲突还有一个是启动时用命令行参数--server.port9090指定适合临时切换。在微服务或DevOps环境里固定端口反而是一种负担。你不可能让每个实例都手动分配端口所以Spring Boot支持配置server.port0来表示随机端口。服务启动后通过Spring的Environment或者LocalServerPort注解获取实际端口再向注册中心注册。这个机制在本地多实例联调、CI/CD流水线里都很实用。我有一个运维同学就因为没搞懂这个设计部署流程时还想着给每个服务预留端口段后来改成随机端口加服务发现以后整个部署链路简单了很多。5.4 线上问题排查Actuator、日志和Arthas三件套项目上线后排查问题靠什么盲猜代码是最低效的方式。我建议所有新项目都引入spring-boot-starter-actuator依赖这个依赖提供了一组运维端点比如/actuator/health做存活探针/actuator/metrics看JVM内存和线程信息/actuator/loggers可以动态调整日志级别/actuator/prometheus把监控指标暴露给Prometheus。这个依赖对业务的侵入几乎为零接入成本非常低但没有它线上问题排查就像摸黑走路。日志是第二板斧。我强调过logback-spring.xml的重要性这里再补充一点线上日志的级别和生产环境配置要分开日志文件要按天滚动并保留足够的周期方便回溯问题。遇到线上问题先翻日志找到异常的调用链再结合接口入参复现。如果日志不够那说明你之前打印日志的时候偷懒了关键分支、外部调用的入参和出参、耗时这些该打的地方都要打。第三板斧是Arthas阿里开源的Java诊断工具。它可以在不重启服务的情况下在线查看类加载信息、方法调用栈、方法入参出参、运行时内存快照。有一年我们线上有个服务CPU飙升load飙到30多我用Arthas的thread命令定位到具体线程再用trace命令跟踪那个方法最后发现是某个供应商接口超时后重试逻辑没判断状态导致线程死循环。整个过程没用jstack和jmap一个工具搞定效率非常高。Arthas的学习曲线有一点但凡是Java后端花一个小时把常用命令过一遍绝对是值得的。5.5 关于Spring Boot升级我个人的一条路线前面零零散散提到了版本这里集中给个建议。如果你是在用Spring Boot 2.1或2.2这个阶段的老项目我的建议是不要直接跨到3.x跨度太大依赖兼容问题会爆炸。先升到2.7.x这是2.x系列最后的版本把SpringFox迁移到SpringDoc把javax迁移问题记录下来然后规划一次单独升级直接升到3.2以上。如果是新项目直接上3.2或更高用Java 17这套组合在未来几年内都会是主流。Spring Boot的升级过程不仅仅是改版本号至少要做三件事。第一看官方release notes从2.1到每个大版本都有Breaking Changes说明比如Spring Boot 2.4的配置文件加载方式变化、2.6的循环引用禁止、3.0的Jakarta改名这些逐条对照项目代码。第二跑一遍完整的单元测试和接口测试升级之后的行为差异往往由测试暴露出来。第三观察线上指标包括QPS、响应时间、GC频率尤其注意Spring Boot 3之后底层默认的Web容器和Jackson版本变化带来的行为影响。说实话升级这事儿没有捷径全靠细心和耐心。但每一次升级都会让你对项目底层有更深的了解至少我现在看到spring boot 2.6或springfox 3.0.0这种组合就能立刻预判可能出什么问题这就是经验的价值。结尾最后再分享一点个人体会。做Spring Boot项目这些年我最大的感受是框架帮我们省掉了很多搭建成本但架构设计、依赖治理、异常处理、监控运维这些看不见的地方才是真正拉开项目质量差距的关键。你照着教程把CRUD跑通只是刚刚踏上台阶能把自己的项目从能跑打磨成稳定、好维护、易排查才是真正把手里的Spring Boot用活了。如果你手里刚好有项目要搭建花一天时间把配置文件、统一返回体、全局异常、日志规范这四件基础事情做好后面几个月的开发体验都会轻松很多。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门