此文涉及 MySQL、Kafka、Redis 组件和用户、资金、订单模块,整体从 Java 迁移到 Python 所需的知识点。

语言思维转换

Java 的语法很臃肿,如果会 Java 再看其他语言简直降维打击,学起来没什么难度,顶多是底层的一些设计、架构不一样。

Python 是动态语言,比如

# 传统 Python
def add(a, b):
    return a + b

add(1, 2)       # 结果是 3
add("a", "b")   # 结果是 "ab"

没有类型校验,没有运行前编译,看起来非常不安全。于是有了 Pydantic

def add_with_hints(a: int, b: int) -> int:
    return a + b

括号里面是参数,-> int 是返回值,用了 Type Hints,相当于一种提示,让 Pydantic 读取到后进行校验和转换。

列举一个接口的例子,比如 Java

// 1. DTO 类 (POJO)
public class CreateUserDTO {
    
    // 2. Validation (javax.validation)
    @NotNull
    @Size(min = 3, max = 50)
    private String username;
    
    @Min(18)
    private Integer age;
    
    // Getters and Setters... (由 Jackson 用于序列化)
}

// 3. Controller (Jackson + Validation)
@PostMapping("/users")
public ResponseEntity<?> createUser(@Valid @RequestBody CreateUserDTO userDTO) {
    // 到了这里,userDTO 已经被 Jackson 解析,并被 Validation 校验过了
    // ...
}

而 Python + FastAPI + Pydantic 是

from pydantic import BaseModel, Field
from typing import Optional # 对应 Java 的 Optional 或 null

# 1. Pydantic 模型 (这一个东西 = Java 的 DTO + Validation + Jackson 注解)
class CreateUserDTO(BaseModel):
    # 对应 @NotNull 和 @Size
    username: str = Field(..., min_length=3, max_length=50) 
    
    # 对应 @Min(18) 和 可选 (Integer 而非 int)
    age: Optional[int] = Field(None, ge=18) # ge = Greater than or Equal
    
    # 注意:
    # 1. ... (三个点) 表示这个字段是必填的
    # 2. Field(None, ...) 表示这个字段是可选的 (默认值为 None)


# 2. FastAPI 路由
@app.post("/users")
async def create_user(user_dto: CreateUserDTO):
    # 到了这里,Pydantic 已经自动完成了:
    # 1. 读取 JSON 请求体 (像 Jackson)
    # 2. 按照类型提示 (str, int) 校验和转换数据
    # 3. 运行校验规则 (min_length, ge) (像 javax.validation)
    # 4. 如果校验失败,自动返回一个 422 错误的 JSON 响应
    
    # 你可以直接使用这个对象
    # user_dto.username 
    
    return user_dto

Python 里面的 Optional 和 Java 里面是同一个意思,写法不一样,Python 里面把八大基本类型的包装类也用 Optional 表示。

image.png

Python 里没有 Lombok,直接.属性就行。

并发模型

这是 Java 和 Python 最大的不同,Java 是多线程,在 Spring Web 中一个请求就是一个线程(非 WebFlux),Tomcat 里面配置最大线程数就这个作用。线程内是阻塞的,比如查数据库线程就停那了,等待数据完成了继续执行。

Python 用的 asyncio 单线程事件循环。查数据库的时候,线程去执行别的,直到数据拿到了再接着执行。(具体流程后面详细分解)

比如这个接口逻辑

  • 查用户

  • 查商品

  • 写订单

  • 写订单详情

Java 是

// 线程 1
public Order createOrder(...) {
    User user = userRepo.findById(...);      // (线程 1 阻塞,等待 5ms)
    Product product = productRepo.findById(...); // (线程 1 阻塞,等待 5ms)
    
    Order order = new Order(...);
    orderRepo.save(order);                   // (线程 1 阻塞,等待 10ms)
    
    OrderDetail detail = new OrderDetail(...);
    detailRepo.save(detail);                 // (线程 1 阻塞,等待 10ms)
    
    return order; // 总耗时:5+5+10+10 = 30ms (线程 1 被独占 30ms)
}

