依赖注入
依赖注入(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>
注解选择建议
| 注解 | 来源 | 匹配方式 | 特点 |
|---|---|---|---|
| @Autowired | Spring | 按类型 | 最常用,支持 required |
| @Qualifier | Spring | 按名称 | 配合 @Autowired 使用 |
| @Primary | Spring | 标记默认 | 解决多 Bean 问题 |
| @Resource | JSR-250 | 按名称 | 标准注解,可指定 name |
| @Inject | JSR-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 的依赖注入机制:
- 注入方式:构造器注入(推荐)、Setter 注入(可选依赖)、字段注入(不推荐)
- 自动装配:
@Autowired、@Qualifier、@Primary、@Resource、@Inject - 集合注入:List、Map、数组,使用
@Order控制顺序 - 泛型注入:根据泛型参数类型自动匹配
- 延迟注入:使用
@Lazy延迟初始化 - 可选依赖:
required = false、Optional、@Nullable - XML 配置:构造器注入、Setter 注入、基本类型和集合注入
下一章我们将学习 Bean 的详细管理,包括 Bean 的定义、作用域和生命周期。