Spring中有哪些依赖注入方式
依赖注入的核心思想是一个组件(比如一个Service类)不再由自己去创建和管理它所依赖的另一个组件(比如一个Repository类),而是将被依赖的组件的控制权交给外部容器(即Spring IoC容器)。组件只需要声明它需要什么,容器就会在运行时将对应的实例注入给它。这样做的好处是极大地降低了组件之间的耦合度,使得代码更易于测试和维护。
Spring主要提供了以下三种依赖注入的方式:
1.构造函数注入 (Constructor Injection)
这是Spring团队目前最推荐的注入方式。
-
实现方式:
在类的构造函数中声明需要依赖的组件作为参数。Spring容器在创建这个类的Bean实例时,会自动查找并传入相应的依赖Bean。
@Service
public class MyService {
private final UserRepository userRepository;
// 如果只有一个构造函数,@Autowired注解可以省略
public MyService(UserRepository userRepository) {
this.userRepository = userRepository;
}
// ... 业务方法
}
-
优点:
-
保证依赖不可变:可以将依赖字段声明为 final,确保一旦注入就不会被修改,增强了程序的稳定性和线程安全。
-
保证对象完整性:依赖在对象构造时必须被提供,这意味着对象在创建后一定是处于一个完整、可用的状态,不会出现依赖为null的情况。
-
清晰地暴露依赖关系:类的所有必要依赖都在构造函数中一目了然。如果构造函数参数过多,也提醒我们这个类可能违反了单一职责原则,需要重构。
-
-
缺点:
-
不支持循环依赖:如果类A的构造函数需要B,同时类B的构造函数也需要A,Spring无法解决这种构造器循环依赖,会抛出异常。但这通常被认为是一个优点,因为它能帮助我们及早发现设计上的问题。
-
代码可能稍显冗长。
-
2.Setter方法注入 (Setter Injection)
-
实现方式:
在类中为需要注入的依赖提供一个公开的setter方法,并通常在该方法上标注@Autowired。Spring容器会先调用类的无参构造函数创建实例,然后再调用setter方法来注入依赖。
@Service
public class MyService {
private UserRepository userRepository;
@Autowired
public void setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}
// ... 业务方法
}
-
优点:
-
适用于可选依赖:对于非必需的依赖,可以使用setter注入,即使没有提供这个依赖,对象本身也能被创建。
-
可以解决循环依赖:Spring可以通过先实例化Bean(此时依赖为null),然后再通过setter方法相互注入,从而解决循环依赖问题。
-
-
缺点:
-
无法保证对象完整性:对象在被创建后,其依赖可能仍是null,只有在setter方法被调用后才完整。
-
依赖可变:setter方法可以被多次调用,导致对象的依赖在运行时被改变,这可能会引入不确定性。
-
3.字段注入 (Field Injection)
这是过去非常流行,但现在已不被推荐的注入方式。
-
实现方式:
直接在类的成员字段上使用@Autowired注解。
@Service
public class MyService {
@Autowired
private UserRepository userRepository;
// ... 业务方法
}
-
优点:
- 代码最简洁:写法非常简单,省去了构造函数或setter方法的代码。
-
缺点:
-
与IoC容器强耦合:被注入的字段通常是私有的,这意味着如果不通过Spring容器,就无法方便地创建这个类的实例并为其设置依赖(尤其是在单元测试中),必须借助反射。
-
隐藏依赖关系:类的依赖关系被隐藏在实现细节中,从类的公开契约(构造函数和方法)上看不出它需要哪些依赖。
-
无法保证依赖不可变:不能将字段声明为 final。
-
容易掩盖设计问题:它同样可以解决循环依赖,但这往往会掩盖代码中存在的坏味道。
-