Java跨模块解耦靠接口定契约、依赖注入管落地,需独立发布contract模块、面向接口编程、构造器注入+动态代理,封装与注入协同守住职责边界。

Java 通过接口实现跨模块解耦,本质是让模块之间只“认行为、不认实现”,再靠依赖注入把具体实现动态塞进去——接口定契约,注入管落地。
接口必须独立发布,作为模块间的唯一契约
不能把接口和实现写在同一个模块里,否则调用方一依赖就拖进一堆实现细节。正确做法是:为每个对外能力单独建 contract 模块,比如 user-api、order-api。
- 里面只放接口类(如
UserQueryService),方法签名清晰,异常类型语义化(如UserNotFoundException) - 只含 DTO(如
UserVO),不含 Jackson/MyBatis 注解、不带业务逻辑 - 可包含常量或枚举(如
UserStatus.ACTIVE),避免字符串硬编码散落
订单服务只需依赖 user-api,哪怕用户服务内部从单体拆成三个子服务,只要接口不变,它完全无感。
调用方必须面向接口编程,且依赖注入由框架完成
光有接口没用,关键在代码里不能出现具体实现类名,也不能手动 new。要靠构造器注入 + 动态代理来实现运行时绑定。
立即学习“Java免费学习笔记(深入)”;
- 业务类声明
private final UserQueryService userQueryService,通过构造函数接收 - 用 Feign 或 Dubbo 声明远程接口,例如:
@FeignClient(name = "user-service")<br>public interface UserQueryService {<br> @GetMapping("/{id}") UserVO findById(@PathVariable Long id);<br>} - 启动时框架自动生成代理,把接口调用转成 HTTP/gRPC 请求,调用方不关心协议、地址、负载策略
注入方式要可控、可测、不可变
字段注入(@Autowired private XxxService)看着省事,但破坏封装、难测试、易出空指针。推荐构造器注入,配合 final 字段。
- 依赖在创建对象时就必须提供,缺失则启动失败,问题暴露早
- 单元测试时直接
new OrderProcessor(mockUserService),不依赖 Spring 上下文 - 多实现时用
@Qualifier("alipayService")或@Primary精准指定,业务代码始终只认接口
封装与注入协同,守住职责边界
接口定义“能做什么”,封装决定“怎么用”。二者缺一不可。
- 仓储层统一用
UserRepository接口,上层不感知是 MySQL 还是 Redis 实现 - 支付模块暴露
PaymentService.pay(),隐藏微信 SDK 初始化、签名生成等细节 - 优惠计算收拢在
order.getFinalPrice()方法内,外部不拼 if-else 判断会员等级
只做注入却不封装,比如把 ArrayList 直接暴露给调用方,等于把内部结构全摊开,耦合照旧。


















