跳到主要内容

依赖注入

依赖注入(Dependency Injection,DI)是 Spring 实现 IOC 的核心机制。本章详细介绍 Spring 支持的各种依赖注入方式,以及它们的优缺点和适用场景。

依赖注入的本质

依赖注入的本质很简单:容器负责将依赖对象注入到需要它的组件中,而不是由组件自己创建依赖。

没有 DI 的情况

public class OrderService {
private OrderDao orderDao = new OrderDaoImpl();
private PaymentService paymentService = new PaymentServiceImpl();

public void processOrder(Order order) {
orderDao.save(order);
paymentService.charge(order);
}
}

OrderService 直接创建依赖对象,耦合度高,难以测试。

使用 DI 的情况

public class OrderService {
private final OrderDao orderDao;
private final PaymentService paymentService;

public OrderService(OrderDao orderDao, PaymentService paymentService) {
this.orderDao = orderDao;
this.paymentService = paymentService;
}

public void processOrder(Order order) {
orderDao.save(order);
paymentService.charge(order);
}
}

OrderService 通过构造器接收依赖,由容器负责注入。代码更清晰,依赖关系一目了然。

注入方式

Spring 支持三种主要的依赖注入方式:构造器注入、Setter 注入和字段注入。

构造器注入

构造器注入是通过类的构造函数来注入依赖的方式,这是 Spring 官方推荐的方式。

@Service
public class UserService {

private final UserDao userDao;
private final EmailService emailService;

@Autowired
public UserService(UserDao userDao, EmailService emailService) {
this.userDao = userDao;
this.emailService = emailService;
}

public void registerUser(User user) {
userDao.save(user);
emailService.sendWelcomeEmail(user);
}
}

优点

不可变性:依赖可以声明为 final,保证对象创建后状态不变,线程安全。

强制依赖:对象创建时必须提供所有依赖,不会出现依赖为 null 的情况。

易于测试:可以直接通过构造器传入 Mock 对象,不需要反射。

依赖清晰:从构造器签名就能看出类需要哪些依赖。

缺点

构造器膨胀:依赖过多时构造器参数列表会很长,这是设计问题的信号。

可选依赖处理麻烦:对于可选依赖,需要额外处理。

单构造器省略 @Autowired

从 Spring 4.3 开始,如果类只有一个构造器,可以省略 @Autowired 注解:

@Service
public class UserService {

private final UserDao userDao;

public UserService(UserDao userDao) {
this.userDao = userDao;
}
}

如果有多个构造器,必须用 @Autowired 指定使用哪个:

@Service
public class UserService {

private final UserDao userDao;
private EmailService emailService;

@Autowired
public UserService(UserDao userDao) {
this.userDao = userDao;
}

public UserService(UserDao userDao, EmailService emailService) {
this.userDao = userDao;
this.emailService = emailService;
}
}

Setter 注入

Setter 注入是通过 setter 方法注入依赖的方式,适合可选依赖。

@Service
public class NotificationService {

private MessageService messageService;
private EmailService emailService;

@Autowired
public void setMessageService(MessageService messageService) {
this.messageService = messageService;
}

@Autowired(required = false)
public void setEmailService(EmailService emailService) {
this.emailService = emailService;
}
}

优点

灵活性高:可以在运行时更换依赖。

可选依赖:通过 required = false 声明可选依赖,容器找不到 Bean 时不会报错。

循环依赖:可以解决部分循环依赖问题。

缺点

非线程安全:依赖可以在任何时候被修改。

可能为 null:依赖可能没有被注入,使用前需要判空。

依赖不清晰:从类定义看不出需要哪些依赖。

字段注入

字段注入是直接在字段上使用 @Autowired 注解,不推荐使用。

@Service
public class UserService {

@Autowired
private UserDao userDao;

@Autowired
private EmailService emailService;
}

缺点

无法使用 final:字段不能声明为 final,无法保证不可变性。

难以测试:单元测试时需要反射才能注入 Mock 对象。

隐藏依赖:从类定义看不出依赖关系,需要查看源码或文档。

与容器耦合:脱离 Spring 容器后无法使用。

为什么还能看到很多字段注入:因为写起来简单,很多教程和示例代码使用这种方式。但在实际项目中应该避免。

