Java对象解耦的核心是消除new、依赖接口与运行时替换——通过接口定义契约、构造器注入实现类、将创建权外移,使业务类不绑定具体实现;Spring的@Autowired仅自动化此过程,前提必须满足接口编程与Bean声明规范。

Java里对象解耦不是靠“写得松一点”,而是靠编译期契约和运行时替换能力——核心是把 new 拆掉,让依赖关系从代码里抽出来,交给接口和实现分离来承载。
为什么直接 new ConcreteClass 会导致紧耦合
当你在业务类里写 new UserServiceImpl(),这个类就同时绑定了具体实现、构造逻辑、生命周期管理。一旦要换数据库、加缓存、做单元测试,就得改源码、重编译、冒回归风险。
- 测试时无法用
MockUserService替换真实服务 - 切换 MySQL 到 PostgreSQL 需要修改所有
new点 - 无法在不改业务代码的前提下注入监控、日志、事务代理
用接口 + 构造器注入实现解耦(无框架版)
解耦的关键动作是:定义接口、实现类独立、使用者只持接口引用、创建权外移。
public interface UserRepository {
User findById(Long id);
}
public class JdbcUserRepository implements UserRepository {
private final DataSource dataSource;
public JdbcUserRepository(DataSource ds) { this.dataSource = ds; }
public User findById(Long id) { / JDBC 查询 / }
}
public class UserService {
private final UserRepository userRepository; // 只依赖接口
public UserService(UserRepository repo) { // 构造器注入
this.userRepository = repo;
}
public User getUser(Long id) {
return userRepository.findById(id);
}
}
此时 UserService 完全不知道数据怎么查,也不关心谁来查——只要传进来的对象实现了 UserRepository 接口就行。你可以传 new JdbcUserRepository(ds),也可以传 new CacheUserRepository(new JdbcUserRepository(ds)),都不用动 UserService 一行代码。
立即学习“Java免费学习笔记(深入)”;
Spring 的 @Autowired 是怎么帮上忙的
它没发明新东西,只是自动化了“谁来 new、什么时候 new、怎么配依赖”这些重复劳动。但前提是:你已经按接口编程写了可替换的类。
- 必须有接口(或至少是
@Component标记的抽象类/普通类) - 实现类需用
@Service/@Repository声明为 Spring Bean - 注入点要用接口类型声明,比如
@Autowired private UserRepository repo; - 如果多个实现类存在,必须用
@Qualifier或@Primary明确选哪个
漏掉任意一条,Spring 就会报 NoUniqueBeanDefinitionException 或 NoSuchBeanDefinitionException ——这不是框架问题,是你没完成解耦的前提条件。
容易被忽略的边界情况
解耦不是一劳永逸。下面这些地方稍不注意,就会悄悄把耦合又焊回去:
- 在 service 方法里写
new SimpleDateFormat("yyyy-MM-dd")—— 时间格式化不该由业务类决定,应抽成DateFormatter接口 - 使用
Class.forName("com.xxx.JdbcUserRepo")手动加载类 —— 这等于硬编码实现类名,绕过了所有注入机制 - 接口方法签名暴露实现细节,比如
User findUserByIdAndVersion(Long id, Integer version),把乐观锁版本号塞进接口,导致所有实现都必须支持,丧失替换自由度 - 把
ApplicationContext注入到 service 里再手动getBean()—— 这叫“服务定位器反模式”,破坏了依赖显式声明原则
真正的解耦感,往往出现在你第一次不改任何业务类,只换一个配置或加一个 @Profile("test") Bean,就让整套流程走通的时候。