Python 是

# 单个事件循环线程
async def create_order(...):
    # 'await':我把控制权交回事件循环
    user = await user_repo.get_by_id(...)      # (I/O 开始,任务暂停)
    
    # ... 某个未来的时刻,事件循环恢复了我的执行 ...
    
    product = await product_repo.get_by_id(...) # (I/O 开始,任务再次暂停)
    
    # ... 再次恢复 ...
    
    order = Order(...)
    await order_repo.save(order)              # (I/O 开始,任务再次暂停)
    
    # ... 再次恢复 ...
    
    detail = OrderDetail(...)
    await detail_repo.save(detail)            # (I/O 开始,任务再次暂停)
    
    # ... 再次恢复 ...
    
    return order # 总耗时:还是 30ms (I/O 总时间)

这导致的第一重大变化是 ThreadLocal 没了,之前用户登录的 token 可以用 AOP 一路带到线程里,在 Python 里面用 FastAPI 的 Depends 解决,后面想说。

第二个是 async 必须全程到底,一个方法用了,所有上下游调用都用,外部的 MySQL、Kafka 这种组件也要用 async 的版本。

那为什么 Python 是单线程的?

主要是 Python 的 RAM 内存管理用的 GLC(Global Interpreter Lock),不像 Java 有复杂的 GC 垃圾回收机制,GLC 在用到对象的引用计数 + 1,没用到 -1,为 0 则清理,为了防止多线程 RAM 内存出问题,有了 GLC。

asyncio 就是用单线程多进程(协程)的方式,提高并发。

进程、线程、协程有什么区别?

image.png

打开 Win 的任务管理器,最外层的 Micosoft Edge 就一个进程,括号里的 18 就是 18 的线程,协程一种特殊的线程,可以很快速的在不同任务之间切换,asyncio 里面这么叫。

Python + asyncio 这么搞效率岂不是很低?多核处理器怎么办?

其实 CPU 的速度是远超 IO 的,老早之前单核 CPU 就有时间切片模拟成多核,服务器大都是 IO 密集型,卡在查 MySQL、Kafka、Redis 等,这样其实更高效。

每个线程的启动都要消耗几 M 的 RAM,线程上下文切换也比较耗资源,进程之间效率高得多。

线上真实的多核处理器服务器,会用进程管理器 Gunicorn 根据服务器核心数启动多个 Python 进程,由 Gunicorn 将请求分发。

asyncio 的原理

asyncio 底层有两个组件,Ready QueueWaiting Map。Ready Queue 是可以立即执行的队列,Waiting Map 是异步等待唤醒的任务。

以为一个请求流程为例

async def create_order(...):
    # 你的代码从这里开始执行
    user_data = await request.json()  # 假设这是第一个 await (I/O)
    ...

这些代码都先当做任务放到 Ready Queue,直到遇到第一个 await 则暂停,把任务移动到 Waiting Map,然后从 Ready Queue 取下一个任务执行。直到所有的 Ready Queue 都执行完成。

一个是 Queue 一个是 Map,Queue 存的是任务,Map 中存的是任务 + 回调,这样数据回调的时候知道去执行哪个任务。

await 交给操作系统系统 OS 处理,在日常开发中比如付款场景等支付宝回调,在这里类似。

await 告诉操作系统帮我查下 MySQL,有结果了通知我。但不同的操作系统通知方式不一样,Mac UNIX 的 kqueue、Linux 的 epoll 属于Reactor 的就绪通知,Win 的 IOPC 属于Proactor 的回调

他俩区别是系统 OS 层的,涉及网卡、数据流那些,Proactor 帮忙把数据流复制了一份,Reactor 则自己去 read() write(),代码层面没有区别,asyncio 已经帮忙封装好了,根据操作系统自动调用。

重要的是理解 Java 多线程和 Python 多进程的区别,比做成工厂,Java 是有 1000 个工人的工厂,一人一个任务,但是任务很轻,工人在工作空档有大把时间摸鱼;Python 是工厂里面只有一头核动力驴,房贷车贷彩礼贷加 8 个儿子,别人摸鱼的时候,他直接干下一个任务。

