Spring 提供了多种依赖注入的方式
- 构造器注入(Constructor Injection)
构造器注入是通过类的构造函数来注入依赖项。这是 Spring 推荐的方式,因为它提供了不可变性和更好的可测试性。
import org.springframework.stereotype.Component;@Component
public class ServiceA {public void doSomething() {System.out.println("ServiceA is doing something.");}
}@Component
public class ServiceB {private final ServiceA serviceA;public ServiceB(ServiceA serviceA) {this.serviceA = serviceA;}public void doSomethingElse() {serviceA.doSomething();System.out.println("ServiceB is doing something else.");}
}
- 字段注入(Field Injection)
字段注入是通过 @Autowired 注解直接注入到类的字段中。这种方式简洁,但不推荐用于生产代码,因为它降低了可测试性和可读性。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;@Component
public class ServiceA {public void doSomething() {System.out.println("ServiceA is doing something.");}
}@Component
public class ServiceB {@Autowiredprivate ServiceA serviceA;public void doSomethingElse() {serviceA.doSomething();System.out.println("ServiceB is doing something else.");}
}
- Setter 方法注入(Setter Injection)
Setter 方法注入是通过类的 setter 方法来注入依赖项。这种方式适用于需要动态更改依赖项的场景。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;@Component
public class ServiceA {public void doSomething() {System.out.println("ServiceA is doing something.");}
}@Component
public class ServiceB {private ServiceA serviceA;// 在 set 方法上需要添加 @Autowired 注解,告诉 Spring 这是一个注入点。@Autowiredpublic void setServiceA(ServiceA serviceA) {this.serviceA = serviceA;}public void doSomethingElse() {serviceA.doSomething();System.out.println("ServiceB is doing something else.");}
}
- 方法注入(Method Injection)
方法注入是通过任意方法来注入依赖项。这种方式类似于 Setter 注入,但可以用于任何方法。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;@Component
public class ServiceA {public void doSomething() {System.out.println("ServiceA is doing something.");}
}@Component
public class ServiceB {private ServiceA serviceA;@Autowiredpublic void initServiceA(ServiceA serviceA) {this.serviceA = serviceA;}public void doSomethingElse() {serviceA.doSomething();System.out.println("ServiceB is doing something else.");}
}
- 使用 @Bean 和 @Configuration 进行显式配置
在复杂的场景中,你可以使用 @Configuration 和 @Bean 注解来显式配置依赖项。这种方式提供了最大的灵活性。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class AppConfig {@Beanpublic ServiceA serviceA() {return new ServiceA();}@Beanpublic ServiceB serviceB() {return new ServiceB(serviceA());}
}public class ServiceA {public void doSomething() {System.out.println("ServiceA is doing something.");}
}public class ServiceB {private final ServiceA serviceA;public ServiceB(ServiceA serviceA) {this.serviceA = serviceA;}public void doSomethingElse() {serviceA.doSomething();System.out.println("ServiceB is doing something else.");}
}
总结
构造器注入:推荐使用,提供了不可变性和更好的可测试性。
Setter 注入:适用于需要动态更改依赖项的场景。
字段注入:简洁但不推荐用于生产代码。
方法注入:类似于 Setter 注入,但更灵活。
接口注入:不常用,Spring 更倾向于使用注解和基于 Java 的配置。
显式配置:提供了最大的灵活性,适用于复杂场景。
选择哪种注入方式取决于具体的需求和场景。
在大多数情况下,构造器注入是最推荐的方式。
因为它更明确且能确保依赖在对象创建时就已准备好。
@Autowired 和 @Resource 是 Spring 框架中用于依赖注入的两个常用注解,它们在功能上有一定的相似性,但在实现细节和使用方式上存在显著区别。以下是它们的核心区别,帮助你根据具体需求做出选择:
1、来源与规范
@Autowired Spring 框架 Spring 专属
@Resource Java EE(JSR-250) 跨框架标准
- @Autowired 是 Spring 提供的注解,专为 Spring 生态系统设计。
- @Resource 是 Java EE 规范的一部分(javax.annotation.Resource),旨在实现跨框架的依赖注入。
- 默认注入策略
注解 默认注入方式 说明
@Autowired 按类型(by type) 根据依赖项的类型自动匹配。如果存在多个相同类型的 bean,会尝试按名称匹配。
@Resource 按名称(by name) 默认按字段名或属性名匹配 bean。如果没有指定名称,则使用变量名或方法名。 - @Autowired 优先按类型匹配,可能导致歧义(多个相同类型的 bean)。
- @Resource 优先按名称匹配,更直观且不易产生歧义。
- 依赖项名称的指定方式
注解 指定名称的方式
@Autowired 使用 @Qualifier 注解显式指定 bean 名称。
@Resource 直接通过 name 属性显式指定 bean 名称。
示例
java
// 使用 @Autowired + @Qualifier
@Autowired
@Qualifier(“specificService”)
private ServiceA serviceA;
// 使用 @Resource
@Resource(name = “specificService”)
private ServiceA serviceA;
@Autowired 需要额外使用 @Qualifier 注解来指定名称。
@Resource 直接通过 name 属性指定名称,更加简洁。
4. 注入位置的支持
注解 支持的注入位置
@Autowired 字段、构造函数、setter 方法、任意方法。
@Resource 字段、构造函数、setter 方法。
@Autowired 支持更广泛的注入位置,包括任意方法(例如初始化方法)。
@Resource 仅支持字段、构造函数和 setter 方法。
5. 必需性的默认行为
注解 默认必需性 修改方式
@Autowired 必需(必须存在依赖项) 通过 required = false 设置为非必需。
@Resource 必需(必须存在依赖项) 通过 lookup 或 mappedName 属性间接修改,但 Spring 中不常用。
@Autowired 默认要求依赖项必须存在,否则会抛出异常。可以通过 required = false 修改此行为。
@Resource 默认也要求依赖项必须存在,但修改必需性的方式不如 @Autowired 直观。
6. 适用场景与选择建议
选择 @Autowired 的场景
Spring 生态系统:项目完全基于 Spring 框架开发,且依赖 Spring 的其他功能。
按类型注入:需要按依赖项的类型自动匹配,且不介意可能存在的歧义(或通过 @Qualifier 解决)。
灵活性需求:需要在构造函数、setter 方法或任意方法中进行注入。
选择 @Resource 的场景
按名称注入:明确需要按 bean 的名称进行注入,避免歧义。
跨框架兼容性:项目可能迁移到其他框架(如 Jakarta EE),需要使用标准注解。
简洁性:希望减少注解数量,直接通过 name 属性指定依赖项。
7. 示例对比
@Autowired 示例
java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
@Component
public class ServiceA {
public void doSomething() {
System.out.println(“ServiceA is doing something.”);
}
}
@Component(“specificService”)
public class AnotherServiceA implements ServiceA {
@Override
public void doSomething() {
System.out.println(“AnotherServiceA is doing something.”);
}
}
@Component
public class ServiceB {
private final ServiceA serviceA;
@Autowired
@Qualifier("specificService") // 显式指定 bean 名称
public ServiceB(ServiceA serviceA) {this.serviceA = serviceA;
}public void doSomethingElse() {serviceA.doSomething();System.out.println("ServiceB is doing something else.");
}
}
@Resource 示例
java
import javax.annotation.Resource;
import org.springframework.stereotype.Component;
@Component
public class ServiceA {
public void doSomething() {
System.out.println(“ServiceA is doing something.”);
}
}
@Component(“specificService”)
public class AnotherServiceA implements ServiceA {
@Override
public void doSomething() {
System.out.println(“AnotherServiceA is doing something.”);
}
}
@Component
public class ServiceB {
private final ServiceA serviceA;
@Resource(name = "specificService") // 直接指定 bean 名称
public ServiceB(ServiceA serviceA) {this.serviceA = serviceA;
}public void doSomethingElse() {serviceA.doSomething();System.out.println("ServiceB is doing something else.");
}
}
8. 总结
特性 @Autowired @Resource
来源 Spring 框架 Java EE 规范
默认注入策略 按类型(by type) 按名称(by name)
指定名称的方式 通过 @Qualifier 通过 name 属性
支持的注入位置 字段、构造函数、setter 方法、任意方法 字段、构造函数、setter 方法
必需性默认行为 必需,可通过 required = false 修改 必需,修改方式不直观
适用场景 Spring 生态系统、按类型注入、灵活性需求 按名称注入、跨框架兼容性、简洁性需求
最终建议
优先选择 @Autowired:
如果项目完全基于 Spring 框架。
需要按类型注入或更灵活的注入方式。
希望利用 Spring 生态系统的其他功能。
选择 @Resource:
如果需要按名称注入以避免歧义。
希望代码更具可移植性(不依赖于 Spring 特定注解)。
项目可能迁移到其他框架(如 Jakarta EE)。
通过理解两者的区别和适用场景,你可以根据项目需求做出更合理的选择。
在使用 @Autowired 进行依赖注入时,@Qualifier 注解用于解决依赖项的歧义问题。当 Spring 容器中存在多个相同类型的 bean 时,@Autowired 默认按类型注入会导致 Spring 无法确定应该注入哪个 bean。这时,@Qualifier 注解就派上用场,帮助明确指定要注入的 bean。
何时需要使用 @Qualifier
存在多个相同类型的 bean
如果你的应用程序中定义了多个相同类型的 bean,并且没有使用 @Primary 注解来指定一个默认的 bean,那么 Spring 无法确定应该注入哪个 bean。这时,你需要使用 @Qualifier 来明确指定要注入的 bean。
示例
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;public interface ServiceA {void doSomething();
}@Component("serviceA1")
public class ServiceAImpl1 implements ServiceA {@Overridepublic void doSomething() {System.out.println("ServiceAImpl1 is doing something.");}
}@Component("serviceA2")
public class ServiceAImpl2 implements ServiceA {@Overridepublic void doSomething() {System.out.println("ServiceAImpl2 is doing something.");}
}@Component
public class ServiceB {private final ServiceA serviceA;@Autowired@Qualifier("serviceA1") // 显式指定要注入的 bean 名称public ServiceB(ServiceA serviceA) {this.serviceA = serviceA;}public void doSomethingElse() {serviceA.doSomething();System.out.println("ServiceB is doing something else.");}
}
在这个示例中,ServiceA 接口有两个实现类 ServiceAImpl1 和 ServiceAImpl2。通过 @Qualifier(“serviceA1”),我们明确指定要注入 ServiceAImpl1 的实例。
需要动态选择 bean
在某些情况下,你可能需要根据不同的条件或配置动态选择要注入的 bean。虽然 @Qualifier 本身是静态的(在编译时确定),但你可以结合其他机制(如配置文件或环境变量)来动态选择 @Qualifier 的值。
与其他框架或库集成
当你的项目与其他框架或库集成时,这些框架或库可能会提供自己的 bean 实现。使用 @Qualifier 可以帮助你明确指定要使用哪个实现。
替代方案
@Primary 注解:如果你有一个默认的 bean 实现,可以使用 @Primary 注解来标记它。这样,在没有明确指定 @Qualifier 的情况下,Spring 会优先注入带有 @Primary 注解的 bean。
示例
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;@Component
@Primary
public class ServiceAImpl1 implements ServiceA {@Overridepublic void doSomething() {System.out.println("ServiceAImpl1 is doing something.");}
}@Component
public class ServiceAImpl2 implements ServiceA {@Overridepublic void doSomething() {System.out.println("ServiceAImpl2 is doing something.");}
}
在这个示例中,ServiceAImpl1 被标记为 @Primary,因此当没有使用 @Qualifier 时,Spring 会注入 ServiceAImpl1。
总结:
在 Spring 框架中,“相同类型的 bean” 指的是在 Spring 容器中存在多个实现了相同接口或继承了相同类的 bean 实例。这意味着这些 bean 属于同一个类型(即相同的接口或父类),Spring 无法仅通过类型来区分它们,从而在依赖注入时产生歧义。
使用 @Qualifier:当 Spring 容器中存在多个相同类型的 bean,并且你需要明确指定要注入的 bean 时。
使用 @Primary:当你希望指定一个默认的 bean 实现,以避免在大多数情况下需要使用 @Qualifier 时。
通过合理使用 @Qualifier 和 @Primary,你可以更灵活地管理 Spring 应用中的依赖注入,避免因 bean 歧义导致的注入问题。