红黑树 uniapp aop

#42

今日计划

1.uniapp

视频课看到p40

示例代码写一点

官网教程看一点

2.小兔鲜儿

day2 视频课随机看

3.Java算法

红黑树 B树 哈希表 以及题目

3h

4.技术栈

aop maven

今天先把ssm 框架的aop看了

Teemo笔记

总结





今日源码

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):通过编译期字节码增强实现,性能更优。  
其本质是将重复逻辑抽象为切面,通过代理机制实现“对扩展开放,对修改关闭”,核心优势在于提升代码复用性与可维护性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值