依赖注入
依赖注入(Dependency Injection,DI)是 LCGYL Framework 的核心特性之一,它帮助你构建松耦合、易测试的应用。
版本说明
本文档基于 LCGYL Framework 2.1 版本,包含构造器注入、循环依赖检测、@Primary、@Lazy 等新特性。
什么是依赖注入?
依赖注入是一种设计模式,通过外部提供对象所需的依赖,而不是在对象内部创建依赖。
传统方式 ❌
java
public class UserService {
// 在内部创建依赖
private UserRepository repository = new UserRepository();
public User findById(Long id) {
return repository.findById(id);
}
}问题:
- 紧耦合,难以替换实现
- 难以进行单元测试
- 无法复用依赖实例
依赖注入方式 ✅
java
@Component
public class UserService {
private final UserRepository repository;
// 通过构造器注入依赖
@Inject
public UserService(UserRepository repository) {
this.repository = repository;
}
public User findById(Long id) {
return repository.findById(id);
}
}优势:
- 松耦合,易于替换实现
- 便于单元测试(可以注入 Mock 对象)
- 依赖由容器管理,自动复用
注册组件
使用 @Component 注解将类注册为可注入的组件。
java
@Component
public class UserService {
// 服务实现
}
@Component
public class UserRepository {
// 数据访问实现
}
@Component
public class EmailService {
// 邮件服务实现
}注入方式
LCGYL Framework 支持三种依赖注入方式。
1. 构造器注入(推荐)✅
最推荐的方式,保证依赖的不可变性。
java
@Component
public class UserService {
private final UserRepository repository;
private final EmailService emailService;
@Inject
public UserService(UserRepository repository, EmailService emailService) {
this.repository = repository;
this.emailService = emailService;
}
}优势:
- 依赖是 final 的,不可变
- 必需的依赖在构造时就提供
- 便于编写单元测试
- 避免循环依赖
2. Setter 注入
通过 setter 方法注入依赖。
java
@Component
public class UserService {
private UserRepository repository;
private EmailService emailService;
@Inject
public void setRepository(UserRepository repository) {
this.repository = repository;
}
@Inject
public void setEmailService(EmailService emailService) {
this.emailService = emailService;
}
}使用场景:
- 可选依赖
- 需要重新配置的依赖
- 解决循环依赖
3. 字段注入
直接在字段上注入。
java
@Component
public class UserService {
@Inject
private UserRepository repository;
@Inject
private EmailService emailService;
}注意:
- 不推荐用于生产代码
- 主要用于测试或快速原型
- 依赖不是 final 的
- 难以进行单元测试
作用域
LCGYL Framework 支持两种作用域。
单例作用域(默认)
每个组件只创建一个实例,在整个应用生命周期中复用。
java
@Component // 默认是单例
public class UserService {
}
// 或显式声明
@Component(scope = Scope.SINGLETON)
public class UserService {
}特点:
- 只创建一次
- 线程安全(需要开发者保证)
- 性能最优
适用场景:
- 无状态的服务
- 工具类
- 配置类
原型作用域
每次注入时都创建新实例。
java
@Component(scope = Scope.PROTOTYPE)
public class OrderProcessor {
private Order order;
public void process(Order order) {
this.order = order;
// 处理订单
}
}特点:
- 每次注入都创建新实例
- 可以有状态
- 内存开销较大
适用场景:
- 有状态的对象
- 需要隔离的处理器
- 临时对象
限定符
当有多个相同类型的实现时,使用限定符指定注入哪个。
定义限定符
java
@Component
@Qualifier("mysql")
public class MySQLUserRepository implements UserRepository {
}
@Component
@Qualifier("mongodb")
public class MongoDBUserRepository implements UserRepository {
}使用限定符
java
@Component
public class UserService {
private final UserRepository repository;
@Inject
public UserService(@Qualifier("mysql") UserRepository repository) {
this.repository = repository;
}
}可选依赖
使用 @Inject(required = false) 标记可选依赖。
java
@Component
public class UserService {
private final UserRepository repository;
private CacheService cacheService; // 可选
@Inject
public UserService(UserRepository repository) {
this.repository = repository;
}
@Inject(required = false)
public void setCacheService(CacheService cacheService) {
this.cacheService = cacheService;
}
public User findById(Long id) {
// 如果有缓存,先查缓存
if (cacheService != null) {
User cached = cacheService.get("user:" + id);
if (cached != null) {
return cached;
}
}
// 从数据库查询
return repository.findById(id);
}
}生命周期回调
组件可以定义生命周期回调方法。
java
@Component
public class DatabaseService {
private Connection connection;
@PostConstruct
public void init() {
// 组件创建后调用
connection = DriverManager.getConnection(url);
System.out.println("数据库连接已建立");
}
@PreDestroy
public void cleanup() {
// 组件销毁前调用
if (connection != null) {
connection.close();
}
System.out.println("数据库连接已关闭");
}
}条件注入
根据条件决定是否注入组件。
基于配置的条件
java
@Component
@ConditionalOnProperty(name = "cache.enabled", havingValue = "true")
public class RedisCacheService implements CacheService {
}基于类存在的条件
java
@Component
@ConditionalOnClass(name = "redis.clients.jedis.Jedis")
public class RedisCacheService implements CacheService {
}基于 Bean 存在的条件
java
@Component
@ConditionalOnMissingBean(CacheService.class)
public class DefaultCacheService implements CacheService {
}循环依赖
问题示例 ❌
java
@Component
public class ServiceA {
@Inject
private ServiceB serviceB;
}
@Component
public class ServiceB {
@Inject
private ServiceA serviceA;
}解决方案
1. 重构代码(推荐)✅
提取公共接口或服务。
java
@Component
public class CommonService {
// 公共逻辑
}
@Component
public class ServiceA {
@Inject
private CommonService commonService;
}
@Component
public class ServiceB {
@Inject
private CommonService commonService;
}2. 使用 Setter 注入
java
@Component
public class ServiceA {
private ServiceB serviceB;
@Inject
public void setServiceB(ServiceB serviceB) {
this.serviceB = serviceB;
}
}3. 使用 @Lazy 延迟注入
java
@Component
public class ServiceA {
@Inject
@Lazy
private ServiceB serviceB;
}最佳实践
1. 优先使用构造器注入
java
// ✅ 推荐
@Component
public class UserService {
private final UserRepository repository;
@Inject
public UserService(UserRepository repository) {
this.repository = repository;
}
}
// ❌ 不推荐
@Component
public class UserService {
@Inject
private UserRepository repository;
}2. 依赖接口而不是实现
java
// ✅ 推荐
@Component
public class UserService {
private final UserRepository repository; // 接口
@Inject
public UserService(UserRepository repository) {
this.repository = repository;
}
}
// ❌ 不推荐
@Component
public class UserService {
private final MySQLUserRepository repository; // 具体实现
@Inject
public UserService(MySQLUserRepository repository) {
this.repository = repository;
}
}3. 保持单例组件无状态
java
// ✅ 推荐:无状态
@Component
public class UserService {
private final UserRepository repository;
public User findById(Long id) {
return repository.findById(id);
}
}
// ❌ 不推荐:有状态
@Component
public class UserService {
private User currentUser; // 状态
public void setCurrentUser(User user) {
this.currentUser = user;
}
}4. 避免过度注入
java
// ❌ 不推荐:注入过多依赖
@Component
public class UserService {
@Inject private UserRepository repository;
@Inject private EmailService emailService;
@Inject private SmsService smsService;
@Inject private LogService logService;
@Inject private CacheService cacheService;
@Inject private MetricsService metricsService;
// ... 更多依赖
}
// ✅ 推荐:拆分职责
@Component
public class UserService {
@Inject private UserRepository repository;
@Inject private NotificationService notificationService; // 聚合通知服务
}
@Component
public class NotificationService {
@Inject private EmailService emailService;
@Inject private SmsService smsService;
}5. 使用 final 保证不可变
java
// ✅ 推荐
@Component
public class UserService {
private final UserRepository repository; // final
@Inject
public UserService(UserRepository repository) {
this.repository = repository;
}
}单元测试
依赖注入使单元测试变得简单。
java
public class UserServiceTest {
private UserService userService;
private UserRepository mockRepository;
@BeforeMethod
public void setUp() {
// 创建 Mock 对象
mockRepository = mock(UserRepository.class);
// 手动注入依赖
userService = new UserService(mockRepository);
}
@Test
public void testFindById() {
// Given
User user = new User(1L, "张三");
when(mockRepository.findById(1L)).thenReturn(user);
// When
User result = userService.findById(1L);
// Then
assertNotNull(result);
assertEquals(result.getName(), "张三");
verify(mockRepository).findById(1L);
}
}