SpringBoot3.x入门到精通系列:2.2 依赖注入与IoC容器
SpringBoot 3.x 依赖注入与IoC容器
🎯 IoC容器概述
IoC (Inversion of Control) 控制反转是Spring框架的核心特性。它将对象的创建、配置和管理交给Spring容器,而不是由对象自己控制。
核心概念
- IoC容器: 负责管理对象的生命周期
- Bean: 由Spring容器管理的对象
- 依赖注入: 容器将依赖关系注入到对象中
- 控制反转: 对象不再主动创建依赖,而是被动接收
🔧 依赖注入方式
1. 构造函数注入 (推荐)
@Service
public class UserService {private final UserRepository userRepository;private final EmailService emailService;// 构造函数注入 - 推荐方式public UserService(UserRepository userRepository, EmailService emailService) {this.userRepository = userRepository;this.emailService = emailService;}public User createUser(User user) {User savedUser = userRepository.save(user);emailService.sendWelcomeEmail(savedUser.getEmail());return savedUser;}
}
优点:
- 保证依赖不可变 (final字段)
- 保证依赖不为null
- 便于单元测试
- 避免循环依赖
2. Setter注入
@Service
public class OrderService {private PaymentService paymentService;private InventoryService inventoryService;@Autowiredpublic void setPaymentService(PaymentService paymentService) {this.paymentService = paymentService;}@Autowiredpublic void setInventoryService(InventoryService inventoryService) {this.inventoryService = inventoryService;}public Order processOrder(Order order) {inventoryService.reserveItems(order.getItems());paymentService.processPayment(order.getPayment());return order;}
}
3. 字段注入 (不推荐)
@Service
public class ProductService {@Autowiredprivate ProductRepository productRepository;@Autowiredprivate CategoryService categoryService;public Product createProduct(Product product) {// 业务逻辑return productRepository.save(product);}
}
缺点:
- 难以进行单元测试
- 违反了封装原则
- 可能导致空指针异常
📋 Bean的定义与管理
1. 使用注解定义Bean
// 组件注解
@Component
public class CommonComponent {public void doSomething() {System.out.println("Common component working...");}
}// 服务层注解
@Service
public class BusinessService {public String processData(String data) {return "Processed: " + data;}
}// 数据访问层注解
@Repository
public class DataRepository {public void saveData(String data) {System.out.println("Saving data: " + data);}
}// 控制器注解
@Controller
public class WebController {@Autowiredprivate BusinessService businessService;@RequestMapping("/process")@ResponseBodypublic String process(@RequestParam String data) {return businessService.processData(data);}
}
2. 使用@Configuration和@Bean
@Configuration
public class AppConfig {@Beanpublic DataSource dataSource() {HikariDataSource dataSource = new HikariDataSource();dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/demo");dataSource.setUsername("root");dataSource.setPassword("password");dataSource.setMaximumPoolSize(20);return dataSource;}@Beanpublic JdbcTemplate jdbcTemplate(DataSource dataSource) {return new JdbcTemplate(dataSource);}@Bean@Primary // 当有多个相同类型的Bean时,优先使用此Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {RedisTemplate<String, Object> template = new RedisTemplate<>();template.setConnectionFactory(connectionFactory);template.setKeySerializer(new StringRedisSerializer());template.setValueSerializer(new GenericJackson2JsonRedisSerializer());return template;}@Bean@Qualifier("stringRedisTemplate") // 指定Bean的名称public RedisTemplate<String, String> stringRedisTemplate(RedisConnectionFactory connectionFactory) {RedisTemplate<String, String> template = new RedisTemplate<>();template.setConnectionFactory(connectionFactory);template.setKeySerializer(new StringRedisSerializer());template.setValueSerializer(new StringRedisSerializer());return template;}
}
3. Bean的作用域
@Component
@Scope("singleton") // 单例模式 (默认)
public class SingletonBean {private int counter = 0;public int increment() {return ++counter;}
}@Component
@Scope("prototype") // 原型模式,每次获取都创建新实例
public class PrototypeBean {private final String id = UUID.randomUUID().toString();public String getId() {return id;}
}@Component
@Scope("request") // 请求作用域,每个HTTP请求创建一个实例
public class RequestScopedBean {private String requestId;@PostConstructpublic void init() {requestId = "REQ-" + System.currentTimeMillis();}public String getRequestId() {return requestId;}
}@Component
@Scope("session") // 会话作用域,每个HTTP会话创建一个实例
public class SessionScopedBean {private String sessionId;@PostConstructpublic void init() {sessionId = "SES-" + System.currentTimeMillis();}public String getSessionId() {return sessionId;}
}
🔄 Bean的生命周期
1. 生命周期回调
@Component
public class LifecycleBean implements InitializingBean, DisposableBean {private String name;public LifecycleBean() {System.out.println("1. 构造函数调用");}@PostConstructpublic void postConstruct() {System.out.println("2. @PostConstruct 方法调用");this.name = "Initialized Bean";}@Overridepublic void afterPropertiesSet() throws Exception {System.out.println("3. InitializingBean.afterPropertiesSet() 调用");}@PreDestroypublic void preDestroy() {System.out.println("4. @PreDestroy 方法调用");}@Overridepublic void destroy() throws Exception {System.out.println("5. DisposableBean.destroy() 调用");}public String getName() {return name;}
}
2. 使用@Bean的初始化和销毁方法
@Configuration
public class BeanConfig {@Bean(initMethod = "init", destroyMethod = "cleanup")public CustomService customService() {return new CustomService();}
}public class CustomService {public void init() {System.out.println("CustomService 初始化");}public void cleanup() {System.out.println("CustomService 清理资源");}public void doWork() {System.out.println("CustomService 工作中...");}
}
🎯 高级依赖注入特性
1. 条件化Bean创建
@Configuration
public class ConditionalConfig {@Bean@ConditionalOnProperty(name = "feature.email.enabled", havingValue = "true")public EmailService emailService() {return new SmtpEmailService();}@Bean@ConditionalOnMissingBean(EmailService.class)public EmailService mockEmailService() {return new MockEmailService();}@Bean@ConditionalOnClass(RedisTemplate.class)public CacheService redisCacheService(RedisTemplate<String, Object> redisTemplate) {return new RedisCacheService(redisTemplate);}@Bean@ConditionalOnMissingClass("org.springframework.data.redis.core.RedisTemplate")public CacheService memoryCacheService() {return new MemoryCacheService();}
}
2. 集合注入
// 定义多个相同接口的实现
@Component
public class EmailNotificationHandler implements NotificationHandler {@Overridepublic void handle(String message) {System.out.println("Email: " + message);}
}@Component
public class SmsNotificationHandler implements NotificationHandler {@Overridepublic void handle(String message) {System.out.println("SMS: " + message);}
}@Component
public class PushNotificationHandler implements NotificationHandler {@Overridepublic void handle(String message) {System.out.println("Push: " + message);}
}// 注入所有实现
@Service
public class NotificationService {private final List<NotificationHandler> handlers;public NotificationService(List<NotificationHandler> handlers) {this.handlers = handlers;}public void sendNotification(String message) {handlers.forEach(handler -> handler.handle(message));}
}
3. 使用@Qualifier精确注入
@Configuration
public class DatabaseConfig {@Bean@Qualifier("primaryDataSource")public DataSource primaryDataSource() {// 主数据源配置return new HikariDataSource();}@Bean@Qualifier("secondaryDataSource")public DataSource secondaryDataSource() {// 从数据源配置return new HikariDataSource();}
}@Service
public class UserService {private final DataSource primaryDataSource;private final DataSource secondaryDataSource;public UserService(@Qualifier("primaryDataSource") DataSource primaryDataSource,@Qualifier("secondaryDataSource") DataSource secondaryDataSource) {this.primaryDataSource = primaryDataSource;this.secondaryDataSource = secondaryDataSource;}public void createUser(User user) {// 使用主数据源}public List<User> findUsers() {// 使用从数据源return Collections.emptyList();}
}
4. 懒加载
@Component
@Lazy // 懒加载,只有在第一次使用时才创建
public class HeavyService {public HeavyService() {System.out.println("HeavyService 创建 - 这是一个耗时的操作");// 模拟耗时操作try {Thread.sleep(2000);} catch (InterruptedException e) {Thread.currentThread().interrupt();}}public void doHeavyWork() {System.out.println("执行重量级工作...");}
}@Service
public class BusinessService {private final HeavyService heavyService;// 即使注入了懒加载的Bean,也只有在实际使用时才会创建public BusinessService(@Lazy HeavyService heavyService) {this.heavyService = heavyService;System.out.println("BusinessService 创建完成");}public void performWork() {// 只有在这里才会真正创建HeavyServiceheavyService.doHeavyWork();}
}
🧪 单元测试中的依赖注入
1. 使用@MockBean
@SpringBootTest
class UserServiceTest {@Autowiredprivate UserService userService;@MockBeanprivate UserRepository userRepository;@MockBeanprivate EmailService emailService;@Testvoid testCreateUser() {// GivenUser user = new User("John", "john@example.com");User savedUser = new User(1L, "John", "john@example.com");when(userRepository.save(any(User.class))).thenReturn(savedUser);// WhenUser result = userService.createUser(user);// ThenassertThat(result.getId()).isEqualTo(1L);verify(emailService).sendWelcomeEmail("john@example.com");}
}
2. 使用@TestConfiguration
@TestConfiguration
public class TestConfig {@Bean@Primarypublic EmailService mockEmailService() {return Mockito.mock(EmailService.class);}
}@SpringBootTest
@Import(TestConfig.class)
class IntegrationTest {@Autowiredprivate UserService userService;@Autowiredprivate EmailService emailService; // 这里会注入Mock对象@Testvoid testUserCreation() {// 测试逻辑}
}
📊 最佳实践
1. 依赖注入最佳实践
- 优先使用构造函数注入
- 避免循环依赖
- 合理使用@Qualifier
- 谨慎使用@Lazy
2. Bean定义最佳实践
@Configuration
public class OptimalConfig {// 使用 proxyBeanMethods = false 提高性能@Configuration(proxyBeanMethods = false)static class DatabaseConfiguration {@Beanpublic DataSource dataSource(DatabaseProperties properties) {return DataSourceBuilder.create().url(properties.getUrl()).username(properties.getUsername()).password(properties.getPassword()).build();}}// 使用条件注解避免不必要的Bean创建@Bean@ConditionalOnMissingBeanpublic CacheManager cacheManager() {return new ConcurrentMapCacheManager();}
}
🔗 下一篇
在下一篇文章中,我们将学习SpringBoot的Web开发基础,包括MVC架构、请求处理、响应格式等内容。
本文关键词: 依赖注入, IoC容器, Bean管理, 构造函数注入, 生命周期, 作用域