《Python实战进阶》No17: 数据库连接与 ORM(SQLAlchemy 实战)
No17: 数据库连接与 ORM(SQLAlchemy 实战)
摘要
本文深入探讨SQLAlchemy在复杂场景下的高级应用,涵盖四大核心主题:
- 会话生命周期管理:通过事件钩子实现事务监控与审计追踪
- 混合继承映射:结合单表/连接表继承优势实现多态模型
- 高性能操作:批量插入与核心SQL构造优化大数据处理
- 异步支持:基于asyncmy的MySQL异步引擎实践
实战案例包含电商系统的分库分表实现策略与基于ORM的审计日志系统设计。扩展部分解析多租户架构的三种隔离方案及SQL注入防御的ORM层最佳实践。配套代码演示了从分片路由到异步操作的完整电商系统实现,帮助开发者掌握构建高并发、高安全性的企业级数据库应用能力。
核心概念
1. 会话生命周期管理(Session事件钩子)
通过事件钩子实现精细化的会话控制,典型场景包括事务边界管理、变更追踪和审计日志。
from sqlalchemy import event
from sqlalchemy.orm import sessionmakerSession = sessionmaker()# 事务提交前事件
@event.listens_for(Session, 'before_commit')
def before_commit(session):print("即将提交事务,当前待处理变更:", session.dirty)# 事务回滚后事件
@event.listens_for(Session, 'after_rollback')
def after_rollback(session):print("事务已回滚,清理临时状态")
2. 混合继承映射策略
结合单表继承和连接表继承的优势,实现灵活的多态查询:
from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationshipBase = declarative_base()class Product(Base):__tablename__ = 'product'id = Column(Integer, primary_key=True)type = Column(String(20)) # 单表继承标识__mapper_args__ = {'polymorphic_on': type}class Book(Product):__tablename__ = 'book'id = Column(Integer, ForeignKey('product.id'), primary_key=True)isbn = Column(String(13))__mapper_args__ = {'polymorphic_identity': 'book'}class Electronic(Product):__tablename__ = 'electronic'id = Column(Integer, ForeignKey('product.id'), primary_key=True)warranty = Column(Integer)__mapper_args__ = {'polymorphic_identity': 'electronic'}
3. 批量操作与核心SQL构造
高效处理大数据量操作的两种方式:
# ORM批量插入
session.bulk_insert_mappings(User, [{"name": "Alice", "age": 30},{"name": "Bob", "age": 25}
])# 核心SQL构造
from sqlalchemy import select, table, column
users = table('users', column('id'), column('name'), column('age')
)
stmt = users.insert().values([{'name': 'Charlie', 'age': 35},{'name': 'David', 'age': 40}
])
connection.execute(stmt)
4. 异步引擎与greenlet整合
使用asyncmy驱动实现MySQL异步操作:
from sqlalchemy.ext.asyncio import create_async_engine
import asyncioasync_engine = create_async_engine("mysql+asyncmy://user:pass@localhost/db",pool_size=5, max_overflow=10
)async def fetch_users():async with async_engine.connect() as conn:result = await conn.execute("SELECT * FROM users")return result.fetchall()asyncio.run(fetch_users())
实战案例
1. 电商系统的分表分库实现
使用ShardedSession实现用户表水平分片:
from sqlalchemy.ext.horizontal_shard import ShardedSessionshards = {'shard1': create_engine('sqlite:///./shard1.db'),'shard2': create_engine('sqlite:///./shard2.db')
}def shard_chooser(mapper, instance, clause=None):if instance:return "shard%d" % (instance.id % 2 + 1)else:return 'shard1'session = ShardedSession(shards=shards,shard_chooser=shard_chooser,id_chooser=lambda *args: list(shards.keys())
)# 自动路由到对应分片
user = User(id=101, name="Alice")
session.add(user) # 自动路由到shard2
2. 版本控制与审计日志实现
通过事件监听实现变更追踪:
from sqlalchemy import inspectaudit_logs = []@event.listens_for(Session, 'before_flush')
def track_changes(session, context, instances):for obj in session.dirty:state = inspect(obj)for attr in state.attrs:hist = attr.load_history()if hist.has_changes():audit_logs.append({'object': obj,'attribute': attr.key,'old': hist.deleted,'new': hist.added})
扩展思考
1. 多租户架构的数据库隔离方案
三种常见实现方式:
# 方案1:schema隔离
class TenantSpecificQuery(Query):def get(self, ident):return super().filter_by(tenant_id=current_tenant.id).get(ident)# 方案2:行级隔离
class BaseModel(Base):__abstract__ = Truetenant_id = Column(Integer, nullable=False)# 方案3:动态schema切换
@event.listens_for(Pool, 'connect')
def connect(dbapi_con, record):dbapi_con.execute(f"SET search_path TO tenant_{current_tenant.id}")
2. SQL注入防御的ORM层实践
安全操作示例:
# 错误方式(存在注入风险)
query = User.query.filter(f"username = '{input_username}'")# 正确方式
query = User.query.filter(User.username == input_username)# 动态查询安全构造
from sqlalchemy import text
stmt = text("SELECT * FROM users WHERE username = :username")
session.execute(stmt, {"username": input_username})
完整代码示例
包含分表分库、审计日志、异步操作的完整电商系统示例:
# 安装依赖
# pip install sqlalchemy aiosqlitefrom sqlalchemy import Column, Integer, String, Float, select
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
import asyncio# 定义模型
Base = declarative_base()class Product(Base):__tablename__ = 'products'id = Column(Integer, primary_key=True)name = Column(String(50))price = Column(Float)shard_id = Column(Integer, nullable=False)# 创建异步引擎
engines = {'shard1': create_async_engine('sqlite+aiosqlite:///./shard1.db', echo=True),'shard2': create_async_engine('sqlite+aiosqlite:///./shard2.db', echo=True)
}# 创建异步会话工厂
async_sessions = {shard_id: sessionmaker(bind=engine,class_=AsyncSession,expire_on_commit=False)for shard_id, engine in engines.items()
}# 根据 shard_id 选择分片
def get_shard_id(shard_id):return f'shard{shard_id % 2 + 1}'# 异步操作示例
async def main():# 创建表for engine in engines.values():async with engine.begin() as conn:await conn.run_sync(Base.metadata.create_all)# 插入数据product = Product(name="Laptop", price=999.99, shard_id=101)shard_id = get_shard_id(product.shard_id)async with async_sessions[shard_id]() as session:session.add(product)await session.commit()print(f"产品已添加到 {shard_id}")# 插入数据product = Product(name="手机", price=10000, shard_id=102)shard_id = get_shard_id(product.shard_id)async with async_sessions[shard_id]() as session:session.add(product)await session.commit()print(f"产品已添加到 {shard_id}")# 查询所有分片的数据all_products = []for shard_id, session_factory in async_sessions.items():async with session_factory() as session:result = await session.execute(select(Product))products = result.scalars().all()all_products.extend(products)print(f"从 {shard_id} 查询到 {len(products)} 个产品")# 显示所有产品print("\n所有产品:")for product in all_products:print(f"产品: {product.name}, 价格: {product.price}, 分片ID: {product.shard_id}")# 运行异步主函数
if __name__ == "__main__":asyncio.run(main())
输出:
d:\python_projects\jupyter_demo\sqlalchemy_demo.py:11: MovedIn20Warning: The ``declarative_base()`` function is now available as sqlalchemy.orm.declarative_base(). (deprecated since: 2.0) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9)Base = declarative_base()
2025-03-09 19:50:49,625 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2025-03-09 19:50:49,626 INFO sqlalchemy.engine.Engine PRAGMA main.table_info("products")
2025-03-09 19:50:49,626 INFO sqlalchemy.engine.Engine [raw sql] ()
2025-03-09 19:50:49,627 INFO sqlalchemy.engine.Engine PRAGMA temp.table_info("products")
2025-03-09 19:50:49,627 INFO sqlalchemy.engine.Engine [raw sql] ()
2025-03-09 19:50:49,628 INFO sqlalchemy.engine.Engine
CREATE TABLE products (id INTEGER NOT NULL,name VARCHAR(50),price FLOAT,shard_id INTEGER NOT NULL,PRIMARY KEY (id)
)
2025-03-09 19:50:49,629 INFO sqlalchemy.engine.Engine [no key 0.00072s] ()
2025-03-09 19:50:49,636 INFO sqlalchemy.engine.Engine COMMIT
2025-03-09 19:50:49,638 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2025-03-09 19:50:49,639 INFO sqlalchemy.engine.Engine PRAGMA main.table_info("products")
2025-03-09 19:50:49,639 INFO sqlalchemy.engine.Engine [raw sql] ()
2025-03-09 19:50:49,640 INFO sqlalchemy.engine.Engine PRAGMA temp.table_info("products")
2025-03-09 19:50:49,640 INFO sqlalchemy.engine.Engine [raw sql] ()
2025-03-09 19:50:49,641 INFO sqlalchemy.engine.Engine
CREATE TABLE products (id INTEGER NOT NULL,name VARCHAR(50),price FLOAT,shard_id INTEGER NOT NULL,PRIMARY KEY (id)
)
2025-03-09 19:50:49,642 INFO sqlalchemy.engine.Engine [no key 0.00067s] ()
2025-03-09 19:50:49,647 INFO sqlalchemy.engine.Engine COMMIT
2025-03-09 19:50:49,649 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2025-03-09 19:50:49,650 INFO sqlalchemy.engine.Engine INSERT INTO products (name, price, shard_id) VALUES (?, ?, ?)
2025-03-09 19:50:49,650 INFO sqlalchemy.engine.Engine [generated in 0.00035s] ('Laptop', 999.99, 101)
2025-03-09 19:50:49,652 INFO sqlalchemy.engine.Engine COMMIT
产品已添加到 shard2
2025-03-09 19:50:49,657 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2025-03-09 19:50:49,658 INFO sqlalchemy.engine.Engine INSERT INTO products (name, price, shard_id) VALUES (?, ?, ?)
2025-03-09 19:50:49,659 INFO sqlalchemy.engine.Engine [generated in 0.00077s] ('手机', 10000.0, 102)
2025-03-09 19:50:49,661 INFO sqlalchemy.engine.Engine COMMIT
产品已添加到 shard1
2025-03-09 19:50:49,665 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2025-03-09 19:50:49,666 INFO sqlalchemy.engine.Engine SELECT products.id, products.name, products.price, products.shard_id
FROM products
2025-03-09 19:50:49,665 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2025-03-09 19:50:49,666 INFO sqlalchemy.engine.Engine SELECT products.id, products.name, products.price, products.shard_id
FROM products
2025-03-09 19:50:49,666 INFO sqlalchemy.engine.Engine SELECT products.id, products.name, products.price, products.shard_id
FROM products
ducts.price, products.shard_id
FROM products
2025-03-09 19:50:49,667 INFO sqlalchemy.engine.Engine [generated in 0.00054s] ()
从 shard1 查询到 1 个产品
2025-03-09 19:50:49,668 INFO sqlalchemy.engine.Engine ROLLBACK
FROM products
2025-03-09 19:50:49,667 INFO sqlalchemy.engine.Engine [generated in 0.00054s] ()
从 shard1 查询到 1 个产品
2025-03-09 19:50:49,668 INFO sqlalchemy.engine.Engine ROLLBACK
2025-03-09 19:50:49,667 INFO sqlalchemy.engine.Engine [generated in 0.00054s] ()
从 shard1 查询到 1 个产品
2025-03-09 19:50:49,668 INFO sqlalchemy.engine.Engine ROLLBACK
2025-03-09 19:50:49,669 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2025-03-09 19:50:49,669 INFO sqlalchemy.engine.Engine SELECT products.id, products.name, pro从 shard1 查询到 1 个产品
2025-03-09 19:50:49,668 INFO sqlalchemy.engine.Engine ROLLBACK
2025-03-09 19:50:49,669 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2025-03-09 19:50:49,669 INFO sqlalchemy.engine.Engine SELECT products.id, products.name, products.price, products.shard_id
2025-03-09 19:50:49,669 INFO sqlalchemy.engine.Engine BEGIN (implicit)
2025-03-09 19:50:49,669 INFO sqlalchemy.engine.Engine SELECT products.id, products.name, products.price, products.shard_id
2025-03-09 19:50:49,669 INFO sqlalchemy.engine.Engine SELECT products.id, products.name, products.price, products.shard_id
FROM products
2025-03-09 19:50:49,670 INFO sqlalchemy.engine.Engine [generated in 0.00029s] ()
从 shard2 查询到 1 个产品
ducts.price, products.shard_id
FROM products
2025-03-09 19:50:49,670 INFO sqlalchemy.engine.Engine [generated in 0.00029s] ()
从 shard2 查询到 1 个产品
2025-03-09 19:50:49,671 INFO sqlalchemy.engine.Engine ROLLBACK
FROM products
2025-03-09 19:50:49,670 INFO sqlalchemy.engine.Engine [generated in 0.00029s] ()
从 shard2 查询到 1 个产品
2025-03-09 19:50:49,671 INFO sqlalchemy.engine.Engine ROLLBACK
从 shard2 查询到 1 个产品
2025-03-09 19:50:49,671 INFO sqlalchemy.engine.Engine ROLLBACK
2025-03-09 19:50:49,671 INFO sqlalchemy.engine.Engine ROLLBACK
所有产品:
产品: 手机, 价格: 10000.0, 分片ID: 102
所有产品:
产品: 手机, 价格: 10000.0, 分片ID: 102
产品: Laptop, 价格: 999.99, 分片ID: 101
产品: 手机, 价格: 10000.0, 分片ID: 102
产品: Laptop, 价格: 999.99, 分片ID: 101
产品: Laptop, 价格: 999.99, 分片ID: 101
通过本章学习,您应该能够:
- 熟练使用SQLAlchemy事件系统管理会话生命周期
- 实现复杂的继承映射策略
- 处理大规模数据操作的性能优化
- 构建高并发的异步数据库应用
- 设计企业级的数据库架构方案
相关文章:

