依赖注入本质是将对象创建与使用解耦,a只管用b而不操心其构造;spring中@autowired失效主因有三:字段未被扫描、目标类未注册、手动new绕过容器;构造器注入优于字段注入,因其保障对象完整性和线程安全。

为什么 new 一个对象会让人头疼
因为硬编码的 new 把对象创建和使用死绑在一起。一旦类 A 依赖类 B,而 B 的构造方式变了(比如要加参数、换实现、加日志代理),所有调用 new B() 的地方都得改——这不是写代码,是批量修 bug。
依赖注入的本质不是“用框架”,而是把“谁来造 B”这个问题,从 A 内部移出去。A 只管用,不操心 B 怎么来。
- 常见错误现象:
NullPointerException频发,但堆栈里找不到明显空指针位置——其实是注入没生效,字段仍是null - 典型场景:单元测试时想替换成 Mock 对象,却发现 A 里直接
new B(),根本没法替换 - 关键区别:手动 new 是「主动拉取」,DI 是「被动接收」;前者控制权在使用者,后者在容器或调用方
Spring 中 @Autowired 注入失败的三个高频原因
@Autowired 看似一行搞定,实际失效时连日志都不报错,静默失败最伤人。
- 字段没加
public/protected修饰,且类没被 Spring 扫描到(比如放在src/test/java下却没配@TestConfiguration) - 目标类没加
@Component、@Service等注解,或者虽然加了但包路径不在@ComponentScan范围内 - 用了
new A()手动实例化,绕过了 Spring 容器——此时哪怕 A 类里有@Autowired,字段也一定是null
验证是否生效?加个构造函数或 @PostConstruct 方法,打印该字段是否为 null,比看文档快得多。
立即学习“Java免费学习笔记(深入)”;
构造器注入 vs 字段注入:选哪个不是风格问题,是生命周期问题
字段注入(@Autowired private B b;)写起来爽,但会让对象处于「半初始化」状态:实例已存在,依赖却还没塞进来。这对不可变性、线程安全、测试隔离都是隐患。
- 构造器注入强制你在创建时就提供所有必需依赖,对象一出生就是完整的——
final字段能用,NullPointerException少一半 - 字段注入在 Lombok 的
@RequiredArgsConstructor配合下可兼顾简洁,但注意:Lombok 生成的构造器不会自动加@Autowired,得手动补上 - 循环依赖时,Spring 默认只对 setter/字段注入做代理处理,构造器注入会直接抛
BeanCurrentlyInCreationException
如果类只有 1–2 个依赖,构造器注入几乎零成本;超过 5 个?先想想这个类是不是该拆了。
不用 Spring 也能做依赖注入:手写一个最小可用版
DI 不是 Spring 的专利。理解它,靠一个 Map<class>, Object></class> + 反射就能跑通核心逻辑。
// 简单容器伪代码
private Map<Class<?>, Object> beans = new HashMap<>();
public <T> void register(Class<T> type, T instance) {
beans.put(type, instance);
}
public <T> T get(Class<T> type) {
return (T) beans.get(type);
}
真正难的是自动装配:扫描类、解析构造器参数、递归获取依赖……这些 Spring 做了十几年才稳下来。自己造轮子前,先问一句:你真需要绕过 Spring 的 AOP、事务、异步等配套能力吗?
多数时候,卡住你的不是 DI 思想,而是没分清「谁负责创建」「谁负责传递」「谁负责销毁」这三件事。漏掉任一环,对象就会在某个深夜突然变成 null。








