我说明一下我开发本地使用win10时,用IDEA打包部署到ubuntu服务器是没有循环依赖问题的,...
作成日: 2026年1月8日
使用モデル GPT-5.2 Thinking by Chat01
作成日: 2026年1月8日
使用モデル GPT-5.2 Thinking by Chat01
我说明一下我开发本地使用win10时,用IDEA打包部署到ubuntu服务器是没有循环依赖问题的,开发本地使用ubuntu时,用idea打包部署到ubuntu服务器就有循环依赖问题,请解释一下原因,我不太想使用手动构造注入,去修改太多的类
taskDetailServiceImpl (field private com.laboratory.modules.taskAssign.projectManagerAssign.service.TaskPmAssignService com.laboratory.modules.taskAssign.deptManagerAssign.service.impl.TaskDetailServiceImpl.pmAssignService)
↓
taskPmAssignServiceImpl (field private com.laboratory.modules.taskAssign.reportAssign.service.TaskReportAssignService com.laboratory.modules.taskAssign.projectManagerAssign.service.impl.TaskPmAssignServiceImpl.reportAssignService)
↓
taskReportAssignServiceImpl (field private com.laboratory.modules.base.reportCertificate.service.BaseReportCertificateService com.laboratory.modules.taskAssign.reportAssign.service.impl.TaskReportAssignServiceImpl.reportCertificateService)
↓
baseReportCertificateServiceImpl (field private com.laboratory.modules.sample.sampleReceiveDetail.service.SamReceiveDetailService com.laboratory.modules.base.reportCertificate.service.impl.BaseReportCertificateServiceImpl.samReceiveDetailService)
↓
samReceiveDetailServiceImpl (field private com.laboratory.modules.sample.entryStockDetail.service.SamEntryStockDetailService com.laboratory.modules.sample.sampleReceiveDetail.service.impl.SamReceiveDetailServiceImpl.samEntryStockDetailService)
↓
samEntryStockDetailServiceImpl (field private com.laboratory.modules.sample.entryStock.service.SamEntryStockService com.laboratory.modules.sample.entryStockDetail.service.impl.SamEntryStockDetailServiceImpl.samEntryStockService)
↓
samEntryStockServiceImpl (field private com.laboratory.modules.bpm.bpmTask.service.BpmTaskService com.laboratory.modules.sample.entryStock.service.impl.SamEntryStockServiceImpl.bpmTaskService)
┌─────┐
| bpmTaskServiceImpl defined in URL [jar:file:/lrms-8000.jar!/BOOT-INF/classes!/com/laboratory/modules/bpm/bpmTask/service/impl/BpmTaskServiceImpl.class]
↑ ↓
| flowNewServiceImpl defined in URL [jar:file:/lrms-8000.jar!/BOOT-INF/classes!/com/laboratory/modules/bpm/flow/service/impl/FlowNewServiceImpl.class]
↑ ↓
| conReviewServiceImpl (field private com.laboratory.modules.taskAssign.taskItems.service.TaskItemsService com.laboratory.modules.contract.conReview.service.impl.ConReviewServiceImpl.taskItemsService)
↑ ↓
| taskItemsServiceImpl (field private com.laboratory.modules.taskAssign.taskItems.service.CheckItemMergeService com.laboratory.modules.taskAssign.taskItems.service.impl.TaskItemsServiceImpl.checkItemMergeService)
↑ ↓
| checkItemMergeServiceImpl (field private com.laboratory.modules.bpm.bpmTask.service.BpmTaskService com.laboratory.modules.taskAssign.taskItems.service.impl.CheckItemMergeServiceImpl.bpmTaskService)
你这条链路里循环依赖是“真实存在的”(最终又回到了 bpmTaskServiceImpl),只是在不同构建产物/配置下,Spring 有时能“绕过去”、有时会直接判定失败,所以才会出现:
下面是最常见的原因(按概率从高到低),以及你不想“大改类”的几种最小改动方案。
Spring Boot 2.6+ 开始,默认把循环依赖 禁掉了(spring.main.allow-circular-references 默认 false)。
Boot 2.5 及以前默认允许(很多 setter/field 注入的循环可以被“提前暴露引用”解决)。
所以很典型的现象是:
2.+ / LATEST / RELEASE / version range),解析到了较老版本 → 启动能过✅你可以很快验证:比较两个 jar 的依赖版本(尤其是 Spring Boot / Spring Framework)。
在服务器上对两个 jar 各执行:
META-INF/MANIFEST.MF/依赖清单对比只要版本不一致,这就是第一嫌疑。
很多人以为“IDEA 打包=一样”,但实际上可能有这些差异:
mvn clean package / gradle bootJar结果就是:同一份代码,jar 内的依赖集合不同,从而触发/不触发循环依赖(或者触发某些 AOP 代理创建顺序差异)。
✅建议统一:只用 Maven/Gradle 官方插件构建,并在 CI 或 Docker 里构建,避免“本机差异”。
比如 Win10 的包里恰好带了某个配置使得循环依赖被允许或被延迟初始化:
spring.main.allow-circular-references=truespring.main.lazy-initialization=true而 Ubuntu 打包时资源过滤/覆盖顺序不同,导致配置没进包或被覆盖。
✅验证方法:直接对比两个 jar 里的 application*.yml/properties 是否一致。
你的循环链里最关键的“回环点”是:checkItemMergeServiceImpl -> bpmTaskServiceImpl(又把 bpmTaskService 拉回来了)。
只要在环上任意一点“延迟拿到对方”,循环就断了。
在 application.yml 加:
yamlspring: main: allow-circular-references: true
优点:几乎不改代码。
缺点:只是“允许它存在”,后面复杂度会继续累积;并且有些循环在更复杂的代理场景仍可能翻车。
@Lazy比如在 CheckItemMergeServiceImpl 里把注入改成延迟:
java@Autowired @Lazy private BpmTaskService bpmTaskService;
或在 BpmTaskServiceImpl 注入 FlowNewService 的地方加 @Lazy。
优点:只改一处字段,不需要你全面改成构造器注入,也不需要“手动构造”。
原理:让其中一个 bean 先创建出来,另一个引用在真正调用时才触发获取,从而断环。
ObjectProvider<T> 延迟获取java@Autowired private ObjectProvider<BpmTaskService> bpmTaskServiceProvider; // 使用时 bpmTaskServiceProvider.getObject().xxx();
优点:比 @Lazy 更明确什么时候取对象。
缺点:需要把调用点改一下(但通常只改少量调用)。
代码里的循环依赖一直都在;Win10 打出来的 jar 之所以能跑,通常是因为:
allow-circular-references=true / lazy 初始化,或如果你愿意把两边 jar 的 BOOT-INF/lib 里 spring-boot 和 spring-framework 的版本列表(截个几行)贴出来,我可以直接帮你定位到底是“版本差异”还是“配置/打包方式差异”,并告诉你应该把 @Lazy 放在环上的哪一刀最稳。