Skip to content

依赖注入

依赖注入(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);
    }
}

下一步

Released under the Apache License 2.0