《Python实战进阶》No17: 数据库连接与 ORM(SQLAlchemy 实战)
No17: 数据库连接与 ORM(SQLAlchemy 实战) 摘要 本文深入探讨SQLAlchemy在复杂场景下的高级应用,涵盖四大核心主题: 会话生命周期管理:通过事件钩子实现事务监控与审计追踪混合继承映射:结合单表/连接表继…...

工程化与框架系列(27)--前端音视频处理
前端音视频处理 🎥 引言 前端音视频处理是现代Web应用中的重要组成部分,涉及音频播放、视频处理、流媒体传输等多个方面。本文将深入探讨前端音视频处理的关键技术和最佳实践,帮助开发者构建高质量的多媒体应用。 音视频技术概述 前端音视…...

芋道打包时报错:缺失@unocss插件
在遇到打包时,报这个错误,提示构建失败是因为 ESLint 在加载 unocss 插件时,找不到 unocss/eslint-plugin 模块 解决办法:安装缺失的依赖:保证unocss/eslint-plugin已经被正确安装, 使用以下命令安装&…...

PY32MD320单片机 QFN32封装,内置多功能三相 NN 型预驱。
PY32MD320单片机是普冉半导体的一款电机专用MCU,芯片采用了高性能的 32 位 ARM Cortex-M0 内核,主要用于电机控制。PY32MD320嵌入高达 64 KB Flash 和 8 KB SRAM 存储器,最高工作频率 48 MHz。PY32MD320单片机的工作温度范围为 -40 ~ 105 ℃&…...