OOP 与设计:从“重量级契约”到“轻量级装饰器”

Spring 的两大核心 IOC 和 AOP 在 Python + FastAPI 中也有对应。

先说 AOP,对应 Python 的装饰器 Decorators 和 FastAPI 的中间件 Middleware

例如 Java 的 @Around

@Aspect
@Component
public class LoggingAspect {
    @Around("execution(* com.example.service.*.*(..))")
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("Enter: {}.{}()", ...);
        Object result = joinPoint.proceed(); // 执行原方法
        log.info("Exit: {}.{}()", ...);
        return result;
    }
}

对应的 Python 是

import functools

# 1. 这是一个“装饰器”,它接受一个函数(func)作为参数
def log_around(func):
    
    # 2. 它返回一个新的“包装函数”(wrapper)
    @functools.wraps(func) # 这是一个好习惯,用来保留原函数元信息
    async def async_wrapper(*args, **kwargs):
        print(f"Enter: {func.__name__}()")
        result = await func(*args, **kwargs) # 3. 在内部执行原函数
        print(f"Exit: {func.__name__}()")
        return result
    
    # (如果原函数是同步的,你需要一个同步的 wrapper)
    def sync_wrapper(*args, **kwargs):
        print(f"Enter: {func.__name__}()")
        result = func(*args, **kwargs)
        print(f"Exit: {func.__name__}()")
        return result

    # 智能返回异步或同步 wrapper
    if asyncio.iscoroutinefunction(func):
        return async_wrapper
    else:
        return sync_wrapper

Python 中其他地方的使用

# 4. "贴"上装饰器
@log_around
async def get_order_by_id(order_id: int):
    print("... 正在执行核心业务: 查询订单 ...")
    order = await order_repo.get(order_id)
    return order

Python 的装饰器相当于 Spring 的 @Around,但它也有个缺点,就是不能指定范围,没有 execution 那样的东西,所以每个方法都要手动写个注解,特点是 Python 语言级别的,只要是函数就能用。

装饰器的作用就是把函数本身当成参数,放到装饰器里面执行,可以对入参、返回值等进行一系列操作。

FastAPI 也有类似的功能叫中间件,好处是可以拦截所有的 Request,缺点是只能在 Request 环境中使用,如果不是请求来的,比如系统本身触发的定时任务,就不能被执行。

列举两个常见的例子,全局日志,Spring 写法是

public class LoggingFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) {
        long startTime = System.currentTimeMillis();
        logger.info("Request: " + request.getMethod() + " " + request.getRequestURI());
        
        chain.doFilter(request, response); // <--- 在这里执行路由
        
        long processTime = System.currentTimeMillis() - startTime;
        response.addHeader("X-Process-Time", String.valueOf(processTime) + "ms");
        logger.info("Request finished in " + processTime + "ms");
    }
}

FastAPI 中间件写法是

import time
from fastapi import FastAPI, Request
import asyncio # 只是为了模拟耗时

app = FastAPI()

# 1. 使用 @app.middleware("http") 注册一个中间件
@app.middleware("http")
async def add_process_time_and_log_middleware(request: Request, call_next):
    # request: Request 对象,等同于 HttpServletRequest
    # call_next: 一个函数,等同于 FilterChain chain
    
    # --- 这是 chain.doFilter() '之前' 的代码 ---
    start_time = time.time()
    print(f"--- [Middleware IN] 收到请求: {request.method} {request.url.path} ---")

    # --- 这是执行 chain.doFilter() ---
    # await call_next(request) 会去调用下一个中间件,或者最终的路由函数
    # response 等同于 HttpServletResponse
    response = await call_next(request)

    # --- 这是 chain.doFilter() '之后' 的代码 ---
    process_time = time.time() - start_time
    # 往响应头里加东西
    response.headers["X-Process-Time"] = f"{process_time:.4f}s"
    print(f"--- [Middleware OUT] 请求处理完毕,耗时: {process_time:.4f}s ---")

    return response

