Files
trumandu--myboot/docs/dependency-injection.md
2026-07-13 12:34:57 +08:00

15 KiB
Raw Permalink Blame History

依赖注入使用指南

MyBoot 框架提供了基于 dependency_injector 的自动依赖注入功能,让您可以轻松管理服务之间的依赖关系,无需手动获取和传递依赖。

目录

快速开始

安装依赖

确保已安装 dependency_injector

pip install dependency-injector

基本示例

from myboot.core.decorators import service

@service()
class UserService:
    """用户服务"""
    def __init__(self):
        self.users = {}

    def get_user(self, user_id: int):
        return self.users.get(user_id)

@service()
class EmailService:
    """邮件服务"""
    def send_email(self, to: str, subject: str):
        print(f"发送邮件到 {to}: {subject}")

@service()
class OrderService:
    """订单服务 - 自动注入 UserService 和 EmailService"""
    def __init__(self, user_service: UserService, email_service: EmailService):
        self.user_service = user_service
        self.email_service = email_service

    def create_order(self, user_id: int, product: str):
        user = self.user_service.get_user(user_id)
        self.email_service.send_email(user['email'], "订单创建", f"您的订单 {product} 已创建")

框架会自动:

  1. 检测 OrderService 的依赖(UserServiceEmailService
  2. 按正确的顺序初始化服务
  3. 自动注入依赖到 OrderService 的构造函数

基本用法

1. 声明依赖

通过类型注解声明依赖是最简单的方式:

@service()
class ProductService:
    def __init__(self, user_service: UserService, cache_service: CacheService):
        self.user_service = user_service
        self.cache_service = cache_service

框架会自动:

  • 从类型注解中识别依赖的服务类
  • 将类名转换为服务名(如 UserServiceuser_service
  • 自动注入对应的服务实例

2. 服务命名规则

服务名称遵循以下规则:

  • 默认命名:类名自动转换为下划线分隔的小写形式

    • UserServiceuser_service
    • EmailServiceemail_service
    • DatabaseClientdatabase_client
  • 自定义命名:通过装饰器参数指定

    @service('custom_user_service')
    class UserService:
        pass
    

3. 多级依赖

支持多级依赖,框架会自动处理依赖顺序:

@service()
class DatabaseClient:
    def __init__(self):
        self.connection = None

@service()
class UserRepository:
    def __init__(self, db: DatabaseClient):
        self.db = db

@service()
class UserService:
    def __init__(self, user_repo: UserRepository):
        self.user_repo = user_repo

依赖顺序:DatabaseClientUserRepositoryUserService

4. 可选依赖

使用 Optional 类型注解声明可选依赖:

from typing import Optional

@service()
class CacheService:
    pass

@service()
class ProductService:
    # cache_service 是可选的,如果不存在则为 None
    def __init__(self, cache_service: Optional[CacheService] = None):
        self.cache_service = cache_service
        if self.cache_service:
            # 使用缓存服务
            pass

5. Client 依赖注入

除了 Service 之间的依赖注入,框架还支持将 Client 注入到 Controller 或 Service 中:

from myboot.core.decorators import client, service, rest_controller, get

@client()
class HttpClient:
    """HTTP 客户端"""
    def request(self, url: str):
        return {"url": url}

@client(name="redis_client")  # 自定义名称
class RedisClient:
    """Redis 客户端"""
    def get(self, key: str):
        return None

@service()
class UserService:
    """注入 Client 到 Service"""
    def __init__(self, http_client: HttpClient):
        self.http_client = http_client

@rest_controller("/api")
class UserController:
    """注入 Client 和 Service 到 Controller"""
    def __init__(self, user_service: UserService, redis_client: RedisClient):
        self.user_service = user_service
        self.redis_client = redis_client

    @get("/users")
    def list_users(self):
        return []

Client 命名规则

  • 默认命名:类名自动转换为下划线形式

    • HttpClienthttp_client
    • RedisClientredis_client
  • 自定义命名:通过装饰器参数指定

    @client(name="my_redis")
    class RedisClient:
        pass
    

Client 查找方式

框架支持多种方式查找 Client 依赖:

@client(name="my_http")  # 自定义名称
class HttpClient:
    pass

@rest_controller("/api")
class MyController:
    # 以下方式都可以成功注入:

    # 方式1:按自定义名称(参数名匹配)
    def __init__(self, my_http: HttpClient):
        pass

    # 方式2:按自动转换名称
    def __init__(self, http_client: HttpClient):
        pass

    # 方式3:按类型匹配(参数名任意)
    def __init__(self, client: HttpClient):
        pass

    # 方式4:显式指定名称
    def __init__(self, x: Provide['my_http']):
        pass

6. Component 组件

@component 装饰器用于注册通用组件,支持依赖注入。它可用于任意需要托管的类(工具类、配置类、包含定时任务的类等)。

基本用法

from myboot.core.decorators import component

@component()
class EmailHelper:
    """邮件工具类"""
    def send(self, to: str, content: str):
        print(f"发送邮件到 {to}: {content}")

带依赖注入

from myboot.core.decorators import component, client

@client()
class SmtpClient:
    def send_mail(self, to: str, subject: str, body: str):
        pass

@component(name='email_helper')
class EmailHelper:
    """带依赖注入的组件"""
    def __init__(self, smtp_client: SmtpClient):
        self.smtp = smtp_client
    
    def send(self, to: str, subject: str, body: str):
        self.smtp.send_mail(to, subject, body)

包含定时任务的组件

重要:定时任务(@cron@interval@once必须@component 装饰的类中定义。这是定义定时任务的唯一方式,不再支持模块级函数或 @service 类中的定时任务。

from myboot.core.decorators import component, service, cron, interval

@service()
class DataService:
    def sync(self):
        print("同步数据...")
    
    def health_check(self):
        print("健康检查...")

@component()
class DataSyncJobs:
    """数据同步任务集合 - 自动注入 DataService"""
    
    def __init__(self, data_service: DataService):
        self.data_service = data_service
    
    @cron("0 2 * * *")  # 每天凌晨 2 点
    def sync_daily_data(self):
        """每日数据同步"""
        self.data_service.sync()
    
    @interval(hours=1)  # 每小时
    def check_data_health(self):
        """数据健康检查"""
        self.data_service.health_check()

注意

  • 定时任务方法会在组件注册时自动扫描并注册到调度器
  • 组件支持依赖注入,可以在构造函数中注入所需的服务

Component 配置选项

@component(
    name='my_component',    # 组件名称,默认使用类名的 snake_case
    scope='singleton',      # 生命周期:'singleton'(默认)或 'prototype'
    lazy=False,             # 是否懒加载
    primary=False           # 当按类型获取有多个匹配时,是否为首选
)
class MyComponent:
    pass

从容器获取组件

from myboot.core.application import app

# 方式1:通过 container 获取
email_helper = app().container.get('email_helper')

# 方式2:通过 Application 直接获取
email_helper = app().get_component('email_helper')

# 方式3:依赖注入(推荐)
@component()
class NotificationService:
    def __init__(self, email_helper: EmailHelper):
        self.email_helper = email_helper

高级特性

1. 显式指定服务名

如果服务名与类名转换规则不匹配,可以使用 Provide 类型提示:

from myboot.core.di import Provide

@service('custom_user_service')
class UserService:
    pass

@service()
class OrderService:
    def __init__(self, user_service: Provide['custom_user_service']):
        self.user_service = user_service

2. 服务生命周期

通过 scope 参数控制服务的生命周期:

# 单例模式(默认)
@service(scope='singleton')
class UserService:
    pass

# 工厂模式(每次创建新实例)
@service(scope='factory')
class TaskService:
    pass

3. 循环依赖检测

框架会自动检测循环依赖并抛出清晰的错误:

@service()
class ServiceA:
    def __init__(self, service_b: ServiceB):
        pass

@service()
class ServiceB:
    def __init__(self, service_a: ServiceA):
        pass

错误信息:

ValueError: 检测到循环依赖: service_a -> service_b -> service_a。
请重构代码以消除循环依赖。

4. 获取服务实例

在路由或其他地方获取服务实例:

from myboot.core.application import get_service

@get('/users/{user_id}')
def get_user(user_id: int):
    user_service = get_service('user_service')
    return user_service.get_user(user_id)

最佳实践

1. 使用类型注解

推荐使用类型注解声明依赖,代码更清晰:

# ✅ 推荐
@service()
class OrderService:
    def __init__(self, user_service: UserService, email_service: EmailService):
        self.user_service = user_service
        self.email_service = email_service

# ❌ 不推荐(需要手动获取)
@service()
class OrderService:
    def __init__(self):
        from myboot.core.application import get_service
        self.user_service = get_service('user_service')
        self.email_service = get_service('email_service')

2. 避免循环依赖

设计服务时避免循环依赖:

# ✅ 好的设计
@service()
class UserService:
    def __init__(self, user_repo: UserRepository):
        self.user_repo = user_repo

@service()
class OrderService:
    def __init__(self, user_service: UserService, order_repo: OrderRepository):
        self.user_service = user_service
        self.order_repo = order_repo

# ❌ 避免循环依赖
@service()
class ServiceA:
    def __init__(self, service_b: ServiceB):
        pass

@service()
class ServiceB:
    def __init__(self, service_a: ServiceA):
        pass

3. 使用接口而非具体实现

虽然 Python 没有接口,但可以通过抽象基类或协议定义接口:

from abc import ABC, abstractmethod

class IUserRepository(ABC):
    @abstractmethod
    def get_user(self, user_id: int):
        pass

@service()
class UserRepository(IUserRepository):
    def get_user(self, user_id: int):
        return {"id": user_id}

@service()
class UserService:
    def __init__(self, user_repo: IUserRepository):
        self.user_repo = user_repo

4. 合理使用可选依赖

对于非必需的依赖,使用 Optional

from typing import Optional

@service()
class ProductService:
    def __init__(
        self,
        db: DatabaseClient,  # 必需依赖
        cache: Optional[CacheService] = None  # 可选依赖
    ):
        self.db = db
        self.cache = cache

常见问题

Q1: 依赖注入失败怎么办?

如果依赖注入失败,框架会自动回退到传统方式(直接实例化)。检查日志中的错误信息:

  1. 依赖的服务未注册:确保依赖的服务已使用 @service() 装饰器
  2. 服务名不匹配:检查服务名是否正确(类名转下划线命名)
  3. 循环依赖:重构代码消除循环依赖

Q2: 如何调试依赖关系?

框架会在日志中输出依赖关系信息:

已注册服务提供者: user_service (依赖: set())
已注册服务提供者: order_service (依赖: {'user_service', 'email_service'})

Q3: 可以在运行时动态获取服务吗?

可以,使用 get_service() 函数:

from myboot.core.application import get_service

def some_function():
    user_service = get_service('user_service')
    if user_service:
        # 使用服务
        pass

Q4: 支持异步服务吗?

目前依赖注入主要支持同步服务。对于异步服务,建议在服务内部处理异步逻辑。

Q5: 如何测试带依赖的服务?

在测试中,可以手动创建服务实例并注入 mock 对象:

def test_order_service():
    # 创建 mock 依赖
    mock_user_service = MockUserService()
    mock_email_service = MockEmailService()

    # 创建服务实例
    order_service = OrderService(mock_user_service, mock_email_service)

    # 测试
    assert order_service is not None

完整示例

from myboot.core.decorators import service, get
from myboot.core.application import get_service
from typing import Optional

# 基础服务
@service()
class DatabaseClient:
    def __init__(self):
        self.connection = "connected"
        print("✅ DatabaseClient 已初始化")

@service()
class CacheService:
    def __init__(self):
        self.cache = {}
        print("✅ CacheService 已初始化")

# 仓储层
@service()
class UserRepository:
    def __init__(self, db: DatabaseClient):
        self.db = db
        print("✅ UserRepository 已初始化(依赖: DatabaseClient")

    def find_by_id(self, user_id: int):
        return {"id": user_id, "name": f"用户{user_id}"}

# 服务层
@service()
class UserService:
    def __init__(
        self,
        user_repo: UserRepository,
        cache: Optional[CacheService] = None
    ):
        self.user_repo = user_repo
        self.cache = cache
        print("✅ UserService 已初始化(依赖: UserRepository, CacheService")

    def get_user(self, user_id: int):
        # 尝试从缓存获取
        if self.cache and user_id in self.cache.cache:
            return self.cache.cache[user_id]

        # 从数据库获取
        user = self.user_repo.find_by_id(user_id)

        # 存入缓存
        if self.cache:
            self.cache.cache[user_id] = user

        return user

# 路由层
@get('/users/{user_id}')
def get_user(user_id: int):
    user_service = get_service('user_service')
    return user_service.get_user(user_id)

总结

依赖注入功能让您能够:

  • 自动管理服务依赖关系
  • 无需手动获取和传递依赖
  • 支持多级依赖和可选依赖
  • 自动检测循环依赖
  • 支持 Client 注入到 Service 和 Controller
  • 支持 Component 组件,可包含定时任务
  • 支持多种依赖查找方式(名称、类型)
  • 保持向后兼容,现有代码无需修改

开始使用依赖注入,让代码更加清晰和可维护!