字段注入
@Autowired: 类型注入+@Qualifer = @Resource
@Resource根据名称注入,当找不到对应名称的时候,根据类型注入
优点
- 简单易用:直接在字段上标注@Autowired,无需额外的构造函数或setter方法。
- 代码简洁:减少了模板代码,特别是在依赖数量不多时。
缺点:
- 不支持不可变性:由于字段是在对象创建后注入,不能声明为final。
- 降低可测试性:不使用Spring容器时,例如在单元测试中,很难替换依赖项。
- 违反了Spring推荐的最佳实践:构造函数注入是推荐的方式,因为它支持不可变性,并且依赖在使用前总是被初始化。
- 当使用new的时候,会导致注入失败
构造器注入
注入全过程
- 启动阶段:与字段注入相同,Spring首先创建所有的bean定义,并扫描标注了Spring注解的类。
- 依赖解析:对于构造函数注入,当创建类的实例时,Spring容器查看类的构造函数参数,确定需要注入哪些依赖。
- 依赖注入:Spring容器然后实例化这些依赖(如果它们尚未创建)并通过构造函数注入到正在创建的bean中。这确保了在对象完全构造之前所有必需的依赖都已经提供。
- 对象初始化:一旦所有构造函数参数被注入,对象被实例化,并且所有设置方法和回调都被调用。
优点:
- 支持不可变性:依赖可以被声明为final,确保了一旦构造对象后不会改变。
- 促进了更好的软件设计:构造函数注入强制要求依赖在构造对象时必须存在,从而保证了bean的依赖不会是null。
- 提高可测试性:容易在测试中通过构造函数替换依赖,特别是使用Mock对象时。
缺点:
- 构造函数臃肿:如果一个类有多个依赖,构造函数可能会变得很长,这可能使代码更难阅读和维护。
- 可能需要更多配置:特别是在存在多个构造函数或需要特定的配置来选择适当构造函数时
tips:
可以使用@RequiredArgsConstructor来代替构造函数
Spring 如何解决构造器注入的循环依赖
1. 修改代码 2. 使用字段依赖注入(已弃用) 3. 使用 @Lazy 注解
public UserService(@Lazy OrderService orderService){
this.orderService = orderService;
}
// 此处的TargetSource 和 上文 buildLazyResolutionProxy() 构建的TargetSource 关联
1. TargetSource targetSource = this.advised.getTargetSource();
// 获取被代理的对象target
2. target = targetSource.getTarget();
// 反射调用被代理对象的方法
3. retVal = invokeMethod(target, method, argsToUse, methodProxy);
@Lazy注解 解决思路是:初始化时注入代理对象,真实调用时使用Spring AOP动态代理去关联真实对象,然后通过反射完成调用。