不该。构造函数中直接 new 具体实现类会破坏可测试性与解耦,正确做法是将依赖声明为参数由外部注入;Spring 推荐 final 构造函数注入以保证不可变性和非空安全;参数过多需拆分或封装。

构造函数里该不该直接 new 依赖对象
不该。构造函数中直接 new 具体实现类,会破坏可测试性与解耦——比如单元测试时无法替换为 mock 实例,也无法通过配置切换不同实现。
正确做法是把依赖声明为参数,由外部传入:
public class OrderService {
private final PaymentGateway paymentGateway;
public OrderService(PaymentGateway paymentGateway) {
this.paymentGateway = paymentGateway;
}
}
这样既明确表达了“我需要一个 PaymentGateway”,又把实例创建权交给了调用方或容器。
Spring 中 @Autowired 构造函数注入为什么推荐用 final 字段
因为 Spring 5.0+ 官方明确推荐构造函数注入(而非 @Autowired 字段注入),而 final 能保证依赖不可变、非空,避免运行时 NullPointerException 或意外重赋值。
立即学习“Java免费学习笔记(深入)”;
常见错误包括:
- 字段未加
final,后续被误设为null - 写了多个构造函数,Spring 找不到唯一匹配的带
@Autowired的那个 - 在非 Spring 管理的类里(如工具类)也照搬构造函数注入,结果依赖始终为
null
推荐写法:
@Service
public class UserService {
private final UserRepository userRepository;
private final EmailSender emailSender;
public UserService(UserRepository userRepository, EmailSender emailSender) {
this.userRepository = userRepository;
this.emailSender = emailSender;
}
}
手动 new 对象时如何处理构造函数依赖
当你不能依赖 Spring 容器(比如在 main 方法、单元测试 setup、或遗留代码中),就得自己组织依赖链。
关键点:
- 按依赖顺序逐层构造:先 new 底层依赖(如
JdbcTemplate),再传给上层(如UserRepository),最后传给业务类 - 避免重复创建共享资源(如数据库连接池),应提取为单例或复用变量
- 测试场景下可直接传入
Mockito.mock(UserRepository.class)等模拟对象
示例:
DataSource dataSource = new HikariDataSource(); JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); UserRepository userRepository = new JdbcUserRepository(jdbcTemplate); UserService userService = new UserService(userRepository, new SmtpEmailSender());
构造函数参数太多是不是设计有问题
是。超过 4–5 个参数通常说明类职责过重或抽象不合理。
可以考虑:
- 拆分类:把部分逻辑抽成新服务,降低单个构造函数参数数量
- 引入参数对象(Parameter Object):如将
timeoutMs、retryCount、backoffStrategy封装进RetryConfig - 检查是否混入了非核心依赖:比如日志
Logger不该作为构造参数,应通过LoggerFactory.getLogger(...)获取
过度依赖注入会让构造函数变成“配置搬运工”,掩盖真实协作关系。
真正难的不是怎么写构造函数,而是判断哪些东西该由外部提供、哪些该自己创建——这取决于变化频率和复用边界。










