有时选择太多也会让人陷入焦虑,比如突然有一段自由时间,却因为想做的事情太多,最后把时间都浪费在了摇摆不定上,静不下心做最重要的事,或者说根本不知道最重要的事情是什么。———- 《认知觉醒:开启自我改变的原动力》

1.介绍

软件工程中,依赖注入dependency injection,缩写为 DI)是一种软件设计模式,也是实现控制反转的其中一种技术。这种模式能让一个对象接收它所依赖的其他对象。“依赖”是指接收方所需的对象。“注入”是指将“依赖”传递给接收方的过程。在“注入”之后,接收方才会调用该“依赖”[1]。此模式确保了任何想要使用给定服务的对象不需要知道如何建立这些服务。取而代之的是,连接收方对象(像是 client)也不知道它存在的外部代码(注入器)提供接收方所需的服务。 —-《维基百科》

通俗的理解: 依赖注入是一种软件设计模式,用于管理不同模块之间的依赖关系。在依赖注入中,一个对象不会直接创建或者获取它所依赖的对象,而是通过外部传递来实现。这种方式使得代码更加灵活、可维护、可测试。

2.与装饰器对比

之前学习过装饰器( Python学习(九):装饰器: https://mp.weixin.qq.com/s/QkKTfCS1rQ8z-QoKkX1JCQ),咋一看和依赖注入很像,下面从多个角度整理了他们之间的区别:

2.1 作用对象

  • 依赖注入:主要用于解耦组件之间的依赖关系,使得组件之间的耦合度降低,同时提高了代码的灵活性和可测试性。它通常用于将一个组件的依赖项(如对象、函数或其他组件)注入到另一个组件中。
  • 装饰器:主要用于增强函数的功能,例如在函数执行前后执行一些额外的逻辑、修改函数的行为或返回值等。装饰器可以为函数添加一些额外的功能,而不需要修改函数本身的定义。

2.2 实现方式

  • 依赖注入:通常通过参数传递或全局注册的方式实现,例如在 FastAPI 中使用 Depends 来注入依赖项。依赖注入使得组件的依赖关系更加明确,同时也更容易进行替换和测试。

  • 装饰器: 是通过在函数定义之前添加 @ 符号和装饰器函数来实现的。装饰器可以修改函数的行为、添加额外的功能或者改变函数的返回值,而不需要修改函数的原始定义。

2.3 使用场景

  • 依赖注入: 通常用于处理组件之间的依赖关系,并且使得代码更加模块化、可测试和可维护。它适用于需要将组件的依赖项解耦的场景,例如在 Web 框架中将数据库连接、配置等注入到路由处理函数中。
  • 装饰器: 通常用于增强函数的功能,例如添加日志、缓存、权限验证等功能。它适用于需要在函数执行前后添加一些额外逻辑的场景,而不需要修改函数本身的定义。

3.使用场景

FastAPI 框架中,依赖注入是一种核心功能,它提供了多种方式来处理和注入依赖项。以下是一些常见的依赖注入场景和示例:

3.1 参数注入

在之前的学习文章中Python框架篇(2):FastApi-参数接收和验证,我们主要关注的是参数是怎么接收,它的底层本质就是使用的依赖注入,才能把参数传递给函数。

from fastapi import FastAPI

app = FastAPI()

@app.post("/items/")
async def create_item(name: str, description: str):
return {"name": name, "description": description}

3.2 路由组依赖

在之前的中间件学习中Python框架篇(5):FastApi-中间件使用: https://mp.weixin.qq.com/s/2MFPnly7pv_dhKT3zGw3VA,我们实现JWT的简单验证,这类实现是所有的接口都会进行JWT验证,有时我们只需要某些路由才进行校验,比如符合这种 /user/*才进行校验,使用依赖项可以很好的解决这类问题,使用示例如下:

a.定义依懒项

文件: app/depends/token_verify_depend.py

async def verifyToken(x_token: str = Header()):
""" token验证 """
print("x_token:", x_token)
if x_token is None:
raise HTTPException(status_code=401, detail="X-Token header missing")
# 在这里进行验证 token 的逻辑,这里简单地假设 token 为 "valid_token"
if x_token != "112334455":
raise HTTPException(status_code=403, detail="Invalid token")
return x_token

b.在路由中使用

文件: app/router/di_router.py

from fastapi import APIRouter, Depends
from app import depends

router = APIRouter(prefix="/di", tags=["依赖项学习"], dependencies=[Depends(depends.verifyToken)])

@router.get("/test")
async def test(user_id: int):
"""
依懒项学习验证测试
"""
return {"user_id": user_id}

c.验证

3.3 全局依赖

除了上面可以针对某些路由加入依赖项,也可以在全局中加入依赖项,下面是官方文档示例:

from fastapi import Depends, FastAPI, Header, HTTPException

async def verify_token(x_token: str = Header()):
if x_token != "fake-super-secret-token":
raise HTTPException(status_code=400, detail="X-Token header invalid")


async def verify_key(x_key: str = Header()):
if x_key != "fake-super-secret-key":
raise HTTPException(status_code=400, detail="X-Key header invalid")
return x_key

# 把依赖项添加至整个 FastAPI 应用
app = FastAPI(dependencies=[Depends(verify_token), Depends(verify_key)])


@app.get("/items/")
async def read_items():
return [{"item": "Portal Gun"}, {"item": "Plumbus"}]