# --- 你的路由 ---
@app.get("/")
async def root():
    print("... (路由函数) 正在处理核心业务 ...")
    await asyncio.sleep(0.5) # 模拟 0.5 秒的 I/O 操作
    return {"message": "Hello World"}

再来个全局异常捕获,Spring 写法是

@ControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleAllExceptions(Exception ex) {
        // ...
        return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

FastAPI 中间件写法是

from fastapi.responses import JSONResponse

@app.middleware("http")
async def global_exception_handler_middleware(request: Request, call_next):
    try:
        # 尝试执行路由 (chain.doFilter)
        response = await call_next(request)
        return response
    except Exception as e:
        # 如果 call_next() 内部 (即你的路由函数) 抛出了任何异常...
        # 就在这里捕获!
        print(f"--- [Middleware ERROR] 捕获到全局异常: {e} ---")
        
        # 不要让异常崩溃,而是返回一个标准化的 JSON 错误
        return JSONResponse(
            status_code=500,
            content={
                "error_type": type(e).__name__,
                "message": f"服务器内部发生未捕获错误: {e}",
            },
        )

# --- 一个会崩溃的路由 ---
@app.get("/crash")
async def crash_me():
    print("... (路由函数) 准备执行 1 / 0 ...")
    # 这会抛出一个 ZeroDivisionError
    a = 1 / 0
    return {"message": "你永远看不到我"}

总结一下,Java 没有 AOP,都是 Spring 提供的,还提供了 @Around @ControllerAdvice 自带的各种 Filter 封装好的工具,所以必须在 Spring 环境内才能使用。Python 本身就有装饰器,可以把函数本身当做参数处理。也可以用 FastAPI 的中间件,和 Spring 类似。

接下来对比 Spring 的 DI 和 FastAPI 的 DI。

Spring 有一个大的 IOC 容器 ApplicationContext 启动时批量扫描所有指定目录和直接的 bean,注入进来。

而 FastAPI 没有这个池子,用Depends +yield 改变代码执行顺序。表达的意思是在 Depends 的时候,先执行 Depends 里方法的代码,直到 yield 之前,返回 yield 让之前的执行完,接着执行 yield 之后的。

例如这是例子,备注是执行的顺序

async def get_db_session():
    # 第 2 步: 开始执行。
    session = await AsyncDatabaseSession()
    print("--- 上层面包: Session 创建 ---")
    
    try:
        # 第 3 步: 'yield'
        # 1. 把 session '扔' 出去
        # 2. 在这里 '冻结' (PAUSE)
        yield session
        
    except Exception:
        # (如果第 4 步出错,会跳到这里)
        await session.rollback()
        print("--- 事务回滚 ---")
    else:
        # (如果第 4 步没出错,会跳到这里)
        await session.commit()
        print("--- 事务提交 ---")
    finally:
        # 第 6 步: '解冻' (RESUME),继续执行
        await session.close()
        print("--- 下层面包: Session 关闭 ---")
@app.post("/orders")
async def create_order(
    # 第 1 步: FastAPI 看到这个依赖
    db: AsyncSession = Depends(get_db_session) 
):
    # 第 4 步: 拿到了 '扔' 出来的 session
    # 开始执行 '馅料'
    print("... 正在执行业务逻辑 ...")
    db.add(Order(...)) 
    
    # (如果这里抛出异常...)
    
    # 第 5 步: '馅料' 执行完毕
    return ...

让我做个最终的总结,属于 Python 的是 asyncio 和装饰器,装饰器可以把函数作为入参,像 AOP 一样观察和修改。

asyncio 是高性能的核心,通过 Ready Queue 和 Waiting Map 把进程转为协程,为 IO 密集型任务提效。

FastAPI 是基于 asyncio 简历的高性能 Web 框架,用中间件等同 Spring AOP,也有自己的 Depends + yield 做 DI。

本站提供的所有下载资源均来自互联网,仅提供学习交流使用,版权归原作者所有。如需商业使用,请联系原作者获得授权。 如您发现有涉嫌侵权的内容,请联系我们 邮箱:[email protected]