注入方式对比

特性构造器注入Setter 注入字段注入
不可变性支持不支持不支持
强制依赖支持不支持不支持
可选依赖不支持支持支持
单元测试简单简单困难
依赖清晰度
推荐程度推荐可选依赖使用不推荐

自动装配

自动装配是 Spring 容器自动为 Bean 注入依赖的机制。Spring 提供了多种自动装配模式。

@Autowired 注解

@Autowired 是最常用的自动装配注解,默认按类型匹配。

@Service
public class UserService {

private final UserDao userDao;

@Autowired
public UserService(UserDao userDao) {
this.userDao = userDao;
}
}

按类型匹配:Spring 会查找容器中类型匹配的 Bean 进行注入。

required 属性:默认为 true,找不到 Bean 时抛出异常。设为 false 表示可选依赖。

@Autowired(required = false)
private OptionalService optionalService;

@Qualifier 注解

当容器中存在多个相同类型的 Bean 时,需要用 @Qualifier 指定 Bean 名称。

@Service
public class NotificationService {

private final MessageService messageService;

@Autowired
public NotificationService(@Qualifier("emailMessageService") MessageService messageService) {
this.messageService = messageService;
}
}

也可以在字段上使用:

@Autowired
@Qualifier("smsMessageService")
private MessageService messageService;

@Primary 注解

当存在多个相同类型的 Bean 时,可以用 @Primary 指定默认 Bean。

@Component
@Primary
public class EmailMessageService implements MessageService {
@Override
public void send(String message) {
System.out.println("Email: " + message);
}
}

@Component
public class SmsMessageService implements MessageService {
@Override
public void send(String message) {
System.out.println("SMS: " + message);
}
}

注入时优先使用标记了 @Primary 的 Bean:

@Service
public class NotificationService {

@Autowired
private MessageService messageService;
}

@Resource 注解

@Resource 是 JSR-250 标准注解,默认按名称匹配。

@Service
public class NotificationService {

@Resource(name = "emailMessageService")
private MessageService messageService;
}

如果不指定 name,默认使用字段名作为 Bean 名称:

@Resource
private MessageService emailMessageService;

@Inject 注解

@Inject 是 JSR-330 标准注解,行为与 @Autowired 类似,但没有 required 属性。

@Inject
private UserService userService;

使用 @Inject 需要添加依赖:

<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>

注解选择建议

注解来源匹配方式特点
@AutowiredSpring按类型最常用,支持 required
@QualifierSpring按名称配合 @Autowired 使用
@PrimarySpring标记默认解决多 Bean 问题
@ResourceJSR-250按名称标准注解,可指定 name
@InjectJSR-330按类型标准注解,无 required

推荐做法:优先使用 @Autowired + @Qualifier,需要标准注解时使用 @Resource@Inject

集合注入

Spring 支持注入集合类型的依赖,可以自动收集所有匹配类型的 Bean。

List 注入

注入所有匹配类型的 Bean:

@Component
public class EmailSender implements MessageSender {
public void send(String message) { System.out.println("Email: " + message); }
}

@Component
public class SmsSender implements MessageSender {
public void send(String message) { System.out.println("SMS: " + message); }
}

@Service
public class NotificationService {

private final List<MessageSender> senders;

@Autowired
public NotificationService(List<MessageSender> senders) {
this.senders = senders;
}

public void notifyAll(String message) {
senders.forEach(sender -> sender.send(message));
}
}

Map 注入

注入 Bean 名称到 Bean 实例的映射:

@Service
public class MessageService {

private final Map<String, MessageSender> senderMap;

@Autowired
public MessageService(Map<String, MessageSender> senderMap) {
this.senderMap = senderMap;
}

public void send(String type, String message) {
MessageSender sender = senderMap.get(type + "Sender");
if (sender != null) {
sender.send(message);
}
}
}

Map 的 key 是 Bean 名称,value 是 Bean 实例。

数组注入

@Autowired
private MessageSender[] senders;

使用 @Order 控制顺序

可以通过 @Order 注解控制集合中 Bean 的顺序:

@Component
@Order(1)
public class EmailSender implements MessageSender { }

@Component
@Order(2)
public class SmsSender implements MessageSender { }

数字越小优先级越高,在 List 中排在前面。

