#42
今日计划
1.uniapp
视频课看到p40
示例代码写一点
官网教程看一点
2.小兔鲜儿
day2 视频课随机看
3.Java算法
红黑树 B树 哈希表 以及题目
3h
4.技术栈
aop maven
今天先把ssm 框架的aop看了
总结
今日源码
uniapp
DEMO2 宠物管理系统
<template>
<!-- 页面容器,设置整体背景、适配小程序顶部导航 -->
<view class="profile-page">
<!-- 顶部栏区域 -->
<view class="top-bar">
<text class="page-title">DOG 宠物管理系统</text>
<!-- 个人信息卡片区域(调整到右上角,点击头像触发弹窗) -->
<view class="profile-card" @click="showUserInfoModal = true">
<!-- 头像(可替换为实际接口返回的头像地址) -->
<image
class="avatar"
:src="userInfo.avatar || defaultAvatar"
mode="aspectFill"
></image>
<!-- 用户名及基础信息 -->
<view class="user-info">
<text class="username">{{ userInfo.username || '匿名用户' }}</text>
<text class="sub-info">宠物</text>
</view>
</view>
</view>
<!-- 宠物档案模块 -->
<view class="pet-archive">
<text class="module-title">宠物档案</text>
<view class="pet-item">
<text class="label">宠物昵称:</text>
<text class="value">{{ petInfo.nickname || '暂未设置' }}</text>
</view>
<view class="pet-item">
<text class="label">宠物品种:</text>
<text class="value">{{ petInfo.breed || '暂未设置' }}</text>
</view>
<view class="pet-item">
<text class="label">宠物年龄:</text>
<text class="value">{{ petInfo.age || '暂未设置' }}</text>
</view>
<view class="pet-item">
<text class="label">疫苗情况:</text>
<text class="value">{{ petInfo.vaccineStatus || '未录入' }}</text>
</view>
</view>
<!-- 快捷功能模块 -->
<view class="quick-func">
<text class="module-title">快捷功能</text>
<view class="func-list">
<view class="func-item" @click="handleFunc('record')">
<image class="func-icon" src="/static/icon-record.png"></image>
<text class="func-text">健康记录</text>
</view>
<view class="func-item" @click="handleFunc('remind')">
<image class="func-icon" src="/static/icon-remind.png"></image>
<text class="func-text">提醒设置</text>
</view>
<view class="func-item" @click="handleFunc('shop')">
<image class="func-icon" src="/static/icon-shop.png"></image>
<text class="func-text">宠物商城</text>
</view>
<view class="func-item" @click="handleFunc('community')">
<image class="func-icon" src="/static/icon-community.png"></image>
<text class="func-text">宠友社区</text>
</view>
</view>
</view>
<!-- 添加宠物按钮 -->
<view class="add-pet-btn" @click="showAddPetModal = true">
<text>添加宠物</text>
</view>
<!-- 底部说明 -->
<view class="footer">
<text>©2025 宠物管理系统 陪伴毛孩子健康成长</text>
</view>
<!-- 个人信息弹窗 -->
<view class="modal-mask" v-if="showUserInfoModal" @click="showUserInfoModal = false">
<view class="user-info-modal" @click.stop>
<text class="modal-title">个人信息</text>
<view class="form-item">
<text class="form-label">用户名:</text>
<input
class="form-input"
v-model="userInfo.username"
placeholder="请输入用户名"
/>
</view>
<view class="form-item">
<text class="form-label">头像地址:</text>
<input
class="form-input"
v-model="userInfo.avatar"
placeholder="请输入头像地址"
/>
</view>
<view class="form-item">
<text class="form-label">性别:</text>
<input
class="form-input"
v-model="userInfo.gender"
placeholder="请输入性别"
/>
</view>
<view class="form-item">
<text class="form-label">年龄:</text>
<input
class="form-input"
v-model="userInfo.age"
placeholder="请输入年龄"
/>
</view>
<view class="confirm-btn" @click="saveUserInfo">保存</view>
</view>
</view>
<!-- 添加宠物弹窗 -->
<view class="modal-mask" v-if="showAddPetModal" @click="showAddPetModal = false">
<view class="add-pet-modal" @click.stop>
<text class="modal-title">添加宠物</text>
<view class="form-item">
<text class="form-label">宠物昵称:</text>
<input
class="form-input"
v-model="newPetInfo.nickname"
placeholder="请输入宠物昵称"
/>
</view>
<view class="form-item">
<text class="form-label">宠物品种:</text>
<input
class="form-input"
v-model="newPetInfo.breed"
placeholder="请输入宠物品种"
/>
</view>
<view class="form-item">
<text class="form-label">宠物年龄:</text>
<input
class="form-input"
v-model="newPetInfo.age"
placeholder="请输入宠物年龄"
/>
</view>
<view class="form-item">
<text class="form-label">疫苗情况:</text>
<input
class="form-input"
v-model="newPetInfo.vaccineStatus"
placeholder="请输入疫苗情况"
/>
</view>
<view class="confirm-btn" @click="addPet">确认添加</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue';
// 模拟用户信息(实际可从接口请求、全局状态管理 Pinia/ Vuex 获取)
const userInfo = ref({
username: '嘉丽狗',
avatar: '/static/default-avatar.jpg', // 本地静态资源,需放在uniapp项目的 static 目录
gender: '男',
age: 18
});
// 默认头像(当用户未设置头像时显示)
const defaultAvatar = '/static/default-avatar.png';
// 模拟宠物信息
const petInfo = ref({
nickname: '蓝角',
breed: '超级塞牙人',
age: '2岁',
vaccineStatus: '已接种狂犬疫苗'
});
// 新增:添加宠物的临时信息对象
const newPetInfo = ref({
nickname: '',
breed: '',
age: '',
vaccineStatus: ''
});
// 新增:控制弹窗显示与隐藏的变量
const showUserInfoModal = ref(false);
const showAddPetModal = ref(false);
// 快捷功能点击事件
const handleFunc = (type) => {
switch(type) {
case 'record':
uni.showToast({ title: '进入健康记录', icon: 'none' });
// 可扩展:uni.navigateTo({ url: '/pages/record/record' })
break;
case 'remind':
uni.showToast({ title: '进入提醒设置', icon: 'none' });
break;
case 'shop':
uni.showToast({ title: '进入宠物商城', icon: 'none' });
break;
case 'community':
uni.showToast({ title: '进入宠友社区', icon: 'none' });
break;
}
}
// 新增:保存个人信息
const saveUserInfo = () => {
// 这里可扩展调用接口,将修改后的 userInfo 提交到后端保存
console.log('修改后的个人信息:', userInfo.value);
showUserInfoModal.value = false;
uni.showToast({ title: '个人信息保存成功', icon: 'none' });
}
// 新增:添加宠物
const addPet = () => {
// 简单校验(可根据实际需求完善)
if (!newPetInfo.value.nickname) {
uni.showToast({ title: '请输入宠物昵称', icon: 'none' });
return;
}
// 这里可扩展调用接口,将 newPetInfo 提交到后端保存,成功后更新 petInfo 等数据
console.log('新增宠物信息:', newPetInfo.value);
// 示例:直接更新到当前展示的 petInfo(实际应根据业务逻辑处理,比如存到列表等)
petInfo.value = { ...newPetInfo.value };
showAddPetModal.value = false;
uni.showToast({ title: '宠物添加成功', icon: 'none' });
// 清空临时对象
newPetInfo.value = {
nickname: '',
breed: '',
age: '',
vaccineStatus: ''
};
}
</script>
<style scoped>
/* 页面整体样式 */
.profile-page {
display: flex;
flex-direction: column;
min-height: 100vh; /* 占满视口高度 */
background-color: #fff; /* 页面背景色 */
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
}
/* 顶部栏样式 */
.top-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 20px;
background: linear-gradient(to right, #ffcad4, #f9c5d1); /* 渐变背景 */
color: #fff;
}
.page-title {
font-size: 18px;
font-weight: bold;
}
/* 右上角个人信息卡片样式,添加点击 cursor 提示 */
.profile-card {
display: flex;
align-items: center;
cursor: pointer;
}
.avatar {
width: 40px;
height: 40px;
border-radius: 50%; /* 圆形头像 */
margin-right: 8px;
}
.user-info {
display: flex;
flex-direction: column;
}
.username {
font-size: 14px;
font-weight: 500;
line-height: 1.2;
}
.sub-info {
font-size: 12px;
opacity: 0.8;
}
/* 宠物档案模块样式 */
.pet-archive {
margin: 20px;
padding: 15px;
background-color: #fdfdfd;
border-radius: 8px;
box-shadow: 0 2px 6px rgba(0,0,0,0.05);
}
.module-title {
font-size: 16px;
font-weight: bold;
color: #333;
margin-bottom: 12px;
position: relative;
padding-left: 8px;
}
.module-title::before {
content: '';
position: absolute;
left: 0;
top: 2px;
width: 3px;
height: 14px;
background-color: #ff8aae;
border-radius: 2px;
}
.pet-item {
display: flex;
justify-content: space-between;
padding: 8px 0;
border-bottom: 1px solid #f2f2f2;
}
.label {
color: #666;
font-size: 14px;
}
.value {
color: #333;
font-size: 14px;
}
/* 快捷功能模块样式 */
.quick-func {
margin: 0 20px 20px;
padding: 15px;
background-color: #fdfdfd;
border-radius: 8px;
box-shadow: 0 2px 6px rgba(0,0,0,0.05);
}
.func-list {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
.func-item {
width: 48%;
text-align: center;
padding: 15px 0;
background-color: #fff;
border-radius: 6px;
box-shadow: 0 2px 4px rgba(0,0,0,0.03);
margin-bottom: 10px;
}
.func-icon {
width: 28px;
height: 28px;
margin-bottom: 6px;
}
.func-text {
font-size: 13px;
color: #333;
}
/* 添加宠物按钮样式 */
.add-pet-btn {
width: 120px;
height: 40px;
line-height: 40px;
text-align: center;
background-color: #ff8aae;
color: #fff;
border-radius: 20px;
margin: 0 auto 60px; /* 与底部保持间距,避免被遮挡 */
box-shadow: 0 2px 6px rgba(0,0,0,0.2);
cursor: pointer;
}
/* 底部说明样式 */
.footer {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
text-align: center;
padding: 10px;
font-size: 12px;
color: #999;
background-color: #f8f8f8;
border-top: 1px solid #eee; /* 添加上边框区分内容 */
box-sizing: border-box; /* 避免 padding 导致宽度溢出 */
}
/* 弹窗遮罩层样式 */
.modal-mask {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.5);
display: flex;
justify-content: center;
align-items: center;
}
/* 个人信息弹窗样式 */
.user-info-modal {
width: 80%;
background-color: #fff;
border-radius: 10px;
padding: 20px;
}
.modal-title {
font-size: 16px;
font-weight: bold;
margin-bottom: 15px;
}
.form-item {
display: flex;
flex-direction: column;
margin-bottom: 12px;
}
.form-label {
font-size: 14px;
color: #333;
margin-bottom: 4px;
}
.form-input {
height: 36px;
border: 1px solid #eee;
border-radius: 4px;
padding: 0 8px;
}
.confirm-btn {
width: 100px;
height: 36px;
line-height: 36px;
text-align: center;
background-color: #ff8aae;
color: #fff;
border-radius: 4px;
margin-top: 15px;
cursor: pointer;
}
/* 添加宠物弹窗样式(与个人信息弹窗样式类似,可按需微调) */
.add-pet-modal {
width: 80%;
background-color: #fff;
border-radius: 10px;
padding: 20px;
}
</style>
大学生家教管理系统
### 大学生家教平台页面原型设计方案
基于大学生家教平台的核心需求(家教展示、课程匹配、用户互动),以下是完整的页面原型设计,包含8个核心页面,覆盖用户主要操作流程。
#### **1. 首页(核心入口)**
**核心功能**:平台概览、快速入口、热门家教推荐
**页面结构**:
- **顶部导航栏**
- 左侧:平台Logo("学伴")
- 右侧:用户头像(未登录显示"登录/注册")、消息通知图标
- **搜索区**
- 搜索框:提示文字"搜索科目/年级/大学"
- 快捷筛选标签:"中小学"、"高中"、"大学课程"、"语言培训"
- **轮播Banner**(3-4张)
- 内容:平台优势("985/211大学生认证")、促销活动("首次试听立减50元")
- **快捷功能区**(图标+文字)
- "找家教"、"做家教"、"课程表"、"我的订单"
- **热门家教推荐**
- 标题:"优质家教推荐"(右侧"更多"入口)
- 横向滚动卡片(4-5个):
- 每张卡片包含:家教头像、姓名、学校专业(如"北京大学 数学")、核心科目标签("高数/线代")、评分(★★★★☆ 4.9)、价格("80元/小时")
- **学科分类导航**
- 网格布局:语文、数学、英语、物理、化学、生物、编程、艺术等(图标+文字)
- **底部导航栏**
- 首页(高亮)、搜索、发布、消息、我的
#### **2. 搜索/筛选页**
**核心功能**:精准筛选家教或课程
**页面结构**:
- **顶部**
- 返回按钮、搜索框(带回显)、取消按钮
- **高级筛选区**(可折叠)
- **学科**:多选项(可勾选多个,如"数学+物理")
- **年级**:下拉选择(小学/初中/高中/大学)
- **性别**:单选(不限/男/女)
- **价格区间**:滑块选择(50-500元/小时)
- **距离**:下拉选择(不限/3km内/5km内,基于定位)
- **学历要求**:多选(本科/硕士/985院校/211院校)
- **评分**:星级选择(4星及以上/3星及以上)
- "重置"和"确认筛选"按钮
- **筛选结果区**
- 排序选项:"推荐优先"、"价格从低到高"、"评分从高到低"
- 家教列表(垂直排列,每行1个):
- 左侧:家教头像(圆形)
- 中间:
- 姓名+标签(如"金牌家教")
- 学校+专业+年级(如"南京大学 计算机 大三")
- 擅长科目(标签形式:"Python/Java")
- 右侧:
- 评分(★★★★☆ 4.8)
- 价格("¥120/小时")
- 距离(如"2.5km",可选)
- **底部**:"加载更多"按钮
#### **3. 家教详情页**
**核心功能**:展示家教详细信息,促进用户决策
**页面结构**:
- **顶部**
- 返回按钮、分享按钮、收藏按钮(空心/实心)
- **家教基本信息**
- 左侧:大尺寸头像(正方形)
- 右侧:
- 姓名+学历标签(如"985在读")
- 一句话简介(如"擅长高中数学,3年家教经验")
- 核心数据:评分(4.9)、接单量(128单)、响应率(98%)
- **核心信息标签栏**(横向滚动)
- "基本信息"、"教学经历"、"学员评价"、"课程安排"
- **内容区**(随标签切换)
- **基本信息**:
- 性别、年龄、学校、专业、年级
- 可授课时间(表格形式:周一至周日,上午/下午/晚上)
- 可授课方式(上门/在线/协商)
- 自我介绍(富文本,支持换行)
- **教学经历**:
- 过往家教案例(时间+学员情况+提升效果,如"2023.09-2024.01 辅导高二学生数学,成绩从70分提升至110分")
- 证书/奖项(如"数学竞赛一等奖",可附图片)
- **学员评价**:
- 评价列表(每条包含:学员昵称、课程、评分、评价内容、时间)
- 评分分布雷达图(如"知识点讲解4.9"、"耐心度4.8")
- **课程安排**:
- 已开设课程(如"高中数学冲刺班",包含课时、价格、大纲)
- **底部操作栏**(固定)
- 左侧:"在线咨询"按钮(聊天图标)
- 右侧:"预约试讲"按钮(主色调,突出显示)
#### **4. 消息中心页**
**核心功能**:用户间沟通、系统通知
**页面结构**:
- **顶部**
- 标题"消息中心"
- 右上角"设置"(消息免打扰设置)
- **标签栏**
- "私信"(默认显示)、"系统通知"
- **私信列表**
- 每条消息:
- 左侧:对方头像
- 中间:
- 对方姓名+身份标签(如"家长")
- 消息预览(未读显示完整,已读显示省略)
- 右侧:
- 时间
- 未读提示(红色小圆点+数字)
- **系统通知列表**
- 每条通知:
- 左侧:系统图标(如订单图标、活动图标)
- 中间:通知内容(如"您预约的试讲已被确认")
- 右侧:时间
- **空状态**(无消息时)
- 图标(信封/铃铛)
- 提示文字"暂无消息"
- (私信页)"去联系家教"按钮
#### **5. 订单管理页**
**核心功能**:查看/管理所有订单
**页面结构**:
- **顶部**
- 标题"我的订单"
- 筛选按钮(可筛选"全部/待确认/进行中/已完成")
- **订单列表**
- 按时间倒序排列,每条订单:
- 左侧:家教头像(或课程封面)
- 中间:
- 订单标题(如"高中数学一对一辅导")
- 订单信息(时间、地点/在线方式)
- 状态标签(如"待确认"、"已完成",不同状态不同颜色)
- 右侧:价格("¥200")
- **订单详情展开区**(点击订单可展开)
- 详细信息:家教信息、课程安排、支付状态、联系方式
- 操作按钮(根据状态显示):
- 待确认:"取消订单"
- 进行中:"联系家教"、"申请退款"
- 已完成:"评价家教"、"再次预约"
- **空状态**(无订单时)
- 图标(订单图标)
- 提示文字"暂无订单"
- "去预约家教"按钮
#### **6. 个人中心页**
**核心功能**:用户信息管理、功能入口
**页面结构**:
- **顶部信息区**
- 背景图(可自定义)
- 头像+用户名+身份(如"大学生家教")
- 编辑资料按钮
- **功能入口区**(网格布局,4列)
- "我的资料"(个人信息图标)
- "我的订单"(订单图标,带数字角标)
- "我的收藏"(星星图标)
- "我的评价"(评价图标)
- "成为家教"(仅家长身份显示,申请入口)
- "实名认证"(未认证显示红色提示)
- "帮助中心"(问号图标)
- "设置"(齿轮图标)
- **数据概览区**(仅家教身份显示)
- 接单量、好评率、收入统计(图表+数字)
- **底部**
- "关于我们"、"用户协议"、"退出登录"按钮
#### **7. 登录/注册页**
**核心功能**:用户身份验证、账号创建
**登录页结构**:
- **顶部**:平台Logo+标语(如"大学生靠谱家教平台")
- **表单区**
- 手机号输入框(带验证码登录选项)
- 密码输入框(带显示/隐藏切换)
- "忘记密码"链接(右侧)
- "登录"按钮(主色调)
- **其他登录方式**
- 第三方登录图标(微信、QQ,灰色底)
- **底部**
- "还没有账号?立即注册"(注册入口)
- 隐私协议勾选框
**注册页结构**:
- **顶部**:返回按钮+标题"注册账号"
- **表单区**
- 手机号输入框+获取验证码按钮
- 验证码输入框
- 密码设置框(带强度提示)
- 身份选择("我是家长"、"我是大学生",单选按钮)
- "注册"按钮
- **底部**
- 注册即同意《用户协议》和《隐私政策》
#### **8. 发布需求页(家长端)**
**核心功能**:家长发布家教需求
**页面结构**:
- **顶部**:返回按钮+标题"发布家教需求"
- **表单区**
- **基本信息**:
- 辅导科目(多选,如"数学+英语")
- 辅导年级(下拉选择)
- 辅导方式(上门/在线/均可,单选)
- 辅导时间(可选择多个时间段)
- **详细要求**:
- 家教性别(不限/男/女)
- 学历要求(多选)
- 预算范围(下拉选择,如"100-150元/小时")
- 特殊要求(文本框,如"需有中考辅导经验")
- **联系信息**:
- 联系人姓名
- 联系电话(默认读取用户信息,可修改)
- 授课地址(仅上门时显示,可选择地图定位)
- **底部**:"发布需求"按钮(主色调,点击前验证必填项)
### 设计说明
1. **风格定位**:简洁清新,以蓝色(信任)和绿色(成长)为主色调,符合教育平台气质
2. **用户体验**:
- 减少跳转层级,核心操作(如预约、咨询)一步可达
- 关键信息(价格、评分、身份)突出显示,辅助决策
- 适配移动端(750rpx标准设计),支持横竖屏切换
3. **差异化亮点**:
- 大学生身份认证标识(突出平台可信度)
- 多维度筛选系统(精准匹配需求)
- 试讲预约机制(降低决策门槛)
可基于此原型进行高保真设计,补充交互细节(如加载动画、弹窗提示)后进入开发阶段。
算法
红黑树
### 红黑树完整实现(带超详细注释)
以下是优化后的红黑树实现,添加了更详细的注释和逻辑说明,帮助理解每个操作的核心思想和步骤:
```java
package com.itheima.datastructure.redblacktree;
import java.util.LinkedList;
import java.util.Queue;
/**
* <h3>红黑树 - 自平衡二叉搜索树</h3>
* <p>特性:</p>
* <ol>
* <li>每个节点要么是红色,要么是黑色</li>
* <li>根节点是黑色</li>
* <li>所有叶子节点(NIL节点,空节点)是黑色</li>
* <li>如果一个节点是红色的,则它的两个子节点都是黑色的</li>
* <li>对每个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点</li>
* </ol>
*/
public class RedBlackTree {
enum Color {
RED, BLACK;
}
Node root;
private int size;
static class Node {
int key; // 键
Object value; // 值
Node left; // 左子节点
Node right; // 右子节点
Node parent; // 父节点
Color color = RED; // 节点颜色,默认为红色(插入时更方便处理)
public Node(int key, Object value) {
this.key = key;
this.value = value;
}
public Node(int key) {
this.key = key;
}
public Node(int key, Color color) {
this.key = key;
this.color = color;
}
public Node(int key, Color color, Node left, Node right) {
this.key = key;
this.color = color;
this.left = left;
this.right = right;
if (left != null) {
left.parent = this;
}
if (right != null) {
right.parent = this;
}
}
// 判断当前节点是否是其父节点的左子节点
boolean isLeftChild() {
return parent != null && parent.left == this;
}
// 获取叔叔节点(父节点的兄弟节点)
Node uncle() {
if (parent == null || parent.parent == null) {
return null;
}
if (parent.isLeftChild()) {
return parent.parent.right;
} else {
return parent.parent.left;
}
}
// 获取兄弟节点
Node sibling() {
if (parent == null) {
return null;
}
if (this.isLeftChild()) {
return parent.right;
} else {
return parent.left;
}
}
@Override
public String toString() {
return key + (color == Color.RED ? "R" : "B");
}
}
// 判断节点是否为红色(空节点视为黑色)
boolean isRed(Node node) {
return node != null && node.color == Color.RED;
}
// 判断节点是否为黑色(空节点视为黑色)
boolean isBlack(Node node) {
return node == null || node.color == Color.BLACK;
}
/**
* 右旋操作 - 用于维持树的平衡
*
* 操作场景:
* - 当某个节点的左子树高度过高时
* - 插入或删除后导致红黑树性质被破坏时
*
* 操作效果:
* - 将当前节点旋转为其左子节点的右子节点
* - 左子节点变为新的父节点
*
* 示例:
* x y
* / \ / \
* y T3 → T1 x
* / \ / \
* T1 T2 T2 T3
*/
private void rightRotate(Node x) {
Node parent = x.parent; // 保存当前节点的父节点
Node y = x.left; // y是x的左子节点,将成为新的父节点
Node z = y.right; // z是y的右子节点,将成为x的左子节点
// 调整y与x的关系
y.right = x;
x.parent = y;
// 调整x与z的关系
x.left = z;
if (z != null) {
z.parent = x;
}
// 调整y与原父节点parent的关系
y.parent = parent;
if (parent == null) {
root = y; // 如果x是根节点,旋转后y成为新的根节点
} else if (parent.left == x) {
parent.left = y; // 如果x是父节点的左子节点,y替代x
} else {
parent.right = y; // 如果x是父节点的右子节点,y替代x
}
}
/**
* 左旋操作 - 用于维持树的平衡
*
* 操作场景:
* - 当某个节点的右子树高度过高时
* - 插入或删除后导致红黑树性质被破坏时
*
* 操作效果:
* - 将当前节点旋转为其右子节点的左子节点
* - 右子节点变为新的父节点
*
* 示例:
* x y
* / \ / \
* T1 y → x T3
* / \ / \
* T2 T3 T1 T2
*/
private void leftRotate(Node x) {
Node parent = x.parent; // 保存当前节点的父节点
Node y = x.right; // y是x的右子节点,将成为新的父节点
Node z = y.left; // z是y的左子节点,将成为x的右子节点
// 调整y与x的关系
y.left = x;
x.parent = y;
// 调整x与z的关系
x.right = z;
if (z != null) {
z.parent = x;
}
// 调整y与原父节点parent的关系
y.parent = parent;
if (parent == null) {
root = y; // 如果x是根节点,旋转后y成为新的根节点
} else if (parent.left == x) {
parent.left = y; // 如果x是父节点的左子节点,y替代x
} else {
parent.right = y; // 如果x是父节点的右子节点,y替代x
}
}
/**
* 插入操作 - 向红黑树中插入新节点
*
* 步骤:
* 1. 按照二叉搜索树的方式插入新节点(将新节点插入到合适的叶子位置)
* 2. 将新节点着色为红色
* 3. 修复因插入导致的红黑树性质破坏(如果父节点是红色)
*/
public void put(int key, Object value) {
Node p = root;
Node parent = null;
// 1. 找到插入位置(二叉搜索树的插入逻辑)
while (p != null) {
parent = p;
if (key < p.key) {
p = p.left; // 小于当前节点,向左子树查找
} else if (p.key < key) {
p = p.right; // 大于当前节点,向右子树查找
} else {
p.value = value; // 键已存在,更新值
return;
}
}
// 2. 创建新节点并连接到父节点
Node inserted = new Node(key, value);
size++;
if (parent == null) {
root = inserted; // 树为空,新节点成为根节点
} else if (key < parent.key) {
parent.left = inserted; // 插入到左子节点
inserted.parent = parent;
} else {
parent.right = inserted; // 插入到右子节点
inserted.parent = parent;
}
// 3. 修复红黑树性质(处理红红冲突)
fixRedRed(inserted);
}
/**
* 修复插入导致的红红冲突
*
* 插入后如果父节点是红色,会违反红黑树性质4(红色节点的子节点必须是黑色)
* 此时需要进行调整,主要有以下几种情况:
*/
private void fixRedRed(Node x) {
// 情况1: 插入节点是根节点,直接将其变黑即可
if (x == root) {
x.color = Color.BLACK;
return;
}
// 情况2: 插入节点的父节点是黑色,无需调整(性质4未被破坏)
if (isBlack(x.parent)) {
return;
}
Node parent = x.parent; // 父节点
Node uncle = x.uncle(); // 叔叔节点
Node grandparent = parent.parent; // 祖父节点
// 情况3: 父节点和叔叔节点都是红色
// 处理方式: 将父节点和叔叔节点变黑,祖父节点变红,然后递归处理祖父节点
if (isRed(uncle)) {
parent.color = Color.BLACK;
uncle.color = Color.BLACK;
grandparent.color = Color.RED;
fixRedRed(grandparent); // 递归处理祖父节点,可能会引发上层的调整
return;
}
// 情况4: 父节点是红色,叔叔节点是黑色(需要旋转和变色)
if (parent.isLeftChild()) {
if (x.isLeftChild()) {
// 情况4.1: LL结构(父节点是左子节点,插入节点也是左子节点)
// 处理方式: 右旋祖父节点,然后交换父节点和祖父节点的颜色
parent.color = Color.BLACK;
grandparent.color = Color.RED;
rightRotate(grandparent);
} else {
// 情况4.2: LR结构(父节点是左子节点,插入节点是右子节点)
// 处理方式: 先左旋父节点,再右旋祖父节点,最后调整颜色
leftRotate(parent);
x.color = Color.BLACK;
grandparent.color = Color.RED;
rightRotate(grandparent);
}
} else {
if (!x.isLeftChild()) {
// 情况4.3: RR结构(父节点是右子节点,插入节点也是右子节点)
// 处理方式: 左旋祖父节点,然后交换父节点和祖父节点的颜色
parent.color = Color.BLACK;
grandparent.color = Color.RED;
leftRotate(grandparent);
} else {
// 情况4.4: RL结构(父节点是右子节点,插入节点是左子节点)
// 处理方式: 先右旋父节点,再左旋祖父节点,最后调整颜色
rightRotate(parent);
x.color = Color.BLACK;
grandparent.color = Color.RED;
leftRotate(grandparent);
}
}
}
/**
* 删除操作 - 从红黑树中删除指定键的节点
*
* 步骤:
* 1. 找到要删除的节点
* 2. 处理删除的三种情况(无子节点、有一个子节点、有两个子节点)
* 3. 修复因删除可能导致的红黑树性质破坏
*/
public void remove(int key) {
Node deleted = find(key); // 找到要删除的节点
if (deleted == null) {
return; // 节点不存在,直接返回
}
doRemove(deleted); // 执行删除操作
size--;
}
// 查找指定键的节点
private Node find(int key) {
Node p = root;
while (p != null) {
if (key < p.key) {
p = p.left; // 向左子树查找
} else if (p.key < key) {
p = p.right; // 向右子树查找
} else {
return p; // 找到节点
}
}
return null; // 未找到节点
}
// 查找替代节点(用于删除有两个子节点的情况)
private Node findReplaced(Node node) {
if (node.left == null && node.right == null) {
return null; // 无子节点
}
if (node.left == null) {
return node.right; // 只有右子节点
}
if (node.right == null) {
return node.left; // 只有左子节点
}
// 有两个子节点的情况,找右子树的最小节点(中序后继)
Node p = node.right;
while (p.left != null) {
p = p.left;
}
return p;
}
/**
* 执行删除操作
*
* 删除节点时需要考虑三种情况:
* 1. 节点没有子节点
* 2. 节点有一个子节点
* 3. 节点有两个子节点
*/
private void doRemove(Node deleted) {
Node replaced = findReplaced(deleted); // 找到替代节点
Node parent = deleted.parent; // 保存父节点
// 情况1: 没有子节点(直接删除)
if (replaced == null) {
if (deleted == root) {
root = null; // 删除根节点,树变空
} else {
if (isBlack(deleted)) {
// 如果删除的是黑色节点,会导致路径上黑色节点数减少,需要修复双黑问题
fixDoubleBlack(deleted);
}
// 从父节点断开连接
if (deleted.isLeftChild()) {
parent.left = null;
} else {
parent.right = null;
}
deleted.parent = null; // 断开父节点引用
}
return;
}
// 情况2: 有一个子节点(用子节点替换当前节点)
if (deleted.left == null || deleted.right == null) {
if (deleted == root) {
// 如果删除的是根节点,用子节点替换根节点的值
root.key = replaced.key;
root.value = replaced.value;
root.left = root.right = null;
} else {
// 将子节点直接连接到父节点
if (deleted.isLeftChild()) {
parent.left = replaced;
} else {
parent.right = replaced;
}
replaced.parent = parent;
// 断开被删除节点的连接
deleted.left = deleted.right = deleted.parent = null;
// 处理颜色
if (isBlack(deleted) && isBlack(replaced)) {
// 如果删除的是黑色节点,且替换节点也是黑色,需要修复双黑问题
fixDoubleBlack(replaced);
} else if (isRed(replaced)) {
// 如果替换节点是红色,将其变为黑色(保持黑色节点数不变)
replaced.color = Color.BLACK;
}
}
return;
}
// 情况3: 有两个子节点
// 处理方式: 找到右子树的最小节点(中序后继),与当前节点交换值,然后删除后继节点
int keyTemp = deleted.key;
Object valueTemp = deleted.value;
deleted.key = replaced.key;
deleted.value = replaced.value;
replaced.key = keyTemp;
replaced.value = valueTemp;
// 递归删除替代节点(此时替代节点的键值已与被删除节点交换)
doRemove(replaced);
}
/**
* 修复删除导致的双黑问题
*
* 当删除一个黑色节点时,会导致经过该节点的路径上黑色节点数减少,违反性质5
* 此时需要进行调整,主要有以下几种情况:
*/
private void fixDoubleBlack(Node x) {
// 情况1: 双黑节点是根节点,直接返回(根节点的黑色高度减1不影响性质5)
if (x == root) {
return;
}
Node parent = x.parent; // 父节点
Node sibling = x.sibling(); // 兄弟节点
// 情况2: 兄弟节点是红色
// 处理方式: 通过旋转将兄弟节点变为黑色,转换为后续情况处理
if (isRed(sibling)) {
if (x.isLeftChild()) {
leftRotate(parent); // 左旋父节点
} else {
rightRotate(parent); // 右旋父节点
}
parent.color = Color.RED; // 父节点变红
sibling.color = Color.BLACK; // 兄弟节点变黑
fixDoubleBlack(x); // 继续处理,转换为兄弟节点为黑色的情况
return;
}
// 情况3: 兄弟节点是黑色
if (sibling != null) {
// 情况3.1: 兄弟节点的两个子节点都是黑色
if (isBlack(sibling.left) && isBlack(sibling.right)) {
sibling.color = Color.RED; // 兄弟节点变红
if (isRed(parent)) {
parent.color = Color.BLACK; // 父节点变黑,问题解决
} else {
fixDoubleBlack(parent); // 向上传播双黑问题
}
}
// 情况3.2: 兄弟节点的子节点中至少有一个是红色
else {
// 情况3.2.1: 兄弟节点是左子节点
if (sibling.isLeftChild()) {
// 情况3.2.1.1: 左侄子是红色(LL情况)
if (isRed(sibling.left)) {
rightRotate(parent); // 右旋父节点
sibling.left.color = Color.BLACK; // 左侄子变黑
sibling.color = parent.color; // 兄弟节点变为父节点颜色
parent.color = Color.BLACK; // 父节点变黑
}
// 情况3.2.1.2: 右侄子是红色(LR情况)
else {
sibling.right.color = Color.BLACK; // 右侄子变黑
leftRotate(sibling); // 左旋兄弟节点
rightRotate(parent); // 右旋父节点
sibling.color = parent.color; // 兄弟节点变为父节点颜色
parent.color = Color.BLACK; // 父节点变黑
}
}
// 情况3.2.2: 兄弟节点是右子节点
else {
// 情况3.2.2.1: 右侄子是红色(RR情况)
if (isRed(sibling.right)) {
leftRotate(parent); // 左旋父节点
sibling.right.color = Color.BLACK; // 右侄子变黑
sibling.color = parent.color; // 兄弟节点变为父节点颜色
parent.color = Color.BLACK; // 父节点变黑
}
// 情况3.2.2.2: 左侄子是红色(RL情况)
else {
sibling.left.color = Color.BLACK; // 左侄子变黑
rightRotate(sibling); // 右旋兄弟节点
leftRotate(parent); // 左旋父节点
sibling.color = parent.color; // 兄弟节点变为父节点颜色
parent.color = Color.BLACK; // 父节点变黑
}
}
}
}
// 情况4: 兄弟节点为空(实际上不会出现,因为触发双黑时兄弟节点不会为null)
else {
fixDoubleBlack(parent); // 向上传播双黑问题
}
}
// 判断键是否存在
public boolean contains(int key) {
return find(key) != null;
}
// 获取键对应的值
public Object get(int key) {
Node node = find(key);
return node != null ? node.value : null;
}
// 返回树的大小
public int size() {
return size;
}
// 判断树是否为空
public boolean isEmpty() {
return root == null;
}
// 中序遍历(升序遍历)
public void inorderTraversal() {
inorderTraversal(root);
System.out.println();
}
private void inorderTraversal(Node node) {
if (node != null) {
inorderTraversal(node.left);
System.out.print(node.key + (node.color == Color.RED ? "R " : "B "));
inorderTraversal(node.right);
}
}
// 层序遍历(用于调试)
public void levelOrderTraversal() {
if (root == null) {
System.out.println("树为空");
return;
}
Queue<Node> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
Node node = queue.poll();
System.out.print(node + " ");
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
}
System.out.println();
}
// 验证红黑树的性质(用于调试)
public boolean validate() {
if (root == null) {
return true;
}
// 性质2: 根节点是黑色
if (isRed(root)) {
System.out.println("性质2不满足:根节点是红色");
return false;
}
// 验证每个节点的红黑性质
return validateNode(root) && checkBlackHeight(root) != -1;
}
// 验证节点的红黑性质(性质4)
private boolean validateNode(Node node) {
if (node == null) {
return true;
}
// 性质4: 红色节点的子节点必须是黑色
if (isRed(node)) {
if (isRed(node.left) || isRed(node.right)) {
System.out.println("性质4不满足:红色节点 " + node.key + " 的子节点是红色");
return false;
}
}
return validateNode(node.left) && validateNode(node.right);
}
// 检查从该节点到所有叶子节点的黑色节点数目是否相同(性质5)
private int checkBlackHeight(Node node) {
if (node == null) {
return 1; // 空节点的黑色高度为1
}
int leftHeight = checkBlackHeight(node.left);
int rightHeight = checkBlackHeight(node.right);
if (leftHeight == -1 || rightHeight == -1) {
return -1; // 子树不满足性质5
}
if (leftHeight != rightHeight) {
System.out.println("性质5不满足:节点 " + node.key + " 的左右子树黑色高度不同");
return -1;
}
return leftHeight + (isBlack(node) ? 1 : 0); // 当前节点为黑色时,高度加1
}
// 测试示例
public static void main(String[] args) {
RedBlackTree tree = new RedBlackTree();
// 插入测试
System.out.println("插入测试:");
tree.put(5, "A");
tree.put(3, "B");
tree.put(7, "C");
tree.put(2, "D");
tree.put(4, "E");
tree.put(6, "F");
tree.put(8, "G");
System.out.println("插入后的中序遍历:");
tree.inorderTraversal(); // 2B 3R 4B 5B 6B 7R 8B
System.out.println("层序遍历:");
tree.levelOrderTraversal(); // 5B 3R 7R 2B 4B 6B 8B
System.out.println("验证红黑树性质:" + (tree.validate() ? "通过" : "失败"));
// 删除测试
System.out.println("\n删除节点 3 后:");
tree.remove(3);
tree.inorderTraversal(); // 2B 4B 5B 6B 7R 8B
tree.levelOrderTraversal(); // 5B 4B 7R 2B 6B 8B
System.out.println("验证红黑树性质:" + (tree.validate() ? "通过" : "失败"));
}
}
```
### 红黑树核心概念详解
#### 红黑树的五大性质
1. **每个节点要么是红色,要么是黑色**
- 这是红黑树的基本属性,通过颜色来维护树的平衡
2. **根节点是黑色**
- 根节点必须为黑色,确保从根到任意叶子的路径上黑色节点数正确
3. **所有叶子节点(NIL节点,空节点)是黑色**
- 叶子节点指的是树结构中的空指针,统一视为黑色节点
4. **如果一个节点是红色的,则它的两个子节点都是黑色的**
- 防止连续的红色节点出现,确保树的路径不会出现过长的红色节点链
5. **对每个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点**
- 称为“黑色高度”,确保树的任何路径不会比其他路径长出两倍,保证了O(log n)的时间复杂度
#### 插入操作的核心逻辑
插入操作分为三个主要步骤:
1. **二叉搜索树插入**
- 按照普通二叉搜索树的方式找到插入位置
2. **颜色设置**
- 将新插入的节点设置为红色(这样更不容易违反红黑树性质)
3. **修复红黑树性质**
- 如果父节点是红色,需要进行调整:
- **情况1**:插入节点是根节点,直接变黑
- **情况2**:父节点是黑色,无需调整
- **情况3**:父节点和叔叔节点都是红色,通过变色处理
- **情况4**:父节点是红色,叔叔节点是黑色,通过旋转和变色处理
#### 删除操作的核心逻辑
删除操作同样分为三个主要步骤:
1. **找到要删除的节点**
- 与普通二叉搜索树相同
2. **处理删除的三种情况**
- 无子节点:直接删除
- 有一个子节点:用子节点替换
- 有两个子节点:用右子树的最小节点替换
3. **修复红黑树性质**
- 如果删除的是黑色节点,可能导致路径上黑色节点数减少,需要修复:
- **情况1**:双黑节点是根节点,直接返回
- **情况2**:兄弟节点是红色,通过旋转转换为后续情况
- **情况3**:兄弟节点是黑色,根据侄子节点颜色进行旋转和变色处理
#### 旋转操作的作用
旋转操作是红黑树维持平衡的关键:
- **左旋**:将某个节点的右子节点提升为父节点,原节点变为新父节点的左子节点
- **右旋**:将某个节点的左子节点提升为父节点,原节点变为新父节点的右子节点
旋转操作不会改变二叉搜索树的性质(左子树节点值 < 根节点值 < 右子树节点值),但可以调整树的结构,从而维护红黑树的五大性质。
技术栈
aop
### **Spring AOP 深入解析**
Spring AOP 是一种强大的编程范式,它通过**代理模式**和**字节码增强**技术实现了非侵入式的代码增强。下面我将从技术实现、使用场景、高级特性等多个维度深入讲解。
### **一、技术实现原理**
#### 1. **代理机制对比**
Spring AOP 支持两种代理方式:
- **JDK 动态代理**
- **基于接口**:必须实现至少一个接口
- **原理**:Java 反射机制,运行时创建接口实现类
- **性能**:调用时反射,首次稍慢
- ```java
UserService proxy = (UserService) Proxy.newProxyInstance(
classLoader,
new Class[]{UserService.class},
invocationHandler
);
```
- **CGLIB 代理**
- **基于类**:通过继承目标类生成子类
- **原理**:ASM 字节码库,修改字节码生成子类
- **性能**:生成类稍慢,但调用快(无需反射)
- **限制**:无法代理 final 类/方法(因无法继承)
**选择策略**:
```
if (目标对象实现了接口) {
使用 JDK 代理;
} else {
使用 CGLIB 代理;
}
// 可通过 @EnableAspectJAutoProxy(proxyTargetClass = true) 强制使用 CGLIB
```
#### 2. **织入过程详解**
Spring AOP 的织入发生在**运行时**,流程如下:
1. **Bean 实例化**:Spring 容器创建目标 Bean
2. **AOP 匹配检查**:
- BeanPostProcessor 检测 Bean 是否需要增强
- 通过 AdvisorRegistry 查找匹配的 Pointcut
3. **代理对象生成**:
- JdkDynamicAopProxy 或 CglibAopProxy 创建代理
4. **方法调用拦截**:
- 代理对象拦截调用,执行拦截器链(Interceptor Chain)
### **二、核心组件详解**
#### 1. **切入点表达式(Pointcut Expressions)**
Spring AOP 使用 AspectJ 表达式语言,常见类型:
| 表达式类型 | 示例 | 说明 |
|--------------------|-----------------------------------------|-----------------------------------|
| execution | `execution(* com..*Service.*(..))` | 匹配方法执行连接点 |
| within | `within(com..*Service)` | 匹配指定类型内的方法调用 |
| @annotation | `@annotation(com.example.Loggable)` | 匹配带有指定注解的方法 |
| args | `args(java.io.Serializable)` | 匹配参数类型符合条件的方法 |
| this | `this(com.example.MyInterface)` | 匹配实现了指定接口的代理对象 |
| target | `target(com.example.MyInterface)` | 匹配实现了指定接口的目标对象 |
**组合表达式**:
```java
@Pointcut("execution(* com..*Service.*(..)) && args(username,..)")
public void serviceWithUsername(String username) {}
```
#### 2. **通知类型(Advice)**
通知的执行顺序(环绕通知包含前置和后置):
```
[环绕前置] → [前置通知] → 目标方法执行 → [返回通知/异常通知] → [后置通知] → [环绕后置]
```
**环绕通知示例**:
```java
@Around("serviceMethods()")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
// 前置逻辑
System.out.println("方法执行前");
try {
// 执行目标方法
Object result = joinPoint.proceed();
// 返回后逻辑
System.out.println("方法成功返回");
return result;
} catch (Exception e) {
// 异常处理
System.out.println("方法抛出异常");
throw e;
} finally {
// 后置逻辑
System.out.println("方法最终执行");
}
}
```
### **三、高级特性**
#### 1. **注解驱动的 AOP**
使用 `@Aspect` 和 `@EnableAspectJAutoProxy` 简化配置:
```java
@Aspect
@Component
public class TransactionAspect {
@Before("@annotation(org.springframework.transaction.annotation.Transactional)")
public void beforeTransaction(JoinPoint joinPoint) {
System.out.println("开启事务");
}
@After("@annotation(org.springframework.transaction.annotation.Transactional)")
public void afterTransaction(JoinPoint joinPoint) {
System.out.println("提交/回滚事务");
}
}
```
#### 2. **切入点参数绑定**
通过 `args` 绑定方法参数:
```java
@Pointcut("execution(* com..*Service.*(..)) && args(username,..)")
public void withUsername(String username) {}
@Before("withUsername(username)")
public void beforeWithUsername(JoinPoint joinPoint, String username) {
System.out.println("操作用户名: " + username);
}
```
#### 3. **引入增强(Introduction)**
为现有 Bean 添加新接口实现:
```java
@Aspect
public class AuditAspect {
@DeclareParents(
value = "com.example.service.*",
defaultImpl = AuditLogImpl.class
)
public static AuditLog auditLog; // 引入 AuditLog 接口
}
// 使用
AuditLog auditLog = (AuditLog) applicationContext.getBean("userService");
auditLog.logAction("createUser");
```
### **四、应用场景与最佳实践**
#### 1. **典型应用场景**
- **事务管理**:`@Transactional` 注解本质是 AOP
- **缓存控制**:如 `@Cacheable`、`@CacheEvict`
- **权限验证**:基于 `@PreAuthorize`、`@PostAuthorize`
- **性能监控**:统计方法执行时间
- **异常处理**:统一异常包装
- **日志追踪**:MDC(Mapped Diagnostic Context)集成
#### 2. **最佳实践**
1. **避免循环依赖**:代理对象可能导致 AOP 失效
2. **内部方法调用问题**:AOP 无法拦截对象内部方法调用
3. **性能考量**:避免过度使用 AOP,尤其是复杂的 Pointcut
4. **事务边界**:确保事务性方法是 public 的
5. **调试技巧**:使用 `@EnableAspectJAutoProxy(exposeProxy = true)` 暴露代理对象
### **五、与 AspectJ 的对比**
| 特性 | Spring AOP | AspectJ |
|--------------------|--------------------------------|---------------------------------|
| 实现方式 | 运行时动态代理 | 编译时/类加载时织入 |
| 对 final 类/方法支持| 不支持(无法代理 final) | 支持(修改字节码) |
| 切入点类型 | 方法执行(Method Execution) | 支持更多(字段访问、构造函数等)|
| 性能 | 稍低(运行时生成代理) | 更高(静态织入) |
| 使用复杂度 | 简单(基于 Spring 容器) | 复杂(需额外编译步骤) |
### **六、常见问题与解决方案**
#### 1. **AOP 不生效问题**
- **原因**:内部方法调用、final 方法、代理类型不匹配
- **解决方案**:
- 通过 `AopContext.currentProxy()` 获取代理对象
- 避免 final 方法
- 检查 `proxyTargetClass` 配置
#### 2. **性能优化**
- 使用 CGLIB 代理(减少反射调用)
- 避免复杂切入点表达式
- 缓存频繁调用的代理对象
#### 3. **调试技巧**
- 启用调试日志:`logging.level.org.springframework.aop=DEBUG`
- 使用 `@Order` 注解控制通知执行顺序
- 利用 IDEA 的 AOP 视图插件可视化切面
### **七、实战案例:自定义权限校验注解**
1. **定义注解**:
```java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresPermission {
String value();
}
```
2. **创建切面**:
```java
@Aspect
@Component
public class PermissionAspect {
@Before("@annotation(com.example.RequiresPermission)")
public void checkPermission(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
RequiresPermission annotation = signature.getMethod().getAnnotation(RequiresPermission.class);
String requiredPermission = annotation.value();
// 检查当前用户是否有该权限
if (!SecurityContextHolder.hasPermission(requiredPermission)) {
throw new AccessDeniedException("权限不足");
}
}
}
```
3. **使用注解**:
```java
@Service
public class UserService {
@RequiresPermission("user:delete")
public void deleteUser(Long userId) {
// 删除用户逻辑
}
}
```
### **总结**
Spring AOP 是一种强大的工具,通过**代理机制**和**切入点表达式**,实现了非侵入式的代码增强。理解其核心概念(切面、通知、切入点)和技术实现(JDK/CGLIB 代理)是掌握 AOP 的关键。
**优势**:
- 解耦业务逻辑与通用功能
- 提高代码复用性
- 便于统一管理横切关注点
**局限**:
- 仅支持方法级拦截
- 存在代理创建和反射调用的性能开销
- 复杂切入点表达式可读性较差
合理使用 AOP 可以大幅提升代码质量,但需注意避免滥用。建议从简单的日志、事务场景入手,逐步掌握其高级特性。
aop应用点
AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,它允许开发者在不修改原有代码的前提下,对程序的横切关注点(如日志、事务、权限等)进行模块化处理。以下是AOP的一些典型应用场景:
### 1. **日志记录**
- **场景**:记录方法调用、参数、返回值或异常信息,用于调试、审计或性能监控。
- **优势**:无需在每个方法中手动添加日志代码,统一管理日志逻辑。
- **示例**:
- 记录用户登录、操作记录。
- 跟踪系统性能瓶颈(如方法执行时间)。
```java
// AOP实现日志切面示例
public aspect LoggingAspect {
// 在所有Service方法执行前记录日志
before(): execution(* com.example.service.*.*(..)) {
System.out.println("Method called: " + thisJoinPoint.getSignature().getName());
}
}
```
### 2. **事务管理**
- **场景**:确保数据库操作的原子性,如批量更新、转账等操作。
- **优势**:通过AOP自动管理事务的开始、提交或回滚,减少重复代码。
- **示例**:
- Spring框架中的`@Transactional`注解就是基于AOP实现的。
```java
// AOP实现事务切面示例
public aspect TransactionAspect {
// 在标记@Transactional的方法执行前后管理事务
around(): @annotation(org.springframework.transaction.annotation.Transactional) {
try {
startTransaction();
Object result = proceed();
commitTransaction();
return result;
} catch (Exception e) {
rollbackTransaction();
throw e;
}
}
}
```
### 3. **权限验证**
- **场景**:在方法调用前检查用户权限,防止未授权访问。
- **优势**:将权限逻辑与业务逻辑分离,提高代码可维护性。
- **示例**:
- 基于角色的访问控制(RBAC)。
- 验证用户令牌有效性。
```java
// AOP实现权限验证示例
public aspect AuthAspect {
// 在所有Controller方法执行前验证权限
before(): execution(* com.example.controller.*.*(..)) {
if (!hasPermission()) {
throw new SecurityException("Access denied");
}
}
}
```
### 4. **缓存管理**
- **场景**:在方法调用前后自动处理缓存,提高系统性能。
- **优势**:避免在业务代码中嵌入复杂的缓存逻辑。
- **示例**:
- 方法调用前检查缓存,存在则直接返回;否则执行方法并缓存结果。
```java
// AOP实现缓存切面示例
public aspect CacheAspect {
// 在标记@Cacheable的方法执行前检查缓存
around(): @annotation(org.springframework.cache.annotation.Cacheable) {
String cacheKey = generateCacheKey(thisJoinPoint);
Object cachedResult = getFromCache(cacheKey);
if (cachedResult != null) {
return cachedResult;
}
Object result = proceed();
putInCache(cacheKey, result);
return result;
}
}
```
### 5. **异常处理**
- **场景**:统一处理业务异常,如转换为HTTP响应码、记录错误日志。
- **优势**:集中管理异常处理逻辑,减少代码冗余。
- **示例**:
- 将业务异常转换为友好的错误信息返回给客户端。
```java
// AOP实现异常处理示例
public aspect ExceptionHandlingAspect {
// 捕获Service层的异常并处理
afterThrowing(Exception e): execution(* com.example.service.*.*(..)) {
logError("Service error: " + e.getMessage());
// 转换为自定义异常或HTTP响应
throw new BusinessException("Operation failed", e);
}
}
```
### 6. **性能监控**
- **场景**:统计方法执行时间,分析系统性能瓶颈。
- **优势**:非侵入式地收集性能数据,无需修改业务代码。
- **示例**:
- 记录数据库查询、远程调用的耗时。
```java
// AOP实现性能监控示例
public aspect PerformanceAspect {
// 统计所有DAO方法的执行时间
around(): execution(* com.example.dao.*.*(..)) {
long startTime = System.currentTimeMillis();
Object result = proceed();
long endTime = System.currentTimeMillis();
System.out.println("Method " + thisJoinPoint.getSignature().getName()
+ " took " + (endTime - startTime) + "ms");
return result;
}
}
```
### 7. **分布式追踪**
- **场景**:在微服务架构中跟踪请求调用链路,排查问题。
- **优势**:自动注入追踪ID,串联跨服务的调用日志。
- **示例**:
- 使用Sleuth或Zipkin等工具实现分布式追踪。
### 8. **参数校验**
- **场景**:在方法调用前验证输入参数的合法性。
- **优势**:将校验逻辑与业务逻辑分离,提高代码可读性。
- **示例**:
- 验证用户输入是否符合格式要求(如邮箱、手机号)。
### 9. **资源管理**
- **场景**:自动管理资源的获取和释放,如文件、数据库连接等。
- **优势**:避免资源泄漏,简化资源管理代码。
- **示例**:
- 使用`try-with-resources`语句的AOP实现。
### 10. **事件通知**
- **场景**:在方法执行前后触发事件,如发送消息、更新统计数据。
- **优势**:解耦事件触发与业务逻辑,提高系统灵活性。
- **示例**:
- 用户注册成功后发送欢迎邮件。
### AOP的优势总结
- **代码复用**:将横切关注点集中管理,避免重复代码。
- **可维护性**:业务逻辑与辅助功能分离,降低代码耦合度。
- **灵活性**:通过配置即可添加或移除功能,无需修改原有代码。
- **扩展性**:易于添加新的横切关注点(如添加新的日志策略)。
### AOP的实现方式
- **静态代理**:编译时通过字节码增强实现(如AspectJ)。
- **动态代理**:运行时通过反射或字节码生成实现(如Spring AOP)。
- **自定义代理**:手动编写代理类(如JDK动态代理、CGLIB)。
不同场景下可选择不同的实现方式,例如Spring框架中的AOP默认使用动态代理,适合大多数企业应用场景。
背
AOP(面向切面编程)通过分离横切关注点(如日志、事务)与业务逻辑,实现代码解耦。面试中可从两方面回答:
**应用场景**:
1. **日志记录**:统一记录方法调用轨迹、参数及异常,避免业务代码侵入;
2. **事务管理**:通过@Transactional注解自动控制数据库事务边界;
3. **权限校验**:在接口调用前验证用户权限,防止未授权访问;
4. **缓存优化**:自动处理缓存的读取与写入,提升系统性能;
5. **异常处理**:统一捕获业务异常并转换为标准响应格式。
**底层理解**:
AOP的核心是通过**切面(Aspect)** 拦截**连接点(Join Point)**,利用**通知(Advice)** 在切入点(Pointcut)处执行额外逻辑。实现方式包括:
- **动态代理**(如Spring AOP):基于JDK Proxy(接口代理)或CGLIB(类代理),在运行时生成代理对象;
- **静态代理**(如AspectJ):通过编译期字节码增强实现,性能更优。
其本质是将重复逻辑抽象为切面,通过代理机制实现“对扩展开放,对修改关闭”,核心优势在于提升代码复用性与可维护性。