feat:更新架构

This commit is contained in:
2025-12-11 17:37:08 +08:00
parent 845f225ff7
commit f7a52fc11a
18 changed files with 2117 additions and 92 deletions

View File

@@ -0,0 +1,820 @@
# 一、简介
  [FastAPI](https://fastapi.tiangolo.com/zh/) 是一个用于构建 API 的现代、快速(高性能)的 web 框架,使用 Python 并基于标准的 Python 类型提示。
  安装:
```cmd
pip install fastapi
pip install uvicorn[standard] # ASGI服务器
```
# 二、数据层
## 2.1 数据库ORM SQLAlchemy
### 2.1.1 配置与连接
```python
# database.py - 数据库配置
from sqlalchemy import create_engine, MetaData
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, Session
from typing import Generator
import os
from dotenv import load_dotenv
load_dotenv()
# 数据库配置
DATABASE_URL = f"mysql+pymysql://{os.getenv("DATABASE_URL", "root:123456@127.0.0.1:3306/test")}"
# 创建引擎
engine = create_engine(
DATABASE_URL,
echo=True, # 显示 SQL 语句(开发环境)
pool_size=20, # 连接池大小
max_overflow=40, # 最大溢出连接数
pool_pre_ping=True, # 连接前 ping
pool_recycle=3600, # 连接回收时间(秒)
)
# 创建会话工厂
SessionLocal = sessionmaker(
autocommit=False,
autoflush=False,
bind=engine,
expire_on_commit=False, # 提交后不使实例过期
)
# 声明基类
Base = declarative_base()
# 依赖注入:获取数据库会话
def get_db() -> Generator[Session, None, None]:
"""
获取数据库会话
使用 yield 确保会话正确关闭
"""
db = SessionLocal()
try:
yield db
finally:
db.close()
```
>[!WARNING]
>需要把pool_pre_ping=True打开否则会出现超过pool_recycle时间后数据库断线。
### 2.1.2 数据库基类
```python
from sqlalchemy import Column, BigInteger, String, DateTime, event
from sqlalchemy.ext.declarative import declared_attr
from config.auth import context_sub
from config.database import Base
from datetime import datetime
from utils.common import camel_to_snake
from id_generator import options, generator
# https://github.com/yitter/IdGenerator/tree/master/Python
options = options.IdGeneratorOptions(worker_id=23)
idgen = generator.DefaultIdGenerator()
idgen.set_id_generator(options)
# 第二层基类包含ID
class IdBase(Base):
__abstract__ = True
id = Column(BigInteger, primary_key=True, index=True)
@declared_attr
def __tablename__(cls):
# 自动把数据库实体类名驼峰转为数据库表名下划线
return camel_to_snake(cls.__name__)
# 自动填充id
@event.listens_for(IdBase, 'before_insert', propagate=True)
def before_insert_listener(mapper, connection, target):
if target.id is None:
target.id = idgen.next_id()
# 第二层基类包含ID和审计字段
class AuditBase(IdBase):
__abstract__ = True
create_time = Column(DateTime, nullable=True, default=datetime.now)
create_by = Column(String(255), nullable=True)
update_time = Column(DateTime, nullable=True, default=datetime.now, onupdate=datetime.now)
update_by = Column(String(255), nullable=True)
@event.listens_for(AuditBase, 'before_insert', propagate=True)
def before_insert(mapper, connection, target):
value = context_sub.get(None)
if value is not None:
target.create_by = value
@event.listens_for(AuditBase, 'before_update', propagate=True)
def before_update(mapper, connection, target):
value = context_sub.get(None)
if value is not None:
target.update_by = value
```
  id采用yitter雪花id。
  审计字段中的create_time和update_time通过SQLAlchemy参数配置。
  create_by和update_by字段为登录认证中存储的账号信息。
### 2.1.3 数据库模型
```python
from sqlalchemy import (BigInteger, Boolean, Column, Integer, DECIMAL, String, LargeBinary)
from sqlalchemy.orm import relationship
from models.base import AuditBase, IdBase
class Blog(AuditBase):
title = Column(String(255), nullable=False, comment="博客标题")
top_value = Column(Integer, nullable=False, default=0, comment="置顶值 越大越靠前")
is_great = Column(Boolean, nullable=False, default=False, comment="是否是精品")
category_id = Column(BigInteger, nullable=False, comment="博客类别")
summary = Column(String(255), nullable=False, comment="博客内容概要")
content_id = Column(BigInteger, nullable=False, comment="博客内容")
word_count = Column(Integer, nullable=False, default=0, comment="字数统计")
read_duration = Column(DECIMAL(10, 2), nullable=False, default=0.00, comment="阅读时长")
is_approved = Column(Boolean, nullable=False, default=False, comment="是否发布")
# 分类关系(多对一)
category = relationship(
"BlogCategory",
# 与BlogCategory的blogs属性建立双向关系
back_populates="blogs",
# 只级联保存和合并操作,不级联删除(删除博客不应删除分类
cascade="save-update, merge",
# 明确指定连接条件
# 如果数据库设置了外键可以省略
primaryjoin="foreign(Blog.category_id) == BlogCategory.id"
)
# 内容关系(一对一)
content = relationship(
"BlogContent",
# 与BlogContent的blog属性建立双向关系
back_populates="blog",
# 完全级联操作:保存、合并、刷新、删除等所有操作都会级联
cascade="all, delete-orphan",
# 设置为False表示一对一关系返回单个对象而不是列表
uselist=False,
# 确保内容只有一个父博客与delete-orphan配合使用
single_parent=True,
# 明确指定连接条件
primaryjoin="foreign(Blog.content_id) == BlogContent.id"
)
# 评论(一对多)
comments = relationship(
"BlogComment",
# 与BlogComment的blog属性建立双向关系
back_populates="blog",
# 完全级联操作:博客删除时自动删除所有评论
cascade="all, delete-orphan",
# 明确指定连接条件
primaryjoin="Blog.id == foreign(BlogComment.blog_id)"
)
class BlogCategory(AuditBase):
name = Column(String(45), nullable=False, comment="类别名称")
blogs = relationship(
"Blog",
# 与Blog的category属性建立双向关系
back_populates="category",
# 完全级联操作:分类删除时自动删除所有关联的博客
# 警告:这会级联删除分类下的所有博客,包括博客的内容、访问记录和评论
cascade="all, delete-orphan",
# 明确指定连接条件
primaryjoin="BlogCategory.id == foreign(Blog.category_id)"
)
class BlogContent(IdBase):
content = Column(LargeBinary, nullable=False, comment="博客内容")
blog = relationship(
"Blog",
# 与Blog的content属性建立双向关系
back_populates="content",
# 设置为False表示一对一关系
uselist=False,
# 明确指定连接条件
primaryjoin="BlogContent.id == foreign(Blog.content_id)"
)
class BlogComment(AuditBase):
blog_id = Column(BigInteger, nullable=False, comment="博客ID")
parent_id = Column(BigInteger, nullable=False, comment="父评论ID")
name = Column(String(255), nullable=False, comment="评论人昵称")
website = Column(String(255), nullable=True, comment="评论人网站")
ip_address = Column(String(45), nullable=False, comment="评论人IP")
user_agent = Column(String(255), nullable=False, comment="评论人浏览器信息")
content = Column(String(255), nullable=False, comment="评论内容")
is_approved = Column(Boolean, nullable=False, default=False, comment="是否通过")
blog = relationship(
"Blog",
# 与Blog的comments属性建立双向关系
back_populates="comments",
# 明确指定连接条件
primaryjoin="foreign(BlogComment.blog_id) == Blog.id"
)
```
  这里没有通过在数据库建立外键而是通过SQLAlchemy中的relationship来建立。
  其中primaryjoin明确指定了连接条件。如果为一对一需要设置uselist=False和single_parent=True返回单个对象而不是列表。
  cascade表示级联操作
```
# cascade 的完整可选值列表:
# 基本级联操作
"save-update" # 保存/更新时级联
"merge" # 合并会话时级联
"refresh-expire" # 刷新过期对象时级联
"expunge" # 从会话中移除时级联
"delete" # 删除时级联
"delete-orphan" # 成为孤儿时删除
# 快捷组合
"all" # 包含除 delete-orphan 外的所有操作
"all, delete-orphan" # 包含所有操作
"none" # 禁用所有级联(默认)
# 其他组合
"save-update, merge" # 常用组合
"save-update, merge, delete" # 包含删除
"save-update, merge, refresh-expire"
```
## 2.2 数据验证pydantic
```python
from typing import Optional
from pydantic import BaseModel, Field, field_validator, ConfigDict
from datetime import datetime
class BlogQuery(BaseModel):
"""博客查询参数"""
category: Optional[str] = Field(None, description="分类名称")
title: Optional[str] = Field(None, description="标题关键词")
year: Optional[int] = Field(None, description="发布年份", ge=2000, le=datetime.now().year)
@field_validator('year')
def validate_year(cls, v):
if v is not None and v > datetime.now().year:
raise ValueError('年份不能超过当前年份')
return v
class BlogBase(BaseModel):
"""博客基础模型"""
title: str = Field(..., min_length=1, max_length=255, description="博客标题")
top_value: int = Field(default=0, ge=0, description="置顶值,越大越靠前", alias="topValue")
is_great: bool = Field(default=False, description="是否是精品", alias="isGreat")
category: str = Field(..., min_length=1, max_length=45, description="分类名称")
content: Optional[str] = Field(None, description="博客内容")
is_approved: bool = Field(default=False, description="是否已发布", alias="isApproved")
@field_validator('title')
def title_not_empty(cls, v):
if not v or not v.strip():
raise ValueError('标题不能为空')
return v.strip()
@field_validator('category')
def category_not_empty(cls, v):
if not v or not v.strip():
raise ValueError('分类不能为空')
return v.strip()
class BlogCreate(BlogBase):
pass
class BlogUpdate(BlogBase):
pass
class BlogResponse(BlogBase):
id: int = Field(..., description="博客ID")
summary: Optional[str] = Field(None, description="内容摘要")
word_count: Optional[int] = Field(None, description="字数统计", alias="wordCount")
read_duration: Optional[float] = Field(None, description="阅读时长", alias="readDuration")
visit_count: Optional[int] = Field(0, description="访问次数", alias="visitCount")
create_time: datetime = Field(..., description="创建时间", alias="createTime")
update_time: datetime = Field(..., description="更新时间", alias="updateTime")
model_config = ConfigDict(
from_attributes=True,
populate_by_name=True,
json_encoders={
datetime: lambda dt: dt.strftime('%Y-%m-%d %H:%M:%S')
}
)
class BlogCategoryResponse(BaseModel):
name: str = Field(..., description="分类名称")
count: int = Field(..., description="博客数量")
model_config = ConfigDict(
from_attributes=True,
populate_by_name=True
)
class BlogStatsResponse(BaseModel):
blog_count: int = Field(..., description="博客总数", alias="blogCount")
category_count: int = Field(..., description="分类总数", alias="categoryCount")
word_count: int = Field(..., description="总字数", alias="wordCount")
class BlogVisitResponse(BaseModel):
ip: str = Field(..., description="IP地址")
os: str = Field(..., description="操作系统")
browser: str = Field(..., description="浏览器")
uri: str = Field(..., description="访问路径")
title: str = Field(None, description="博客标题")
visit_time: datetime = Field(..., description="访问时间", alias="visitTime")
model_config = ConfigDict(
from_attributes=True,
populate_by_name=True,
json_encoders={
datetime: lambda dt: dt.strftime('%Y-%m-%d %H:%M:%S')
}
)
class BlogLatestResponse(BaseModel):
id: int = Field(..., description="博客ID")
title: str = Field(..., description="博客标题")
model_config = ConfigDict(
from_attributes=True,
populate_by_name=True
)
class BlogAdjacentResponse(BaseModel):
id: int = Field(..., description="博客ID")
title: str = Field(..., description="博客标题")
model_config = ConfigDict(
from_attributes=True,
populate_by_name=True
)
class BlogCommentCreate(BaseModel):
parent_id: int = Field(default=0, ge=0, description="父评论ID0表示顶级评论", alias="parentId")
name: str = Field(..., min_length=1, max_length=50, description="评论人昵称")
website: Optional[str] = Field(None, description="评论人网站")
content: str = Field(..., min_length=1, max_length=1000, description="评论内容")
@field_validator('name')
def name_not_empty(cls, v):
if not v or not v.strip():
raise ValueError('昵称不能为空')
return v.strip()
@field_validator('content')
def content_not_empty(cls, v):
if not v or not v.strip():
raise ValueError('评论内容不能为空')
return v.strip()
class BlogCommentResponse(BlogCommentCreate):
id: int = Field(..., description="评论ID")
ip_address: str = Field(..., description="IP地址", alias="ipAddress")
user_agent: str = Field(..., description="浏览器信息", alias="userAgent")
create_time: datetime = Field(..., description="创建时间", alias="createTime")
model_config = ConfigDict(
from_attributes=True,
populate_by_name=True,
json_encoders={
datetime: lambda dt: dt.strftime('%Y-%m-%d %H:%M:%S')
}
)
```
  `@field_validator`为Pydantic V2的验证器装饰器。
  `ConfigDict`为全局配置类,`from_attributes=True`表示允许从SQLAlchemy等ORM对象创建。`populate_by_name=True`表示允许通过字段的别名alias来赋值 `json_encoders`表示自定义特定类型的 JSON 序列化方式。
# 三、服务层
## 3.1 查询操作
```python
def query_blog_by_id(db: Session, blog_id: int) -> BlogResponse:
check_blog_exist(db, blog_id)
stmt = (select(
Blog.id,
Blog.title,
Blog.top_value,
Blog.is_great,
BlogCategory.name.label("category"),
Blog.summary,
BlogContent.content,
Blog.word_count,
Blog.read_duration,
func.count(BlogVisit.id).label("visitCount"),
Blog.is_approved,
Blog.create_time,
Blog.update_time
).where(Blog.id == blog_id)
.outerjoin(BlogCategory, Blog.category_id == BlogCategory.id)
.outerjoin(BlogContent, Blog.content_id == BlogContent.id)
.outerjoin(BlogVisit, Blog.id == BlogVisit.blog_id))
blog = db.execute(stmt).first()
return BlogResponse.model_validate(blog)
```
  条件和分页查询
```python
def query_blog_by_condition(db: Session, blog_query: BlogQuery) -> List[BlogResponse]:
stmt = get_query_blog_by_condition_stmt(blog_query)
results = db.execute(stmt).fetchall()
return [BlogResponse.model_validate(result) for result in results]
def get_query_blog_by_condition_stmt(blog_query: BlogQuery):
stmt = (select(
Blog.id,
Blog.title,
Blog.top_value,
Blog.is_great,
BlogCategory.name.label("category"),
Blog.summary,
BlogContent.content,
Blog.word_count,
Blog.read_duration,
func.count(BlogVisit.id).label("visitCount"),
Blog.is_approved,
Blog.create_time,
Blog.update_time
).where(Blog.is_approved == 1)
.outerjoin(BlogCategory, Blog.category_id == BlogCategory.id)
.outerjoin(BlogContent, Blog.content_id == BlogContent.id)
.outerjoin(BlogVisit, Blog.id == BlogVisit.blog_id)
.group_by(Blog.id))
conditions = []
if blog_query.category:
conditions.append(BlogCategory.name == blog_query.category)
if blog_query.title:
conditions.append(Blog.title.like(f"%{blog_query.title}%"))
if blog_query.year:
conditions.append(func.extract('year', Blog.create_time) == blog_query.year)
if conditions:
stmt = stmt.where(and_(*conditions))
stmt = stmt.order_by(desc(Blog.is_great), desc(Blog.update_time))
return stmt
def query_blog_by_condition_page(db: Session, blog_query: BlogQuery,
current_page: int = 1, page_size: int = 10) -> PageResult[BlogResponse]:
stmt = get_query_blog_by_condition_stmt(blog_query)
return paginate_query(db, stmt, current_page, page_size)
def paginate_query(db: Session, query, current_page: int = 1, page_size: int = 10) -> PageResult:
"""通用分页查询函数"""
# 计算总记录数
total = db.execute(select(func.count()).select_from(query.subquery())).scalar_one_or_none() or 0
# 计算总页数
total_pages = (total + page_size - 1) // page_size if page_size != 0 else 0
# 执行分页查询
results = db.execute(query.offset((current_page - 1) * page_size).limit(page_size)).all()
# 转换为字典列表
records = [row._asdict() if hasattr(row, "_asdict") else dict(row) for row in results]
return PageResult(
current=current_page,
size=page_size,
total=total,
pages=total_pages,
records=records
)
```
## 3.2 新增操作
```python
def add_blog(db: Session, blog: BlogCreate) -> bool:
word_count = get_word_count(blog.content)
db_blog = Blog(
title=blog.title,
top_value=blog.top_value,
is_great=blog.is_great,
category_id=add_blog_category(db, blog.category),
summary=get_blog_summary(blog.content),
word_count=word_count,
read_duration=get_read_duration(word_count),
is_approved=blog.is_approved
)
# 级联新增
db_blog.content = BlogContent(content=blog.content.encode('utf-8'))
db.add(db_blog)
db.commit()
db.refresh(db_blog)
return True
```
  这里的`db_blog.content`可以直接赋值`BlogContent`对象实现级联新增,不用传`content_id`
>[!WARNING]
>这里的category和blog是多对一的关系且需要一定的逻辑处理不能直接赋值`BlogCategory`对象,否则每新增一条博客都会新增一个`category`
## 3.3 更新操作
```python
def update_blog(db: Session, blog_id: int, blog: BlogUpdate) -> bool:
db_blog = check_blog_exist(db, blog_id)
db_blog.title = blog.title
db_blog.top_value = blog.topValue
db_blog.is_great = blog.isGreat
db_blog.category_id = update_blog_category(db, blog.category)
# 级联更新
db_blog.content = BlogContent(content=blog.content.encode('utf-8'))
word_count = get_word_count(blog.content)
db_blog.summary = get_blog_summary(blog.content),
db_blog.word_count = word_count
db_blog.read_duration = get_read_duration(word_count)
db_blog.is_approved = blog.isApproved
db.commit()
db.refresh(db_blog)
return True
```
## 3.4 删除操作
```python
def delete_blog(db: Session, blog_id: int) -> bool:
db_blog = check_blog_exist(db, blog_id)
# 级联删除
db.delete(db_blog)
db.commit()
return True
```
# 四、路由层
```python
router = APIRouter(
prefix="/blog",
tags=["博客管理"],
responses={404: {"description": "Not found"}}
)
@router.get("/page", summary="分页查询博客", response_model=PageResult[BlogResponse])
def query_blog_by_page(
current_page: int = Query(1, ge=1, alias="currentPage", description="当前页码从1开始"),
page_size: int = Query(10, ge=1, le=100, alias="pageSize", description="每页显示数量1-100之间"),
db: Session = Depends(get_db)
):
return blog_service.query_blog_by_page(db, current_page, page_size)
@router.get("/condition", summary="条件查询博客", response_model=List[BlogResponse])
def query_blog_by_condition(query: BlogQuery = Depends(), db: Session = Depends(get_db)):
return blog_service.query_blog_by_condition(db, query)
@router.post("", summary="新增博客内容", response_model=bool)
def add_blog(
blog: BlogCreate = Body(..., description="博客创建数据"),
db: Session = Depends(get_db),
_=Depends(verify_token)
):
return blog_service.add_blog(db, blog)
@router.put("/{blog_id}", summary="更新博客内容", response_model=bool)
def update_blog(
blog_id: int = Path(..., ge=1, description="博客ID"),
blog: BlogUpdate = Body(..., description="博客更新数据"),
db: Session = Depends(get_db),
_=Depends(verify_token)
):
return blog_service.update_blog(db, blog_id, blog)
@router.delete("/{blog_id}", summary="删除博客内容", response_model=bool)
def delete_blog(
blog_id: int = Path(..., ge=1, description="博客ID"),
db: Session = Depends(get_db),
_=Depends(verify_token)
):
return blog_service.delete_blog(db, blog_id)
@router.put("/{blog_id}/comment", summary="新增博客评论", response_model=bool)
def add_blog_comment(
request: Request,
blog_id: int = Path(..., ge=1, description="博客ID"),
blog_comment: BlogCommentCreate = Body(..., description="博客评论数据"),
db: Session = Depends(get_db)
):
return blog_service.add_blog_comment(db, request, blog_id, blog_comment)
```
  路由装饰器参数中的`response_model`表示定义接口返回的数据模型。
  参数注解中的`Query()`表示参数来自URL查询字符串`Path/Query/Body`分别对应路径参数、查询参数、请求体参数。
  `Depends()`表示依赖注入,自动解析参数或执行依赖函数。
  `db: Session = Depends(get_db)`表示获取数据库连接,`_=Depends(verify_token)`表示验证用户身份。
  `request: Request`可以获取到HTTP请求的完整上下文信息。
>[!TIP]
>这里的`query: BlogQuery = Depends()`会从查询参数中自动实例化`BlogQuery`对象,并进行数据验证和类型转换。
  注册路由:
```python
from fastapi import FastAPI
from .blog import router as blog_router
def register_routers(app: FastAPI):
app.include_router(blog_router, prefix="")
# main.py
app = FastAPI(title="Blog Service")
register_routers(app)
```
# 五、中间件
## 5.1 全局异常处理器
```python
from fastapi import Request, HTTPException, status
from fastapi.responses import JSONResponse
from sqlalchemy.exc import SQLAlchemyError
from config.logging import logger
# 自定义异常类
class AppException(Exception):
def __init__(self, message: str, details=None):
self.message = message
self.details = details
# 全局异常处理中间件
async def global_exception_handler(request: Request, call_next):
try:
# 记录请求信息(可选)
logger.info(f"请求: {request.method} {request.url}")
if request.query_params:
logger.info(f"查询参数: {dict(request.query_params)}")
response = await call_next(request)
# 记录响应信息(可选)
if response.status_code >= 400:
logger.warning(f"响应: {response.status_code}")
return response
except AppException as e:
# 记录业务异常
logger.error(f"业务异常: {e.message} - 详情: {e.details}")
return JSONResponse(status_code=status.HTTP_400_BAD_REQUEST,
content={"message": e.message, "details": e.details})
except SQLAlchemyError as e:
# 记录数据库异常
logger.critical(f"数据库异常: {str(e)}")
return JSONResponse(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
content={"message": "数据库操作失败", "details": str(e)})
except Exception as e:
# 记录未知异常(带堆栈信息)
logger.critical(f"未知异常: {str(e)}")
return JSONResponse(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
content={"code": 500, "message": "服务器内部错误", "details": str(e)})
def get_credentials_exception() -> HTTPException:
return HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
```
## 5.2 日志处理器
```python
import atexit
import sys
from fluent import sender
from loguru import logger
from config.setting import settings
FLUENTD_HOST = settings.FLUENTD_HOST
FLUENTD_PORT = 24224
TOPIC_TAG = 'blog-service'
# 日志级别
LOG_LEVEL = settings.LOG_LEVEL.upper()
# 日志格式
STDOUT_FORMAT = (
"<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | "
"<level>{level: <8}</level> | "
"<cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - "
"<level>{message}</level>"
)
FILE_FORMAT = (
"{time:YYYY-MM-DD HH:mm:ss.SSS} | "
"{level: <8} | "
"{name}:{function}:{line} - {message}"
)
fluent_sender = sender.FluentSender(
tag=TOPIC_TAG,
host=FLUENTD_HOST,
port=FLUENTD_PORT,
buffer_max_size=8 * 1024 * 1024,
timeout=3.0,
retry_timeout=60
)
def log_to_fluent(message):
try:
record = message.record
# 构建结构化日志数据
log_data = {
'topic': TOPIC_TAG,
'timestamp': record['time'].timestamp(),
'level': record['level'].name.lower(),
'message': record['message'],
'source': f"{record['file'].path}:{record['line']}",
'module': record['module'],
'function': record['function'],
'process_id': record['process'].id,
'thread_id': record['thread'].id,
**record['extra']
}
if not fluent_sender.emit(TOPIC_TAG, log_data):
print(f"Fluentd 发送失败: {fluent_sender.last_error}")
except Exception as e:
print(f"日志处理异常: {str(e)}")
# 移除默认处理器
logger.remove()
# 添加控制台处理器
logger.add(
sink=sys.stdout,
level=LOG_LEVEL,
format=STDOUT_FORMAT,
colorize=True,
backtrace=True, # 显示完整异常堆栈
diagnose=True, # 显示详细异常信息
)
if settings.ENVIRONMENT == 'docker':
logger.add(
log_to_fluent,
level=LOG_LEVEL, # 处理 INFO 及以上级别
format="{message}", # 原始消息(实际使用结构化数据)
backtrace=True, # 启用堆栈回溯
diagnose=True # 显示诊断信息
)
atexit.register(fluent_sender.close)
# 导出配置好的logger
__all__ = ["logger"]
```

View File

@@ -0,0 +1,165 @@
# 一、基础概念
## 1.1 OAuth2 Password Bearer 模式​
&emsp;&emsp;用于**用户名+密码**登录,获取**access_token**。
## 1.2 FastAPI 的 OAuth2PasswordBearer
&emsp;&emsp;从请求头**Authorization**中提取TokenToken格式为 **Bearer Token**,必须是这个格式,如果不是则会提示**401 Unauthorized**错误。
# 二、核心流程
## 2.1 获取Token
```python
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="session")
@app.post("/session")
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
# 1. 验证用户名和密码
# 2. 返回 Token
return Token
```
&emsp; &emsp;这里的**tokenUrl="session"** 对应的是fastapi中的url路径@app.post("/session")用于Swagger文档中的Token认证。
&emsp; &emsp;前端必须要以FormData表单的形式传递username和password且必须是username和password字段。
```ts
const data = new FormData()
data.append('username', username)
data.append('password', password)
```
&emsp; &emsp;在获取Token前一般还需要进行验证用户名和密码是否和数据库中的信息一致。
&emsp; &emsp;可以采用JWT格式封装TokenValue
```python
def create_token(payload: dict, expires_delta: Optional[timedelta] = None):
# 复制一份
payload_copy = payload.copy()
# 加上有效时间
if expires_delta:
expire = datetime.now() + expires_delta
else:
expire = datetime.now() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
payload_copy.update({"exp": expire})
# 生成jwt Token
return jwt.encode(payload_copy, SECRET_KEY, algorithm=ALGORITHM)
```
## 2.2 访问接口
```python
@router.delete("/{blog_id}", summary="删除博客内容", response_model=bool)
def delete_blog(blog_id: int, db: Session = Depends(get_db), _ = Depends(verify_token)):
return blog_service.delete_blog(db, blog_id)
```
&emsp; &emsp;例如访问这个删除接口在参数中添加验证token的依赖
```python
async def verify_token(token: str = Depends(oauth2_scheme)):
try:
# 1. 检验token信息
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
# 2. 校验payload信息
if not verify_payload(payload):
raise get_credentials_exception()
# 3. 校验数据库中是否存在 payload中的账户信息
sub = get_payload_sub(payload)
# 4. 存储账户信息
context_sub.set(sub)
except jwt.exceptions.InvalidTokenError:
raise get_credentials_exception()
```
&emsp; &emsp;该依赖又依赖于子依赖oauth2_scheme通过调用OAuth2PasswordBearer方法从请求头Authorization中获取token值。
&emsp; &emsp;校验Token通常包含校验格式是否正确和Token包含的账户信息是否正确。
# 三、参考代码
```python
from contextvars import ContextVar
from datetime import datetime, timedelta
from typing import Optional
from fastapi import Depends
from fastapi.security import OAuth2PasswordBearer
import jwt
from passlib.context import CryptContext
from middleware.exceptions import get_credentials_exception
# 密钥和算法配置
SECRET_KEY = "sjdi@!#3ksj2780se1283"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
# 密码哈希上下文
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
# OAuth2 方案
# 设置默认登录接口
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="session")
# 请求上下文
context_sub: ContextVar[str] = ContextVar('sub')
# 获取数据库 加密密码
def get_password_hash(password: str):
return pwd_context.hash(password)
# 验证数据库密码
def verify_password(plain_password: str, hashed_password: str):
return pwd_context.verify(plain_password, hashed_password)
# 生成payload
def create_payload(sub: str) -> dict:
return {
"sub": sub
}
# 验证payload
def verify_payload(payload: dict) -> bool:
return "sub" in payload
# 从payload中获取用户
def get_payload_sub(payload: dict) -> str:
return payload["sub"]
# 创建token
def create_token(payload: dict, expires_delta: Optional[timedelta] = None):
# 复制一份
payload_copy = payload.copy()
# 加上有效时间
if expires_delta:
expire = datetime.now() + expires_delta
else:
expire = datetime.now() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
payload_copy.update({"exp": expire})
# 生成jwt Token
return jwt.encode(payload_copy, SECRET_KEY, algorithm=ALGORITHM)
# 验证token
async def verify_token(token: str = Depends(oauth2_scheme)):
try:
# 1. 检验token信息
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
# 2. 校验payload信息
if not verify_payload(payload):
raise get_credentials_exception()
# 3. 校验数据库中是否存在 payload中的账户信息
sub = get_payload_sub(payload)
# 4. 存储账户信息
context_sub.set(sub)
except jwt.exceptions.InvalidTokenError:
raise get_credentials_exception()
```
# 四、注意事项
1. 如果整个路由模块都需要Token验证可以在APIRouter中添加依赖
```python
protected_router = APIRouter(dependencies=[Depends(verify_token)])
```

View File

@@ -0,0 +1,282 @@
# 一、启动流程
![暂无图片](https://cxx0822.iepose.cn/rustfs/blog/7d7b8c086729d400a3891a20f2107fa6.png 'SpringBoot启动流程.png')
# 二、 阶段详解
## 2.1 SpringApplication初始化
**🔹 步骤1.1 - main方法入口执行**
- 执行位置:标注@SpringBootApplication的类中的main方法
- 核心动作:
1. 创建SpringApplication实例对象
2. 设置应用的基本配置信息
3. 准备启动所需的基础环境
&emsp;&emsp;main方法是整个Spring Boot应用启动的唯一起点。当Java虚拟机开始执行程序时首先会调用标注有@SpringBootApplication注解的主类中的main方法。在这个方法内部会创建SpringApplication实例并调用其run方法正式开启启动流程。
&emsp;&emsp;SpringApplication的构造方法会进行一些重要的初始化工作包括推断主配置类、设置初始引导类等。推断主配置类的过程是通过分析当前线程的堆栈信息来完成的确保能够准确找到包含main方法的那个类。
**🔹 步骤1.2 - 加载SpringFactories配置**
- 配置源META-INF/spring.factories文件
- 加载内容:
1. ApplicationContextInitializer上下文初始化器
2. ApplicationListener应用监听器
3. BeanFactoryPostProcessorBean工厂后处理器
4. AutoConfigurationImportSelector自动配置选择器
&emsp;&emsp;Spring Boot使用SpringFactoriesLoader机制从类路径下的**META-INF/spring.factories**文件中加载各种扩展组件。**这种机制是Spring Boot自动配置的核心基础它允许框架和开发者通过标准的配置文件来注册和发现各种扩展实现。**
&emsp;&emsp;配置文件中定义了多种类型的组件,包括**应用上下文初始化器、应用事件监听器、Bean工厂后处理器**等。这些组件将在后续的启动过程中按照特定的顺序被执行,共同完成应用的初始化工作。
**🔹 步骤1.3 - 推断Web应用类型**
- 检测逻辑:
1. 检查类路径是否存在Servlet相关类
2. 检查Spring MVC相关组件
3. 检查WebFlux相关组件
- 推断结果
🅰️ SERVLET传统Web应用
🅱️ REACTIVE响应式Web应用
©️ NONE非Web应用
&emsp;&emsp;应用类型推断是Spring Boot自动配置的重要环节。系统会根据项目的依赖情况自动判断应用类型这直接影响后续创建的应用上下文类型和内嵌服务器的选择。
&emsp;&emsp;**推断过程主要通过检查类路径中是否存在特定的类来完成。例如如果存在Servlet相关的类就推断为Web应用如果存在WebFlux相关的类就推断为响应式Web应用。** 这种基于类路径的自动推断机制使得开发者无需手动配置应用类型,大大简化了配置工作。
## 2.2 环境准备与配置加载
**🔹 步骤2.1 - 创建环境对象**
```
环境对象层次结构:
Environment环境接口
ConfigurableEnvironment可配置环境
具体环境实现StandardEnvironment/StandardServletEnvironment
├── PropertySources属性源列表
├── Profiles激活的配置文件
└── ConversionService类型转换服务
```
&emsp;&emsp;环境对象是Spring Boot应用运行时的配置中心它负责管理所有的配置属性和运行环境信息。根据应用类型的不同Spring Boot会创建相应的环境实例。
&emsp;&emsp;环境对象采用分层设计提供了统一的属性访问接口同时支持多种属性源的动态管理。环境对象还负责管理激活的配置文件Profile支持基于不同环境的配置隔离和切换。
**🔹 步骤2.2 - 配置属性源加载**
📊 属性源加载优先级(从高到低):
| 优先级 | 属性源类型 | 说明 |
| - | - | - |
| 1 | 命令行参数 | --spring.profiles.active=dev |
| 2 | Java系统属性 | System.getProperties() |
| 3 | 操作系统环境变量 | 系统级环境配置 |
| 4 | 应用配置文件 | application-{profile}.yml/properties |
| 5 | 默认属性 | SpringApplication.setDefaultProperties() |
&emsp;&emsp;属性源加载遵循严格的优先级顺序,确保重要的配置能够覆盖默认配置。这种优先级设计使得配置管理更加灵活,开发者可以通过不同级别的配置来调整应用行为。
**🔹 步骤2.3 - Profile处理机制**
- Profile激活方式
1. 通过spring.profiles.active显式指定
2. 通过spring.profiles.include包含其他profile
3. 默认使用default profile
- Profile解析流程
1. 读取所有可用的profile配置
2. 解析条件化配置注解
3. 合并不同profile的配置项
4. 处理配置覆盖和冲突解决
&emsp;&emsp;Profile机制是Spring框架提供的环境隔离解决方案它允许开发者为不同的运行环境定义不同的配置。Spring Boot在启动时会根据激活的Profile来加载相应的配置并处理配置之间的覆盖关系。
&emsp;&emsp;Profile的激活可以通过多种方式实现包括命令行参数、系统属性、配置文件等。系统支持同时激活多个Profile并提供了灵活的配置合并策略来处理可能出现的配置冲突。
**🔹 步骤2.4 - 配置属性绑定**
1. 属性值解析:从各种属性源中获取属性值
2. 类型转换:将字符串值转换为目标类型
3. 数据验证基于JSR-303进行数据校验
4. 对象绑定:将属性值设置到@ConfigurationProperties类
&emsp;&emsp;配置属性绑定是将外部配置值注入到Java对象中的过程。Spring Boot提供了强大的属性绑定功能支持宽松的绑定规则、类型转换、数据验证等特性。
&emsp;&emsp;绑定过程首先会从环境对象中获取属性值,然后进行类型转换,将字符串类型的配置值转换为目标属性类型。如果配置了验证规则,还会执行数据验证,确保配置值的正确性。最后,将验证通过的属性值设置到目标对象中。
## 2.3 应用上下文创建与初始化
**🔹 步骤3.1 - 创建应用上下文实例**
📋 上下文类型映射表:
| 应用类型 | 上下文实现类 | 特点描述 |
| - | - | - |
| Web应用 | AnnotationConfigServletWebServerApplicationContext | 支持Servlet容器 |
| 响应式应用 | AnnotationConfigReactiveWebServerApplicationContext | 支持响应式编程 |
| 普通应用 | AnnotationConfigApplicationContext | 基础应用上下文 |
&emsp;&emsp;应用上下文是Spring框架的核心容器负责管理Bean的生命周期和依赖关系。Spring Boot会根据应用类型创建相应类型的应用上下文实例
**🔹 步骤3.2 - 应用上下文层次结构**
```
BeanFactoryBean工厂基础接口
ApplicationContext应用上下文接口
ConfigurableApplicationContext可配置应用上下文
AbstractApplicationContext抽象实现
GenericApplicationContext/AnnotationConfigApplicationContext
```
&emsp;&emsp;应用上下文采用层次化设计,不同层次的接口和类承担不同的职责。这种设计使得上下文的功能可以逐步扩展,同时保持代码的清晰性和可维护性。
**🔹 步骤3.3 - 执行应用上下文初始化器**
- 执行时机上下文创建后Bean加载前
- 主要功能:
1. 🔧 注册自定义Bean定义
2. 🔧 设置上下文特定属性
3. 🔧 添加特殊的后处理器
4. 🔧 配置环境变量覆盖
&emsp;&emsp;应用上下文初始化器是Spring Boot提供的重要扩展点允许开发者**在上下文正式刷新之前执行自定义的初始化逻辑。** **初始化器可以通过spring.factories文件注册也可以通过SpringApplication的addInitializers方法添加。**
&emsp;&emsp;**初始化器的主要作用包括注册自定义的Bean定义、配置上下文特定的属性、添加特殊的后处理器等。** 通过初始化器,开发者可以深度定制应用上下文的行为,满足特殊的业务需求。
**🔹 步骤3.4 - 发布应用上下文事件**
事件发布序列:
1. ApplicationStartingEvent- 应用启动事件
2. ApplicationEnvironmentPreparedEvent- 环境准备完成事件
3. ApplicationContextInitializedEvent- 上下文初始化事件
4. ApplicationPreparedEvent- 应用准备事件
&emsp;&emsp;Spring Boot的启动过程采用事件驱动模型**每个关键步骤都会发布相应的事件**。这种设计使得各个模块之间解耦,同时也便于开发者通过监听事件来扩展启动逻辑。
## 2.4 Bean定义加载与处理
**🔹 步骤4.1 - Bean定义加载方式**
多种Bean定义加载途径
1. 🔍 组件扫描:自动扫描@Component@Service等注解
2. 📝 @Bean方法:处理@Configuration类中的@Bean方法
3. 📂 @Import导入:导入其他配置类
4. 🔗 ImportSelector动态选择导入的配置类
&emsp;&emsp;Bean定义加载是Spring容器初始化的核心环节。Spring Boot支持多种Bean定义加载方式每种方式都有其适用的场景和特点。
**🔹 步骤4.2 - 组件扫描详细过程**
```
组件扫描流程:
开始扫描指定包路径
读取包下的所有class文件
解析类上的注解信息
识别Spring组件注解
注册Bean定义到容器
完成组件扫描
```
&emsp;&emsp;组件扫描是Spring Boot自动配置的基础机制它能够自动发现和注册项目中的Spring组件。扫描过程基于注解元数据通过反射机制分析类的结构信息。
**🔹 步骤4.3 - Bean工厂后处理**
| 后处理器 | 功能描述 | 执行时机 |
| - | - | - |
| ConfigurationClassPostProcessor | 处理@Configuration类 | Bean定义加载后 |
| PropertySourcesPlaceholderConfigurer | 处理属性占位符 | 属性解析阶段 |
| CustomScopeConfigurer | 注册自定义作用域 | 作用域配置阶段 |
&emsp;&emsp;BeanFactoryPostProcessor是Spring框架的重要扩展点允许在Bean实例化之前修改Bean定义信息。Spring Boot在启动过程中会执行多个内置的BeanFactoryPostProcessor。
## 2.5 Bean实例化与生命周期
**🔹 步骤5.1 - Bean实例化策略**
实例化顺序规则:
1. 🥇 BeanFactoryPostProcessor- 工厂后处理器最先实例化
2. 🥈 BeanPostProcessor- Bean后处理器其次实例化
3. 🥉 单例Bean- 按依赖顺序实例化普通Bean
4. 🏅 其他作用域Bean- 按需实例化
&emsp;&emsp;Bean实例化遵循特定的顺序规则确保依赖关系正确的Bean能够按正确的顺序创建。实例化过程采用懒加载和急切实例化相结合的策略。
**🔹 步骤5.2 - 依赖注入机制**
依赖注入的三种方式:
1. 🏗️ 构造器注入- 通过构造函数注入依赖
2. 🛠️ Setter注入- 通过setter方法注入依赖
3. 🎯 字段注入- 直接在字段上使用@Autowired注入
&emsp;&emsp;依赖注入是Spring框架的核心特性它通过自动装配机制将Bean之间的依赖关系解耦。Spring支持多种依赖注入方式每种方式都有其适用的场景。
**🔹 步骤5.3 - Bean后处理流程**
```
Bean后处理序列
Bean实例化
执行BeanPostProcessor.postProcessBeforeInitialization
执行@PostConstruct方法
执行InitializingBean.afterPropertiesSet
执行自定义init方法
执行BeanPostProcessor.postProcessAfterInitialization
Bean完全就绪
```
&emsp;&emsp;BeanPostProcessor是Bean生命周期管理的重要扩展点它允许在Bean初始化前后执行自定义逻辑。Spring Boot内置了多个BeanPostProcessor用于处理各种注解和AOP代理。
**🔹 步骤5.4 - 循环依赖解决机制**
三级缓存解决方案:
1. 一级缓存存放完全初始化完成的Bean
2. 二级缓存存放早期暴露的Bean已实例化但未初始化
3. 三级缓存存放Bean工厂用于创建Bean的早期引用
&emsp;&emsp;循环依赖是Spring容器需要解决的重要问题。Spring通过三级缓存机制来解决单例Bean的循环依赖问题确保即使存在循环引用也能正确完成依赖注入。
## 2.6 Web服务器启动与配置
**🔹 步骤6.1 - 内嵌服务器选择策略**
服务器自动配置逻辑:
1. 检查类路径中的服务器依赖
2. 按优先级选择Tomcat > Jetty > Undertow
3. 根据应用类型创建对应的Web服务器工厂
4. 配置服务器参数(端口、上下文路径等)
&emsp;&emsp;Spring Boot支持多种内嵌服务器包括Tomcat、Jetty和Undertow。服务器选择基于类路径中的依赖采用特定的优先级规则。
**🔹 步骤6.2 - Servlet容器初始化**
1. 🎯 创建ServletContextServlet上下文
2. 🎯 注册DispatcherServlet前端控制器
3. 🎯 配置字符编码过滤器
4. 🎯 设置会话管理配置
5. 🎯 启用静态资源服务
&emsp;&emsp;Servlet容器初始化是Web应用启动的关键环节。Spring Boot会自动配置Servlet容器并注册必要的Servlet、Filter和Listener。
**🔹 步骤6.3 - MVC组件自动配置**
自动配置的MVC组件
1. HandlerMapping- 请求映射处理器
2. HandlerAdapter- 处理器适配器
3. ViewResolver- 视图解析器
4. MessageConverter- 消息转换器
5. Interceptor- 拦截器配置
&emsp;&emsp;Spring Boot为Spring MVC提供了完整的自动配置包括处理器映射、视图解析、消息转换等组件。这些组件基于约定大于配置的原则提供了合理的默认值。
## 2.7 启动完成与后处理
**🔹 步骤7.1 - 启动事件发布序列**
```
ApplicationStartingEvent
ApplicationEnvironmentPreparedEvent
ApplicationContextInitializedEvent
ApplicationPreparedEvent
ContextRefreshedEvent
ApplicationReadyEvent
```
&emsp;&emsp;当所有的Bean实例化、依赖注入和初始化回调都执行完成后Spring容器会发布ContextRefreshedEvent事件。这个事件标志着Spring IoC容器已经完全刷新并准备就绪。
**🔹 步骤7.2 - 命令行运行器执行**
运行器类型与特点:
| 运行器接口 | 执行方法 | 参数类型 | 使用场景 |
| - | - | - | - |
| ApplicationRunner | run(ApplicationArguments) | 封装的应用参数 | 需要丰富参数信息时 |
| CommandLineRunner | run(String... args) | 原始字符串参数 | 简单参数处理时 |
&emsp;&emsp;Spring Boot会执行所有实现了ApplicationRunner或CommandLineRunner接口的Bean。
**🔹 步骤7.3 - 健康检查与指标收集**
启动后的监控机制:
1. ❤️ 健康检查通过HealthIndicator监控应用健康状态
2. 📊 应用指标通过Micrometer收集运行时指标
3. 🔔 事件监听:监控应用生命周期事件
4. 📝 日志记录:记录启动完成状态和耗时

View File

@@ -0,0 +1,72 @@
# 一、原生镜像
&emsp;&emsp;传统 Java 应用基于 JVM 运行,需要加载完整的类库和 JVM 运行时环境,导致启动时间长、内存占用高。而**原生镜像技术通过提前编译AOT将 Java 应用直接编译为本地机器码,无需 JVM 即可运行**,具有以下核心优势:
1. 启动速度极快:毫秒级启动,相比传统 JVM 应用提升 10-100 倍、
2. 内存占用低:仅包含应用运行所需的最小资源集合
3. 部署体积小:通常为传统 JAR 包的 1/10 左右
4. 适合云原生场景:尤其适合 Kubernetes 等容器化环境
# 二、安装使用
## 2.1 基础软件
1. GraalVM类似于JAVA JDK下载[官网](https://www.graalvm.org/downloads/),下载完成后需要配置环境变量
2. native-image安装cmd运行gu install native-image
3. Visual Studio Community2022 选择使用C++的桌面开发
## 2.2 结合Springboot3 项目
1. IDEA创建Springboot3项目
2. Project Structure选择GraalVM JDK
3. 配置pom打包
```xml
<plugins>
<!-- 将项目打包成可执行的 JAR/WAR 文件 -->
<!-- repackage目标会替换 Maven 默认的打包行为 -->
<!-- 使 JAR/WAR 文件包含所有依赖项,成为一个独立运行的应用 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
<!-- 执行 AOT 编译 -->
<goal>process-aot</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- GraalVM Native Maven 插件 -->
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<version>0.10.2</version>
<configuration>
<buildArgs combine.children="append">
<!-- 支持 http 协议 -->
<buildArg>--enable-url-protocols=http</buildArg>
<!-- 用于解决数据库中文乱码问题 -->
<buildArg>-H:+AddAllCharsets</buildArg>
</buildArgs>
</configuration>
<executions>
<execution>
<id>build-native</id>
<goals>
<goal>compile-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
```
4. 选择Maven->Lifecycle->clean和package或者输入命令mvn -Pnative native:compile
5. 在target目录下可以看到生成的.exe文件打开运行即可。
## 三、注意事项
&emsp;&emsp;实际体验下来发现主要存在以下几个问题
1. 编译打包的速度非常慢
2. 目前有些第三方库并不支持Mybatis已支持Mybatis Plus暂不支持
# 四、参考资料
1. [使用 Spring Boot 和 GraalVM 构建原生镜像](https://springdoc.cn/spring-native-intro/)
2. [GraalVM 原生镜像支持](https://docs.springframework.org.cn/spring-boot/reference/native-image/introducing-graalvm-native-images.html)

View File

@@ -0,0 +1,60 @@
# 一、Bean概念
## 1.1 定义
&emsp;&emsp;Spring bean是Spring框架在运行时管理的对象Bean是一个由Spring IoC容器实例化、组装和管理的对象。
&emsp;&emsp;一个 Bean 可以是任何普通的 Java 对象,例如 POJO、Service、Respository、Controller 等等。将一个类声明为 Bean 的方式可以是在类级别上使用 `@Component` 注解或其派生注解(`@Service``@Repository``@Controller`等),也可以是通过配置文件进行显式的声明。
## 1.2 实例化
&emsp;&emsp;Spring 容器负责实例化 Bean。当应用程序启动时Spring 容器会根据配置信息或注解扫描的结果,找到并实例化所有被标记为 Bean 的类,并将它们加入容器中。实例化的过程由 Spring 的 IoC 容器负责。
## 1.3 管理
&emsp;&emsp;一旦 Bean 被实例化Spring 容器将负责管理 Bean 的生命周期和依赖关系。它会根据配置文件或注解的信息,自动解决 Bean 之间的依赖关系确保在需要的时候正确的注入依赖。Spring 容器还会负责销毁不再需要的 Bean。
## 1.4 依赖注入
&emsp;&emsp;依赖注入是 Spring 框架的一个重要特性,它允许通过自动或显式配置的方式将 Bean 的依赖项注入到其它 Bean 中。依赖注入可以通过构造函数注入、Setter 方法注入或字段注入的方式实现,其中最常见的是使用 `@Autowired`注解进行注入。
## 1.5 作用域
&emsp;&emsp;Spring 框架提供了多种作用域scope来管理 Bean 的生命周期。常见的作用域包括单例Singleton、原型Prototype、会话Session、请求Request等。默认情况下Bean 是单例的,即每个容器中只存在一个实例。但可以根据需要配置其它作用域。
## 1.6 自动装配
&emsp;&emsp;Spring Boot 支持自动装配Auto - wiring它能够根据类型或名称自动解析和注入依赖关系。通过在需要注入的字段、构造函数或 Setter 方法上使用 `@Autowired` 注解Spring 容器会自动查找并注入对应的 Bean。
# 二、Bean生命周期
![暂无图片](https://cxx0822.iepose.cn/rustfs/blog/b30628a6b1fa3cfd250cc5efff612a68.jpg 'Bean生命周期.jpg')
## 2.1 实例化
&emsp;&emsp;指创建对象的过程,也就是说,当 Spring 容器启动时,它会扫描所有的配置文件,读取 Bean 定义,然后根据定义创建 Bean 对象的实例。这个过程中Spring 容器会使用反射机制来调用 Bean 的构造函数来实例化对象。
## 2.2 属性赋值
&emsp;&emsp;对 Bean 的实例化对象进行属性填充;
## 2.3 初始化
&emsp;&emsp;指 Bean 对象被创建后Spring 容器会根据配置文件中的定义来对其进行一系列的属性设置、依赖注入等操作使其可以正常运行。这个过程中Spring 容器会调用 Bean 的一些特定方法,如 set 方法、init-method 方法等来完成对象的初始化。
## 2.4 销毁
&emsp;&emsp;执行一些预定义的销毁方法;
# 三、Bean注解——@Component
## 基本概念
&emsp;&emsp;@Component 是 Spring 框架中的一个注解,用于将类标记为 Spring 管理的组件。当一个类被 @Component 注解修饰时Spring 会在启动时自动扫描并将该类实例化为 Bean放入应用程序的上下文ApplicationContext以便在其他地方可以通过依赖注入使用。
## 主要作用
1. 组件扫描Spring 通过组件扫描机制自动发现并注册被 @Component 注解的类。
2. Bean 定义:被注解的类会被视为 Spring Bean由 Spring 容器管理其生命周期。
3. 依赖注入:其他组件可以通过注解(如 @Autowired)注入这些 Bean。
## 其他衍生注解
| 注解 | 所属层 | 继承关系 | 适用场景 |
|------------|------------|-------------|----------|
| @Component | 通用组件 | 基础注解 | 不适合特定层的组件(如工具类、配置类) |
| @Repository | 数据访问层 (DAO) | 继承 @Component | 数据库操作类、ORM 映射类(如 JPA Repository |
| @Service | 服务层 (Service) | 继承 @Component | 业务逻辑处理(如订单处理、用户认证) |
| @Controller | 控制层 (MVC/Web) | 继承 @Component | 处理 HTTP 请求的控制器(如 REST API、Web 页面控制器) |
| @RestController | 控制层 (REST API) | 继承 @Controller | RESTful API 接口开发 |
| 注解 | 功能描述 | Spring 自动处理特性 |
|------------------|----------------------|-------------------|
| @Component | 标记类为 Spring Bean通过组件扫描自动注册。<br>无特定语义,是其他组件注解的基础。 | - Bean 生命周期管理<br>- 依赖注入支持 |
| @Repository | 标记数据访问组件(如数据库操作类),<br>自动处理数据访问异常。 | - 异常转换<br>- DAO 组件扫描 |
| @Service | 标记业务逻辑组件,负责处理业务流程、事务管理等。<br>提供语义化,明确该类在架构中的角色。 | - 与 @Transactional 注解默认集成<br>- IDE 识别为服务层组件 |
| @Controller | 标记 Web 控制器,处理 HTTP 请求。<br>与 Spring MVC 框架紧密集成。 | - 请求映射自动注册<br>- 支持 @RequestMapping@GetMapping 等注解<br>- 视图解析(返回视图名称或 JSON |
| @RestController | @Controller + @ResponseBody 的组合,专门用于 RESTful API直接返回 JSON/XML 等数据。 | - 所有方法默认返回 JSON/XML无需每个方法添加 @ResponseBody |

View File

@@ -0,0 +1,105 @@
# 一、简介
&emsp;&emsp;Spring Boot Starter是一组预定义的依赖项集合旨在简化Maven或Gradle等构建工具中的依赖管理。每个Starter都包含了实现特定功能所需的库和组件以及相应的配置文件。开发者只需在项目中引入相应的Starter依赖即可快速搭建起具备该功能的项目骨架。
&emsp;&emsp;Starter=依赖+自动配置+配置文件
# 二、实现原理
## 2.1 传统实现
&emsp;&emsp;例如引入Spring中的jpa则需要以下步骤
1. 在Maven中引入数据库依赖
2. 在Maven中引入jpa依赖
3. 在配置文件中配置属性
4. 调试程序
&emsp;&emsp;每次新建项目都需要重复此流程,操作繁琐。
## 2.2 自定义Starter实现
1. 新建Maven项目在pom.xml文件中定义需要的依赖项。
2. 创建自动配置类 AutoConfigurationTest添加@configuration注解使其能够被SpringBoot自动扫描到。
3. 添加自动装配机制在src/main/resources/META-INF文件夹下创建spring.factories文件添加以下配置
```bash
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.springbootstartercustom.AutoConfigurationTest
```
&emsp;&emsp;这里文件夹和文件名一定要正确因此SpringFactoriesLoader中就是这么定义的。
4. 在配置文件中自定义属性(可选)。
5. 安装打包到maven仓库中。
6. 其他项目通过pom.xml文件引入该starter。
## 2.3 Starter实现原理
&emsp;&emsp;加载依赖->扫描自动配置类->加载配置文件
# 三、高级特性
## 3.1 可插拔Starter
&emsp;&emsp;所谓可插拔就是可以自行决定是否需要加载该starter的功能。例如可以通过注解的方式决定是否加载。
1. 定义注解
```java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EnableAutoConfigTest {
}
```
2. 在自动配置类 AutoConfiguration中增加条件注解
```java
@Configuration
@ConditionalOnBean(annotation = EnableAutoConfigTest.class)
public class AutoConfigurationTest {
}
```
3. 在相应位置添加@EnableAutoConfigTest注解该stater才会生效
## 3.2 自定义配置文件
&emsp;&emsp;所谓自定义配置文件就是可以在引入stater后可以通过修改配置文件覆盖原来的配置属性从而灵活配置stater功能。
1. 引用spring-boot-configuration-processor
```xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
```
2. 定义Properties配置类
```java
@ConfigurationProperties(prefix = "test")
public class TestProperties {
private String name = "test";
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
```
3. 在自动配置类 AutoConfigurationTest中引用
```java
@Configuration(proxyBeanMethods = false)
@Import({TestProperties.class})
public class AutoConfigurationTest {
@Resource
private TestProperties testProperties;
}
```
4. 在引入的stater工程中修改配置文件
```yaml
test:
name: test1
```

View File

@@ -0,0 +1,543 @@
# 一、IOC
&emsp;&emsp;Spring框架的核心是IOC控制反转容器它负责管理应用程序中的对象称为Bean的创建、配置和组装。
# 二、Bean
## 2.1 定义
&emsp;&emsp;在Spring中Bean是指由Spring IOC容器管理的对象实例。这些对象不再由应用程序直接创建而是由容器负责实例化、配置和组装。
&emsp;&emsp;例如@Service@Controller就是特殊的Bean
## 2.2 特点
- 由容器管理生命周期由Spring容器控制
- 可配置性通过配置元数据定义Bean的行为
- 依赖注入容器自动处理Bean之间的依赖关系
- 作用域支持支持singleton、prototype等不同作用域
## 2.3 创建方式
```java
// 1. 通过@Component注解
@Component
public class UserService {
// ...
}
// 2. 通过@Bean注解配置类中
@Configuration
public class AppConfig {
@Bean
public DataSource dataSource() {
return new DataSource();
}
}
// 3. 通过XML配置
<bean id="userService" class="com.example.UserService"/>
```
## 2.4 作用域
&emsp;&emsp;控制 Bean 在容器中的实例数量和生命周期。Spring 提供多种作用域,默认是 singleton。通过 @Scope 注解指定,如 @Scope("prototype")。
- singleton单例容器中只有一个实例所有请求共享该实例默认值
- prototype多例每次请求如 getBean())都会创建新实例,容器不管理其销毁。
- requestWeb每个 HTTP 请求创建一个实例,仅在当前请求有效。
- sessionWeb每个会话创建一个实例在会话有效期内有效。
## 2.5 生命周期
1. 实例化Instantiation容器通过构造器创建 Bean 实例(分配内存)。
2. 属性注入Population容器将依赖的 Bean 注入到当前 Bean 的字段或方法(如 @Autowired 标注的依赖)。
3. 初始化前Post-processing before initialization
执行 BeanPostProcessor 的 postProcessBeforeInitialization 方法AOP 代理生成在此阶段)。
4. 初始化Initialization
调用 @PostConstruct 注解的方法JSR-250 标准,推荐)。
若实现 InitializingBean 接口,调用 afterPropertiesSet() 方法。
执行自定义初始化方法(如 @Bean(initMethod = "init") 中指定的 init 方法)。
5. 初始化后Post-processing after initialization
执行 BeanPostProcessor 的 postProcessAfterInitialization 方法。
6. 使用In UseBean 被应用程序调用。
7. 销毁Destruction
调用 @PreDestroy 注解的方法JSR-250 标准,推荐)。
若实现 DisposableBean 接口,调用 destroy() 方法。
执行自定义销毁方法(如 @Bean(destroyMethod = "destroy") 中指定的 destroy 方法)。
![暂无图片](https://cxx0822.iepose.cn/rustfs/blog/b1054386c885ffefb0f08074ad6b6268.png 'Bean生命周期.png')
# 三、BeanDefinition
## 3.1 定义
&emsp;&emsp;BeanDefinition是Spring框架中描述Bean的元数据对象它包含了创建Bean实例所需的所有信息。
## 3.2 作用
- 定义Bean的类名
- 设置Bean的作用域
- 配置Bean的属性值
- 定义依赖关系
- 配置初始化/销毁方法
## 3.3 结构
```xml
BeanDefinition (接口)
AbstractBeanDefinition (抽象类)
RootBeanDefinition, GenericBeanDefinition, ChildBeanDefinition
```
## 3.4 关键属性
```java
public interface BeanDefinition {
// Bean的类名
void setBeanClassName(String beanClassName);
String getBeanClassName();
// 作用域singleton、prototype等
void setScope(String scope);
String getScope();
// 是否延迟初始化
void setLazyInit(boolean lazyInit);
boolean isLazyInit();
// 依赖的Bean名称
void setDependsOn(String... dependsOn);
String[] getDependsOn();
// 初始化方法
void setInitMethodName(String initMethodName);
String getInitMethodName();
// 销毁方法
void setDestroyMethodName(String destroyMethodName);
String getDestroyMethodName();
// 工厂方法名
void setFactoryMethodName(String factoryMethodName);
String getFactoryMethodName();
}
```
## 3.5 创建过程
```java
// 编程方式创建BeanDefinition
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClassName("com.example.UserService");
beanDefinition.setScope(BeanDefinition.SCOPE_SINGLETON);
beanDefinition.setLazyInit(false);
// 注册到BeanFactory
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.registerBeanDefinition("userService", beanDefinition);
```
# 四、BeanFactory
## 4.1 定义
&emsp;&emsp;BeanFactory是Spring IOC容器的根接口提供了最基本的IOC功能。它是访问Spring容器的基本客户端视图。
## 4.2 结构
```xml
BeanFactory (根接口)
ListableBeanFactory, HierarchicalBeanFactory, AutowireCapableBeanFactory
ConfigurableBeanFactory, ConfigurableListableBeanFactory
DefaultListableBeanFactory (主要实现类)
```
## 4.3 核心方法
```java
public interface BeanFactory {
// 根据名称获取Bean实例
Object getBean(String name) throws BeansException;
// 根据名称和类型获取Bean实例
<T> T getBean(String name, Class<T> requiredType) throws BeansException;
// 根据类型获取Bean实例
<T> T getBean(Class<T> requiredType) throws BeansException;
// 判断是否包含指定名称的Bean
boolean containsBean(String name);
// 判断Bean是否是单例
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
// 判断Bean是否是原型
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
// 获取Bean的类型
Class<?> getType(String name) throws NoSuchBeanDefinitionException;
}
```
## 4.4 实现示例
```java
// 创建BeanFactory实例
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
// 创建BeanDefinition
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClassName("com.example.UserService");
// 注册BeanDefinition
beanFactory.registerBeanDefinition("userService", beanDefinition);
// 获取Bean实例
UserService userService = beanFactory.getBean("userService", UserService.class);
```
## 4.5 特点
- 轻量级:功能相对简单,资源消耗少
- 延迟加载默认在第一次请求时才创建Bean实例
- 基础功能提供最基本的IOC功能
# 五、ApplicationContext
## 5.1 定义
&emsp;&emsp;ApplicationContext是BeanFactory的子接口在BeanFactory的基础上提供了更多企业级功能是Spring容器的完整实现。
## 5.2 结构
```xml
BeanFactory
ApplicationContext (扩展接口)
ConfigurableApplicationContext
AbstractApplicationContext
ClassPathXmlApplicationContext, FileSystemXmlApplicationContext,
AnnotationConfigApplicationContext, WebApplicationContext
```
## 5.3 特点
- 完整的IOC功能包含BeanFactory的所有功能
- 预实例化单例Bean默认在启动时创建单例Bean
- 企业级功能支持AOP、事务管理、国际化等
- 资源访问:统一的资源访问接口
- 事件机制:支持应用内事件发布/订阅
## 5.4 refresh方法
```java
// AbstractApplicationContext.java
/**
* Spring ApplicationContext 的核心启动方法
* 定义了容器初始化的完整生命周期
*/
public void refresh() throws BeansException, IllegalStateException {
// 使用同步锁确保容器启动的线程安全性,防止并发刷新
synchronized (this.startupShutdownMonitor) {
// [1] 准备刷新容器 - 设置启动状态,验证环境配置
prepareRefresh();
// [2] 获取或创建BeanFactory - 创建DefaultListableBeanFactory实例
// 如果是XML配置会在这里加载和解析XML文件
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// [3] 配置BeanFactory - 设置类加载器、注册核心BeanPostProcessor等
prepareBeanFactory(beanFactory);
try {
// [4] 后处理BeanFactory - 模板方法,允许子类进行特殊配置
// Web应用会在这里注册Servlet相关的Scope
postProcessBeanFactory(beanFactory);
// [5] 🚀 关键步骤调用BeanFactory后置处理器
// 执行BeanFactoryPostProcessor和BeanDefinitionRegistryPostProcessor
// ConfigurationClassPostProcessor在这里扫描@Component、@Configuration等注解
invokeBeanFactoryPostProcessors(beanFactory);
// [6] 注册Bean后置处理器 - 注册所有BeanPostProcessor
// 包括AOP、事务、自定义的BeanPostProcessor此时只是注册还未执行
registerBeanPostProcessors(beanFactory);
// [7] 初始化消息源 - 设置国际化消息资源
// 用于支持多语言消息解析
initMessageSource();
// [8] 初始化应用事件广播器 - 创建事件发布机制的核心组件
// 用于发布ApplicationEvent事件
initApplicationEventMulticaster();
// [9] 模板方法:子类可以在这里执行特殊的刷新逻辑
// SpringBoot在这里启动内嵌的Tomcat服务器
onRefresh();
// [10] 注册监听器 - 将所有ApplicationListener注册到事件广播器
// 包括静态指定的监听器和从BeanFactory中获取的监听器
registerListeners();
// [11] 🏭 核心步骤完成BeanFactory的初始化
// 实例化所有非懒加载的单例Bean执行依赖注入、AOP代理等
finishBeanFactoryInitialization(beanFactory);
// [12] 完成刷新过程 - 发布ContextRefreshedEvent事件
// 标记容器启动完成,可以正常工作了
finishRefresh();
} catch (BeansException ex) {
// 如果刷新过程中出现异常需要销毁已经创建的单例Bean防止资源泄漏
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// 销毁已经创建的单例Bean
destroyBeans();
// 重置容器状态为未激活
cancelRefresh(ex);
// 重新抛出异常
throw ex;
} finally {
// 最后重置一些公共的缓存,比如反射缓存
resetCommonCaches();
}
}
}
```
### 5.4.1 prepareRefresh()
```java
protected void prepareRefresh() {
this.startupDate = System.currentTimeMillis();
this.closed.set(false);
this.active.set(true);
if (logger.isInfoEnabled()) {
logger.info("Refreshing " + this);
}
initPropertySources();
getEnvironment().validateRequiredProperties();
this.earlyApplicationEvents = new LinkedHashSet<>();
}
```
- 这是容器启动的第一步,主要做初始化准备工作
- 设置容器的启动时间戳和活跃状态
- 验证必要的环境属性是否配置正确(比如通过@Value注解标记为必须的属性
- 初始化早期事件集合,用于存储在事件多播器创建前发布的事件
- 为整个刷新过程奠定基础状态
### 5.4.2 obtainFreshBeanFactory()
```java
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
refreshBeanFactory();
return getBeanFactory();
}
// 子类实现示例
protected final void refreshBeanFactory() throws IllegalStateException {
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
DefaultListableBeanFactory beanFactory = createBeanFactory();
customizeBeanFactory(beanFactory);
loadBeanDefinitions(beanFactory);
this.beanFactory = beanFactory;
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error", ex);
}
}
```
- 创建或刷新底层的BeanFactorySpring容器的核心工厂
- 如果是刷新操作会先销毁旧的BeanFactory和所有Bean实例
- 创建新的DefaultListableBeanFactory实例
- 加载Bean定义从XML文件或注解配置中解析Bean的元数据
- 这个步骤完成后容器就知道了有哪些Bean需要管理但还没有创建Bean实例
### 5.4.3 prepareBeanFactory()
```java
protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
beanFactory.setBeanClassLoader(getClassLoader());
beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver());
beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment()));
beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
beanFactory.ignoreDependencyInterface(EnvironmentAware.class);
beanFactory.ignoreDependencyInterface(ApplicationContextAware.class);
beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);
beanFactory.registerResolvableDependency(ApplicationContext.class, this);
}
```
- 配置BeanFactory的基本设置和功能组件
- 设置类加载器、SPEL表达式解析器、属性编辑器
- 注册ApplicationContextAwareProcessor这是实现各种Aware接口的关键
- 忽略某些接口的依赖注入(这些接口由容器特殊处理)
- 注册可解析的依赖关系当Bean需要这些类型时直接返回容器实例
- 为BeanFactory添加基本的功能支持
### 5.4.4 postProcessBeanFactory()
```java
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
// 空方法 - 模板方法模式
// 子类可以覆盖此方法添加特定逻辑
}
```
- 模板方法允许子类在标准初始化后对BeanFactory进行自定义配置
- 在Web环境中会在这里注册Servlet相关的Scoperequest、session等
- 可以添加特定环境的BeanPostProcessor或进行其他特殊配置
- 这是框架扩展的重要切入点
### 5.4.5 invokeBeanFactoryPostProcessors()
```java
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
}
```
- 🚀 这是最关键的一步负责扫描和注册所有的Bean定义
- 执行所有BeanFactoryPostProcessor它们可以修改Bean的定义信息
- 特别是ConfigurationClassPostProcessor它负责
扫描@Component@Service@Repository等注解
处理@Configuration配置类
解析@Bean方法定义
处理@Import@ComponentScan等注解
- 这一步完成后所有的Bean定义都已经被解析和注册
### 5.4.6 registerBeanPostProcessors()
```java
protected void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory) {
PostProcessorRegistrationDelegate.registerBeanPostProcessors(beanFactory, this);
}
```
- 注册所有的BeanPostProcessor到BeanFactory中
- 包括AOP、事务管理、自定义的Bean后置处理器等
- 注意此时只是注册真正的调用要等到Bean实例化时
- 这些处理器按照优先级顺序注册,确保执行顺序正确
- 这是Spring AOP和事务管理等高级功能的基础
### 5.4.7 initMessageSource()
```java
protected void initMessageSource() {
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (beanFactory.containsLocalBean(MESSAGE_SOURCE_BEAN_NAME)) {
this.messageSource = beanFactory.getBean(MESSAGE_SOURCE_BEAN_NAME, MessageSource.class);
}
else {
DelegatingMessageSource dms = new DelegatingMessageSource();
this.messageSource = dms;
beanFactory.registerSingleton(MESSAGE_SOURCE_BEAN_NAME, this.messageSource);
}
}
```
- 初始化国际化消息支持
- 如果用户配置了自定义的MessageSource就使用用户的配置
- 否则创建默认的DelegatingMessageSource
- 支持从属性文件读取多语言消息,实现国际化功能
- 用于解析${}占位符和国际化消息
### 5.4.8 initApplicationEventMulticaster()
```java
protected void initApplicationEventMulticaster() {
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
this.applicationEventMulticaster = beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
}
else {
this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
}
}
```
- 初始化应用事件发布机制的核心组件
- 如果用户配置了自定义的事件广播器,就使用用户的配置
- 否则创建默认的SimpleApplicationEventMulticaster
- 这个组件负责将应用事件广播给所有监听器
- 支持观察者模式,实现应用内组件间的解耦通信
### 5.4.9 onRefresh() - 刷新时扩展点
```java
protected void onRefresh() throws BeansException {
// 空方法 - 模板方法
}
```
- 模板方法,允许子类在特定时机执行自定义逻辑
- 在Spring Boot中这里会启动内嵌的Web服务器Tomcat、Jetty等
- 在Spring MVC中这里会初始化HandlerMapping、HandlerAdapter等组件
- 这是框架扩展的重要切入点不同的ApplicationContext实现有不同的行为
### 5.4.10 registerListeners()
```java
protected void registerListeners() {
for (ApplicationListener<?> listener : getApplicationListeners()) {
getApplicationEventMulticaster().addApplicationListener(listener);
}
String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
for (String listenerBeanName : listenerBeanNames) {
getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
}
// 发布早期事件
if (this.earlyApplicationEvents != null) {
for (ApplicationEvent earlyEvent : this.earlyApplicationEvents) {
getApplicationEventMulticaster().multicastEvent(earlyEvent);
}
}
}
```
- 注册所有的事件监听器到事件广播器
- 包括静态注册的监听器和从BeanFactory中获取的监听器Bean
- 发布在监听器注册前收集的早期事件
- 确保事件发布机制完整可用,应用事件能够正确分发
### 5.4.11 finishBeanFactoryInitialization()
```java
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME)) {
beanFactory.setConversionService(beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
}
if (!beanFactory.hasEmbeddedValueResolver()) {
beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal));
}
beanFactory.freezeConfiguration();
beanFactory.preInstantiateSingletons();
}
```
- 🏭 这是最核心的步骤真正创建Bean实例
- 初始化类型转换服务ConversionService
- 注册嵌入式值解析器(用于解析${}占位符)
- 冻结Bean定义防止后续修改
- 实例化所有非懒加载的单例Bean这个过程中会
创建Bean实例
执行依赖注入(@Autowired@Resource等
执行BeanPostProcessorAOP代理在这里发生
调用初始化方法(@PostConstruct、InitializingBean
- 这是容器启动过程中最耗时的步骤
### 5.4.12 finishRefresh()
```java
protected void finishRefresh() {
clearResourceCaches();
initLifecycleProcessor();
getLifecycleProcessor().onRefresh();
publishEvent(new ContextRefreshedEvent(this));
LiveBeansView.registerApplicationContext(this);
}
```
- 完成容器的启动过程,进行最后的清理和通知工作
- 清除资源缓存,释放临时资源
- 初始化生命周期处理器管理Bean的生命周期
- 发布ContextRefreshedEvent事件通知所有监听器容器已启动完成
- 注册LiveBeansView用于监控和调试
- 标记整个容器启动流程结束,容器进入就绪状态
### 5.4.13 总结
&emsp;&emsp;refresh()方法按固定顺序执行12个步骤核心可归纳为准备容器环境 → 解析并注册Bean定义→ 初始化事件、消息等基础设施 → 实例化所有非懒加载的单例Bean→ 完成启动并发布事件。整个过程通过模板方法模式定义,确保了扩展性和一致性。
&emsp;&emsp;核心在于两个关键扩展机制BeanFactoryPostProcessor在Bean定义阶段介入负责修改和注册Bean的定义信息BeanPostProcessor在Bean实例化阶段介入负责对创建好的Bean实例进行增强和包装。

View File

@@ -0,0 +1,89 @@
# 一、基础概念
&emsp;&emsp;Spring MVCSpring Model-View-Controller是 Spring Framework 中的一部分,它是一个基于 **<font style="color:#DF2A3F;">请求驱动</font>** 的 Web 框架,主要用于构建 Web 应用程序,并且遵循 MVC模型-视图-控制器) 设计模式。它将 Web 应用的业务逻辑、用户界面和请求处理分离,使得代码更加模块化和可维护。
# 二、具体流程
![暂无图片](https://cxx0822.iepose.cn/rustfs/blog/04fb16a3e32f247bbf0bb7e176de8444.png '具体流程.png')
1. **客户端发起HTTP请求**
&emsp;&emsp;客户端浏览器、API 客户端等)向服务器发送 HTTP 请求。请求包含了目标 URL、HTTP 方法GET、POST 等)以及可能的请求参数。
前端通过发送 HTTP 请求(通常是 AJAX 请求)到后端的 API 端点,并期望获得响应(通常是 JSON 数据)。
2. **请求到达 DispatcherServlet**
&emsp;&emsp;所有 HTTP 请求都会先到达 Spring MVC 的核心组件:**<font style="color:#DF2A3F;">DispatcherServlet</font>**。它是前端控制器负责将请求分发给正确的处理器。DispatcherServlet 是所有请求的入口Spring Boot 会自动配置它。
&emsp;&emsp;DispatcherServlet 接受所有的 HTTP 请求,根据请求 URL 和 HTTP 方法,定位匹配的控制器方法,将请求交给相应的 Handler Mapping 来匹配控制器。在请求处理前后,执行 拦截器(可选)。最终通过 View Resolver 返回一个合适的视图(对于 RESTful API通常返回 JSON 格式的数据)。
3. **查找处理器Handler Mapping**
&emsp;&emsp;DispatcherServlet 根据请求的 URL 和 HTTP 方法,查找与之匹配的处理器方法(通常是一个控制器中的方法)。这是由 HandlerMapping 完成的。Spring MVC 提供了多种方式来映射 URL 到控制器方法,例如 @RequestMapping@GetMapping 等注解。
&emsp;&emsp;@RestController:该注解标记的类会返回 JSON 数据。
&emsp;&emsp;@RequestMapping:用于映射 URL 路径和 HTTP 方法。
&emsp;&emsp;@GetMapping@PostMapping 等:简化版本的 @RequestMapping
4. **执行拦截器Interceptor**
&emsp;&emsp;在请求到达控制器之前DispatcherServlet 会依次执行配置的拦截器。拦截器主要用于日志记录、安全检查、请求参数的预处理等。
&emsp;&emsp;preHandle请求进入控制器方法之前。
&emsp;&emsp;postHandle请求完成后视图渲染之前。
&emsp;&emsp;afterCompletion视图渲染完成后。
&emsp;&emsp;**<font style="color:#DF2A3F;">如果拦截器的 preHandle 方法返回 false请求会被拦截后续的处理不会继续进行。</font>**
5. **控制器方法处理请求Handler Execution**
&emsp;&emsp;DispatcherServlet 调用合适的控制器方法来处理请求。控制器方法负责处理请求的业务逻辑,并返回一个 Java 对象,这个对象会被自动转换成 JSON 格式返回给客户端。
&emsp;&emsp;Spring 通过 @ResponseBody 注解(或 @RestController)实现将 Java 对象自动转换为 JSON 格式。默认情况下Spring 使用 Jackson 来将 Java 对象序列化成 JSON 数据。
&emsp;&emsp;@RestController:将控制器标记为返回 JSON 数据的 RESTful 控制器。
&emsp;&emsp;@ResponseBody:标记方法返回的数据需要直接作为响应体返回,而不是解析为视图。
&emsp;&emsp;@RequestParam:用于获取单个请求参数。
&emsp;&emsp;@PathVariable:用于从 URL 中提取变量(如 /users/{id} 中的 {id})。
6. **响应返回客户端**
&emsp;&emsp;DispatcherServlet 将处理完的结果JSON 数据)返回给客户端。响应数据通常会包含:
&emsp;&emsp;**状态码**:如 200 OK成功、201 Created资源创建成功、404 Not Found资源未找到、500 Internal Server Error服务器错误等。
&emsp;&emsp;**响应头**:通常包括 Content-Type: application/json表示响应体是 JSON 格式。
&emsp;&emsp;**响应体**:包含返回的数据,通常是一个 JSON 格式的对象或数组。
以上只适用于基于RESTful API 前后端分离的架构。
# 三、完整流程
![暂无图片](https://cxx0822.iepose.cn/rustfs/blog/cad3b69253771b02b07d94f7d01c0e4f.png '完整流程.png')
&emsp;&emsp;在SpringMVC前面还有Tomcat容器层。客户端发起请求后先经过Tomcat容器根据请求的 URL 确定该请求是否是静态资源请求(如 .html、.css、.js。如果是静态资源请求Tomcat 会直接返回文件,而不经过 Spring MVC 的请求处理链。对于动态请求(如请求 /user 等Tomcat 会将这些请求交给 DispatcherServlet 进行处理。
&emsp;&emsp;在进入DispatcherServlet前可以增加过滤器进行处理。过滤器不属于Spring部分。
&emsp;&emsp;Tomcat 是一个轻量级的 Web 服务器和 Servlet 容器,支持 Java Servlet 和 JSP 规范,能够处理 HTTP 请求并生成动态 Web 内容。
&emsp;&emsp;Servlet 是 Java 编写的服务器端程序,能够接收 HTTP 请求并生成响应,它是动态 Web 内容生成的核心技术。
&emsp;&emsp;Tomcat 管理 Servlet 的生命周期,并负责处理客户端的请求和返回响应。
# 四、重要知识点
## 4.1 拦截器和过滤器区别
1. 拦截器不依赖与servlet容器过滤器依赖与servlet容器。
2. 拦截器只能对action请求(DispatcherServlet 映射的请求)起作用,而过滤器则可以对几乎所有的请求起作用。
3. 拦截器可以访问容器中的Bean(DI)而过滤器不能访问基于spring注册的过滤器也可以访问容器中的bean
4. 执行顺序:过滤器->拦截器
## 4.2 拦截器执行顺序
&emsp;&emsp;HandlerInterceptor 拦截器执行顺序:
1. preHandle():请求处理前 按注册顺序依次执行。
2. Controller请求处理 请求到达Controller并被处理。
3. postHandle():请求处理后,视图渲染前 按注册顺序逆序执行。
4. afterCompletion():视图渲染后 按注册顺序逆序执行。
## 4.3 请求和响应体拦截器
&emsp;&emsp;RequestBodyAdvice, ResponseBodyAdvice主要发生在Controller执行前后
1. preHandle():请求处理前
2. beforeBodyRead请求体反序列化前
3. @RequestBodyController方法参数绑定
4. Controller请求处理
5. beforeBodyWrite():响应体序列化之前
6. postHandle():请求处理后
7. 视图渲染
8. afterCompletion():请求结束
## 4.4 @Controller 和 @RestController区别
&emsp;&emsp;@Controller:用于定义一个 Spring MVC 控制器类。它通常用于返回视图(如 JSP 或 Thymeleaf
&emsp;&emsp;@RestController:是 @Controller@ResponseBody 的组合,用于开发 RESTful API。它将方法的返回值直接写入 HTTP 响应体中,通常返回 JSON 或 XML 格式的数据。
## 4.5 异常处理
&emsp;&emsp;@ExceptionHandler:用于控制器类或方法中处理特定异常。它会捕获指定的异常并返回相应的响应(如错误页面、错误消息等)。
&emsp;&emsp;@ControllerAdvice:是一个全局异常处理器,用于捕获应用中所有控制器抛出的异常。

View File

@@ -1,11 +1,32 @@
---
layout: doc
title: Web后端
description: Web后端开发技术文档包含数据库、框架、中间件等
---
# 内容导航
# Web 后端开发
后端开发技术栈的学习笔记和总结。
## 主要内容
- [MySQL知识点](/Web-Backend/MySQL-Knowledge)
- [Flyway简单使用](/Web-Backend/Flyway)
- [MyBatis简介和使用](/Web-Backend/MyBatis)
- [RustFS简介和使用](/Web-Backend/RustFS)
### 🍃 SpringBoot
SpringBoot 框架学习系列:
- [Spring IOC简介](/SpringBoot/SpringIOC)
- [SpringBoot启动流程](/SpringBoot/SpringBoot-Start-Process)
- [Spring MVC简介](/SpringBoot/SpringMVC)
- [SpringBoot Starter原理](/SpringBoot/SpringBootStarter)
- [SpringBoot Bean简介](/SpringBoot/SpringBootBean)
- [SpringBoot3原生镜像](/SpringBoot/SpringBoot3-GraalVM)
### 🐍 FastAPI
Python FastAPI 框架学习:
- [FastAPI基础教程](/FastAPI/FastAPI-Guide)
- [基于OAuth2的FastApi安全验证](/FastAPI/OAuth2)