泛型注入

Spring 支持泛型类型的依赖注入,可以根据泛型参数类型匹配 Bean。

public interface Repository<T> {
T findById(Long id);
void save(T entity);
}

@Repository
public class UserRepository implements Repository<User> {
@Override
public User findById(Long id) {
return new User(id, "User-" + id);
}

@Override
public void save(User user) {
System.out.println("Saving user: " + user.getName());
}
}

@Repository
public class OrderRepository implements Repository<Order> {
@Override
public Order findById(Long id) {
return new Order(id, "Order-" + id);
}

@Override
public void save(Order entity) {
System.out.println("Saving order: " + entity.getId());
}
}

注入时 Spring 会根据泛型参数类型自动匹配:

@Service
public class UserService {

private final Repository<User> userRepository;

@Autowired
public UserService(Repository<User> userRepository) {
this.userRepository = userRepository;
}
}

@Service
public class OrderService {

private final Repository<Order> orderRepository;

@Autowired
public OrderService(Repository<Order> orderRepository) {
this.orderRepository = orderRepository;
}
}

延迟注入

使用 @Lazy 注解可以延迟 Bean 的初始化,只有在真正使用时才创建。

@Service
public class UserService {

private final EmailService emailService;

@Autowired
public UserService(@Lazy EmailService emailService) {
this.emailService = emailService;
}
}

延迟注入常用于解决循环依赖问题,或者优化启动性能。

可选依赖

对于可选的依赖,有几种处理方式:

使用 @Autowired(required = false)

@Autowired(required = false)
private OptionalService optionalService;

如果容器中没有 OptionalService,optionalService 为 null。

使用 Optional

@Autowired
private Optional<OptionalService> optionalService;

Spring 会自动将依赖包装成 Optional,不存在时为 Optional.empty()

使用 @Nullable

@Autowired
public void setOptionalService(@Nullable OptionalService optionalService) {
this.optionalService = optionalService;
}

XML 配置注入

虽然现代项目多使用注解配置,但了解 XML 配置有助于理解 Spring 的工作原理。

构造器注入

<bean id="userService" class="com.example.spring.service.UserService">
<constructor-arg ref="userDao"/>
<constructor-arg ref="emailService"/>
</bean>

<bean id="userDao" class="com.example.spring.dao.UserDaoImpl"/>
<bean id="emailService" class="com.example.spring.service.EmailServiceImpl"/>

指定参数索引:

<bean id="userService" class="com.example.spring.service.UserService">
<constructor-arg index="0" ref="userDao"/>
<constructor-arg index="1" ref="emailService"/>
</bean>

指定参数名称:

<bean id="userService" class="com.example.spring.service.UserService">
<constructor-arg name="userDao" ref="userDao"/>
<constructor-arg name="emailService" ref="emailService"/>
</bean>

Setter 注入

<bean id="userService" class="com.example.spring.service.UserService">
<property name="userDao" ref="userDao"/>
<property name="emailService" ref="emailService"/>
</bean>

注入基本类型

<bean id="dataSource" class="com.example.spring.datasource.SimpleDataSource">
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="password"/>
<property name="maxConnections" value="10"/>
</bean>

注入集合

<bean id="configService" class="com.example.spring.service.ConfigService">
<property name="servers">
<list>
<value>server1.example.com</value>
<value>server2.example.com</value>
<value>server3.example.com</value>
</list>
</property>
<property name="properties">
<map>
<entry key="timeout" value="30000"/>
<entry key="retry" value="3"/>
</map>
</property>
</bean>

小结

本章详细介绍了 Spring 的依赖注入机制:

  1. 注入方式:构造器注入(推荐)、Setter 注入(可选依赖)、字段注入(不推荐)
  2. 自动装配@Autowired@Qualifier@Primary@Resource@Inject
  3. 集合注入:List、Map、数组,使用 @Order 控制顺序
  4. 泛型注入:根据泛型参数类型自动匹配
  5. 延迟注入:使用 @Lazy 延迟初始化
  6. 可选依赖required = falseOptional@Nullable
  7. XML 配置:构造器注入、Setter 注入、基本类型和集合注入

下一章我们将学习 Bean 的详细管理,包括 Bean 的定义、作用域和生命周期。