深入解析 configService.addListener 使用中的注意事项
在使用 Nacos 的 configService.addListener 方法进行配置监听时,为了确保程序的稳定性、可靠性以及高效性,有诸多注意事项需要我们关注。下面将对这些关键要点进行详细阐述。 一、连接稳定性 1.1 网络连接问题 Nacos 客户端与服务端通过网络进行通信&…...

Windows控制台函数:控制台读取输入函数ReadConsoleA()
目录 什么是 ReadConsoleA? 它长什么样? 怎么用它? 它跟 std::cin 有什么不一样? 注意事项 什么是 ReadConsoleA? ReadConsoleA 是一个 Windows API 函数,用来从控制台读取用户输入。想象一下&#…...

奇安信 2025 年护网蓝队初选笔试题(附答案解析)
🔥 爆款 CSDN 题库 | 超全护网蓝队笔试真题 | 含详细答案解析 🔥 熬夜为大家整理了 奇安信 2025 年护网蓝队初选笔试题,(关注我我会持续更新)涵盖 SQL 注入、Web 安全、渗透测试、二进制安全 等核心知识点,…...

国产编辑器EverEdit - Web预览设置
1 设置-高级-Web预览 1.1 设置说明 选择主菜单工具 -> 设置 -> 常规,在弹出的选项窗口中选择Web预览分类,如下图所示: 1.1.1 本地浏览HTML文件 如果用户只是在本地浏览HTML文件,即直接用浏览器打开HTML文件,确…...

P8686 [蓝桥杯 2019 省 A] 修改数组--并查集 or Set--lower_bound()的解法!!!
P8686 [蓝桥杯 2019 省 A] 修改数组--并查集 题目 并查集解析代码【并查集解】 Set 解法解析lower_bound代码 题目 并查集解析 首先先让所有的f(i)i,即每个人最开始的祖先都是自己,然后就每一次都让轮到那个数的父亲1(…...

HTML 编辑器推荐与 VS Code 使用教程
在进行 HTML 编程时,选择一款合适的 HTML 编辑器能极大地提高开发效率。以下为大家推荐几款常用且功能强大的 HTML 编辑器,同时详细介绍如何使用 VS Code 创建和预览 HTML 文件。 一、HTML 编辑器推荐 VS Code:由微软开发,是一款…...

MyBatis增删改查:静态与动态SQL语句拼接及SQL注入问题解析
MyBatis 是一个优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集的工作。本文将深入探讨 MyBatis 中的增删改查操作,重点讲解静态与动态 SQL 语句的拼接,并分析 S…...

在运维工作中,Lvs、nginx、haproxy工作原理分别是什么?
在运维工作中,LVS、NGINX和HAProxy都是常用的负载均衡和反向代理工具,它们在高可用性和负载均衡场景中发挥重要作用。以下是其原理和应用场景详解: LVS(Linux Virtual Server) 工作原理 LVS是基于Linux内核的负载均…...

linux学习(五)(服务器审查,正常运行时间负载,身份验证日志,正在运行的服务,评估可用内存)
服务器审查 在 Linux 中审查服务器的过程包括评估服务器的性能、安全性和配置,以确定需要改进的领域或任何潜在问题。审查的范围可以包括检查安全增强功能、检查日志文件、审查用户帐户、分析服务器的网络配置以及检查其软件版本。 Linux 以其稳定性和安全性而闻名…...

Java在小米SU7 Ultra汽车中的技术赋能
目录 一、智能驾驶“大脑”与实时数据 场景一:海量数据的分布式计算 场景二:实时决策的毫秒级响应 场景三:弹性扩展与容错机制 技术隐喻: 二、车载信息系统(IVI)的交互 场景一:Android Automo…...

开发环境搭建-02.后端环境搭建-熟悉项目结构
一.后端环境搭建...

js实现pdf文件路径预览和下载
预览 直接浏览器窗口打开默认就是预览 window.open(文件路径)下载 function downloadPDF(url, filename) {fetch(url).then(response > response.blob()).then(blob > {const link document.createElement(a);link.href URL.createObjectURL(blob);link.download fi…...

【RAG】基于向量检索的 RAG (BGE示例)
RAG机器人 结构体 文本向量化: 使用 BGE 模型将文档和查询编码为向量。 (BGE 是专为检索任务优化的开源 Embedding 模型,除了本文API调用,也可以通过Hugging Face 本地部署BGE 开源模型) 向量检索: 从数据库中找到与查询相关的文…...

Vue源码解析之mustache模板引擎
个人简介 👀个人主页: 前端杂货铺 🙋♂️学习方向: 主攻前端方向,正逐渐往全干发展 📃个人状态: 研发工程师,现效力于中国工业软件事业 🚀人生格言: 积跬步…...

python: DDD using postgeSQL and SQL Server
postgreSQL 注意: # psycopg 2 驱动的连接字符串 #engine create_engine(postgresql://post:geovindulocalhost:5433/TechnologyGame) #Session sessionmaker(bindengine)# 使用 psycopg3 驱动的连接字符串 #engine create_engine(postgresqlpsycopg://user:g…...

Python实例:PyMuPDF实现PDF翻译,英文翻译为中文,并按段落创建中文PDF
基于PyMuPDF与百度翻译的PDF翻译处理系统开发:中文乱码解决方案与自动化排版实践 一 、功能预览:将英文翻译为中文后创建的PDF 二、完整代码 from reportlab.lib.pagesizes import letter from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle...

IntelliJ IDEA 2021版创建springboot项目的五种方式
第一种方式,通过https://start.spring.io作为spring Initializr的url来创建项目。 第二种方式,通过https://start.spring.io官网来直接创建springboot项目压缩包,然后导入至我们的idea中。 点击generate后,即可生成压缩包…...

c#面试题整理6
1.String类能否被继承,为什么 可以看到String类的修饰符是sealed,即是密封类,故不可被继承 2.一个对象的方法是否只能由一个线程访问 不是,但是可通过同步机制,确保同一个时间只有一个线程访问 3.计算2*8ÿ…...

跟着 Lua 5.1 官方参考文档学习 Lua (12)
文章目录 5.7 – Input and Output Facilities补充内容io.input ([file])io.read ()io.write ()io.output ([file])io.lines ([filename])io.flush ()io.close ([file])io.open (filename [, mode])io.popen (prog [, mode])io.tmpfile ()io.type (ob)file:read ()file:lines (…...

大语言模型中的归一化技术:LayerNorm与RMSNorm的深入研究
在LLama等大规模Transformer架构的语言模型中,归一化模块是构建网络稳定性的关键组件。本文将系统分析归一化技术的必要性,并详细阐述为何原始Transformer架构中的LayerNorm在LLama模型中被RMSNorm所替代的技术原理。 归一化技术的基础原理 归一化的核…...

nodejs使用WebSocket实现聊天效果
在nodejs中使用WebSocket实现聊天效果(简易实现) 安装 npm i ws 实现 创建 server.js /*** 创建一个 WebSocket 服务器,监听指定端口,并处理客户端连接和消息。** param {Object} WebSocket - 引入的 WebSocket 模块,…...

【仿muduo库one thread one loop式并发服务器实现】
文章目录 一、项目介绍1-1、项目总体简介1-2、项目开发环境1-3、项目核心技术1-4、项目开发流程1-5、项目如何使用 二、框架设计2-1、功能模块划分2-1-1、SERVER模块2-1-2、协议模块 2-2、项目蓝图2-2-1、整体图2-2-2、模块关系图2-2-2-1、Connection 模块关系图2-2-2-2、Accep…...

10.2 继承与多态
文章目录 继承多态 继承 继承的作用是代码复用。派生类自动获得基类的除私有成员外的一切。基类描述一般特性,派生类提供更丰富的属性和行为。在构造派生类时,其基类构造函数先被调用,然后是派生类构造函数。在析构时顺序刚好相反。 // 基类…...

Go红队开发—格式导出
文章目录 输出功能CSV输出CSV 转 结构体结构体 转 CSV端口扫描结果使用CSV格式导出 HTML输出Sqlite输出nmap扫描 JSONmap转json结构体转jsonjson写入文件json编解码json转结构体json转mapjson转string练习:nmap扫描结果导出json格式 输出功能 在我们使用安全工具的…...

线性代数之矩阵特征值与特征向量的数值求解方法
文章目录 前言1. 幂迭代法(Power Iteration)幂法与反幂法求解矩阵特征值幂法求最大特征值编程实现补充说明 2. 逆幂迭代法(Inverse Iteration)移位反幂法 3. QR 算法(QR Algorithm)——稠密矩阵理论推导编程…...

Spring MVC源码分析のinit流程
文章目录 前言一、 init1.1、createWebApplicationContext1.2、onRefresh 二、请求处理器2.1、RequestMapping2.2、Controller接口2.3、HttpRequestHandler接口2.4、HandlerFunction 三、initHandlerMappings3.1、getDefaultStrategies3.1.1、RequestMappingHandlerMapping3.1.…...