Update: cache module, security modules and build

This commit is contained in:
likingcode
2026-03-18 15:18:41 +08:00
parent 4a0737ddeb
commit d257e3595d
48 changed files with 2076 additions and 822 deletions

View File

@@ -3,173 +3,200 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>IoC 容器学习 - Spring Boot</title>
<title>IoC 生命周期可视化实验室 - Spring Boot</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; }
.container { max-width: 1400px; margin: 0 auto; padding: 20px; }
.header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 30px 20px; text-align: center; margin-bottom: 20px; border-radius: 10px; }
.header h1 { font-size: 2em; }
.nav { display: flex; gap: 10px; margin-bottom: 20px; flex-wrap: wrap; justify-content: center; }
.nav a { padding: 10px 20px; background: white; border-radius: 20px; text-decoration: none; color: #333; font-size: 0.9em; }
.nav a:hover, .nav a.active { background: #667eea; color: white; }
.card { background: white; border-radius: 10px; padding: 20px; margin-bottom: 20px; box-shadow: 0 2px 10px rgba(0,0,0,0.08); }
.card h3 { color: #667eea; margin-bottom: 15px; border-bottom: 2px solid #eee; padding-bottom: 10px; }
.grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(320px,1fr)); gap:16px; }
.concept-box { background: #f8f9fa; border-left: 4px solid #667eea; padding: 15px; margin: 10px 0; border-radius: 5px; }
.concept-box h4 { color: #333; margin-bottom: 10px; }
.lab { background:#fff7e6; border-left:4px solid #fa8c16; padding:15px; border-radius:8px; margin-bottom:20px; }
.btn { padding: 10px 20px; background: #667eea; color: white; border: none; border-radius: 5px; cursor: pointer; margin: 5px; }
.btn:hover { background: #5a6fd6; }
.btn-secondary { background: #6c757d; }
.result { background: #1e1e1e; color: #d4d4d4; padding: 15px; border-radius: 5px; margin-top: 10px; font-family: monospace; font-size: 0.9em; overflow-x: auto; max-height: 400px; overflow-y: auto; }
.btn-purple { background:#8e44ad; }
.result { background: #1e1e1e; color: #d4d4d4; padding: 15px; border-radius: 5px; margin-top: 10px; font-family: monospace; font-size: 0.9em; overflow-x: auto; max-height: 420px; overflow-y: auto; white-space: pre-wrap; }
table { width: 100%; border-collapse: collapse; margin: 10px 0; }
th, td { padding: 10px; text-align: left; border-bottom: 1px solid #eee; }
th { background: #f8f9fa; font-weight: 600; }
tr:hover { background: #f8f9fa; }
.badge { padding: 4px 8px; border-radius: 4px; font-size: 0.8em; }
.badge-primary { background: #667eea; color: white; }
.badge-success { background: #28a745; color: white; }
.badge-warning { background: #ffc107; color: #333; }
.timeline { background:#0f172a; color:#e2e8f0; padding:14px; border-radius:8px; max-height:420px; overflow:auto; }
.timeline-item { padding:10px; border-left:3px solid #60a5fa; margin:10px 0; background:rgba(255,255,255,0.04); }
.small { color:#666; font-size:0.9em; line-height:1.7; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>📦 IoC 容器学习</h1>
<p>控制反转 (Inversion of Control) 与依赖注入 (Dependency Injection)</p>
<h1>📦 IoC 生命周期可视化实验室</h1>
<p>把“类加载”“Bean 创建”“单例/多例/懒加载”拆开看,自己动手触发、自己观察结果。</p>
</div>
<div class="nav">
<a href="index.html">🏠 首页</a>
<a href="ioc.html" class="active">📦 IoC</a>
<a href="verify-lab.html">🩺 修复验证</a>
<a href="aop.html">🔪 AOP</a>
<a href="mybatis.html">💾 MyBatis</a>
<a href="transaction.html">🔄 事务</a>
<a href="users.html">👥 用户</a>
<a href="api.html">🔌 API</a>
</div>
<div class="card">
<h3>📚 核心概念</h3>
<div class="concept-box">
<h4>什么是 IoC</h4>
<p><strong>控制反转 (Inversion of Control)</strong>:将对象的创建和管理交给 Spring 容器,而不是由开发者手动创建。</p>
<p><strong>依赖注入 (Dependency Injection)</strong>IoC 的一种实现方式通过构造器、Setter 或字段将依赖注入到对象中。</p>
<div class="lab">
<strong>🧪 实验任务卡</strong>
<ul style="padding-left:20px; line-height:1.8; margin-top:8px;">
<li>先点“加载总览”,搞清楚 JVM 类加载 和 Spring Bean 生命周期不是一回事</li>
<li>再点“比较三种作用域”,对比 singleton / prototype / lazy singleton 的实例 ID 和 hashCode</li>
<li>然后分别连续点击“获取单例 / 获取多例 / 获取懒加载单例”,观察时间线变化</li>
<li>最后重置时间线,再重新做一遍,看看第一次触发和第二次触发的区别</li>
</ul>
</div>
<div class="grid">
<div class="card">
<h3>📚 核心概念总览</h3>
<button class="btn" onclick="loadOverview()">加载总览</button>
<div id="overviewResult" class="result">点击按钮查看“类加载 vs Bean 生命周期”...</div>
</div>
<div class="concept-box">
<h4>为什么用 IoC</h4>
<ul>
<li><strong>解耦</strong>:对象之间不直接依赖,通过接口交互</li>
<li><strong>可测试</strong>:方便使用 Mock 对象进行单元测试</li>
<li><strong>可维护</strong>:集中管理对象生命周期</li>
<li><strong>AOP 支持</strong>:便于实现切面编程</li>
</ul>
<div class="card">
<h3>🆚 作用域对比实验</h3>
<button class="btn" onclick="compareScopes()">比较三种作用域</button>
<button class="btn btn-secondary" onclick="resetTimeline()">重置时间线</button>
<div class="small">观察重点singleton 两次获取是否同一实例prototype 为什么每次都变lazy singleton 第一次获取前是否就已经创建?</div>
<div id="compareResult" class="result"></div>
</div>
</div>
<div class="card">
<h3>🎯 单点触发实验</h3>
<button class="btn" onclick="inspect('singleton')">获取单例 Bean</button>
<button class="btn btn-purple" onclick="inspect('prototype')">获取多例 Bean</button>
<button class="btn btn-secondary" onclick="inspect('lazy')">获取懒加载单例</button>
<div id="inspectResult" class="result">点击上面的按钮,观察实例 ID / hashCode / 访问次数如何变化...</div>
</div>
<div class="grid">
<div class="card">
<h3>🕰️ 生命周期时间线</h3>
<button class="btn" onclick="loadTimeline()">刷新时间线</button>
<div id="timelineResult" class="timeline">时间线加载中...</div>
</div>
<div class="card">
<h3>📊 Bean 作用域解释</h3>
<table>
<tr><th>作用域</th><th>创建时机</th><th>实例特点</th></tr>
<tr><td><span class="badge badge-primary">singleton</span></td><td>通常容器启动时</td><td>全局一个实例,反复获取同一个对象</td></tr>
<tr><td><span class="badge badge-success">prototype</span></td><td>每次 getBean 时</td><td>每次都是新实例,不进单例池</td></tr>
<tr><td><span class="badge badge-warning">lazy singleton</span></td><td>第一次真正使用时</td><td>启动不创建,首次获取才创建,之后复用</td></tr>
</table>
<div class="concept-box">
<h4>记住这句话</h4>
<p><strong>类加载 ≠ Bean 创建Bean 创建 ≠ 每次请求都 new。</strong></p>
</div>
</div>
</div>
<div class="card">
<h3>🔍 查看所有 Bean</h3>
<p>Spring 容器中管理的所有 Bean 对象</p>
<button class="btn" onclick="loadBeans()">刷新 Bean 列表</button>
<button class="btn btn-secondary" onclick="document.getElementById('beansResult').innerHTML=''">清空</button>
<div id="beansResult" class="result"></div>
</div>
<div class="card">
<h3>📊 Bean 作用域</h3>
<table>
<tr><th>作用域</th><th>说明</th><th>使用场景</th></tr>
<tr><td><span class="badge badge-primary">singleton</span></td><td>默认,整个应用只有一个实例</td><td>无状态的服务、配置类</td></tr>
<tr><td><span class="badge badge-success">prototype</span></td><td>每次请求都创建新实例</td><td>有状态的对象</td></tr>
<tr><td><span class="badge badge-warning">request</span></td><td>每个 HTTP 请求一个实例</td><td>Web 应用</td></tr>
<tr><td><span class="badge badge-warning">session</span></td><td>每个 HTTP 会话一个实例</td><td>用户会话数据</td></tr>
</table>
<button class="btn" onclick="testScopes()">测试作用域</button>
<div id="scopesResult" class="result"></div>
</div>
<div class="card">
<h3>⚡ 性能统计</h3>
<p>实时监控方法执行时间和调用次数</p>
<button class="btn" onclick="loadPerformance()">刷新统计</button>
<button class="btn btn-secondary" onclick="resetPerformance()">重置统计</button>
<div id="performanceResult" class="result"></div>
</div>
<div class="card">
<h3>💉 依赖注入方式对比</h3>
<table>
<tr><th>方式</th><th>优点</th><th>缺点</th><th>推荐度</th></tr>
<tr><td>构造器注入</td><td>明确依赖、不可变、易测试</td><td>参数多时代码长</td><td>⭐⭐⭐⭐⭐</td></tr>
<tr><td>Setter 注入</td><td>可选依赖、灵活</td><td>可能为 null</td><td>⭐⭐⭐</td></tr>
<tr><td>字段注入</td><td>代码简洁</td><td>隐藏依赖、难测试</td><td></td></tr>
</table>
</div>
</div>
<script>
async function loadOverview() {
const result = document.getElementById('overviewResult');
result.textContent = '加载中...';
try {
const res = await fetch('/api/learning/ioc/lifecycle/overview');
const data = await res.json();
result.textContent = JSON.stringify(data, null, 2);
} catch (e) {
result.textContent = '加载失败: ' + e.message;
}
}
async function inspect(scope) {
const result = document.getElementById('inspectResult');
result.textContent = '触发中...';
try {
const res = await fetch(`/api/learning/ioc/lifecycle/inspect/${scope}?trigger=ui-click`);
const data = await res.json();
result.textContent = JSON.stringify(data, null, 2);
loadTimeline();
} catch (e) {
result.textContent = '触发失败: ' + e.message;
}
}
async function compareScopes() {
const result = document.getElementById('compareResult');
result.textContent = '对比中...';
try {
const res = await fetch('/api/learning/ioc/lifecycle/compare');
const data = await res.json();
result.textContent = JSON.stringify(data, null, 2);
loadTimeline();
} catch (e) {
result.textContent = '对比失败: ' + e.message;
}
}
async function loadTimeline() {
const result = document.getElementById('timelineResult');
result.textContent = '加载中...';
try {
const res = await fetch('/api/learning/ioc/lifecycle/timeline');
const data = await res.json();
if (!data.length) {
result.textContent = '暂无时间线事件,先点上面的实验按钮。';
return;
}
result.innerHTML = data.map(item => `
<div class="timeline-item">
<div><strong>#${item.seq}</strong> [${item.scope}] ${item.beanName}</div>
<div>${item.phase}</div>
<div style="color:#94a3b8; margin-top:6px;">${item.detail}</div>
</div>
`).join('');
} catch (e) {
result.textContent = '加载失败: ' + e.message;
}
}
async function resetTimeline() {
await fetch('/api/learning/ioc/lifecycle/reset', { method: 'POST' });
loadTimeline();
}
async function loadBeans() {
const result = document.getElementById('beansResult');
result.textContent = '加载中...';
try {
const res = await fetch('/api/learning/ioc/beans');
const data = await res.json();
result.innerHTML = `<strong>总 Bean 数: ${data.total}</strong>\n\n用户相关 Bean:\n${data.userBeans.map(b => ' - ' + b).join('\n')}`;
result.innerHTML = `<strong>总 Bean 数: ${data.total}</strong>\n\nIoC 学习相关 Bean:\n${data.userBeans.map(b => ' - ' + b).join('\n')}`;
} catch (e) {
result.textContent = '加载失败: ' + e.message;
}
}
async function testScopes() {
const result = document.getElementById('scopesResult');
try {
const res = await fetch('/api/learning/ioc/scopes');
const data = await res.json();
result.innerHTML = JSON.stringify(data, null, 2);
} catch (e) {
result.textContent = '测试失败: ' + e.message;
}
}
async function loadPerformance() {
const result = document.getElementById('performanceResult');
result.textContent = '加载中...';
try {
const res = await fetch('/api/learning/ioc/performance');
const data = await res.json();
if (Object.keys(data).length === 0) {
result.textContent = '暂无性能数据,请先调用一些 API';
return;
}
let html = '<table><tr><th>方法</th><th>调用次数</th><th>错误数</th><th>平均耗时(ms)</th><th>最大耗时(ms)</th></tr>';
for (const [key, val] of Object.entries(data)) {
html += `<tr><td>${key}</td><td>${val.count}</td><td>${val.errors}</td><td>${val.avgMs}</td><td>${val.maxMs}</td></tr>`;
}
html += '</table>';
result.innerHTML = html;
} catch (e) {
result.textContent = '加载失败: ' + e.message;
}
}
async function resetPerformance() {
try {
await fetch('/api/learning/ioc/performance/reset', { method: 'POST' });
loadPerformance();
} catch (e) {
alert('重置失败: ' + e.message);
}
}
loadBeans();
loadPerformance();
loadOverview();
loadTimeline();
</script>
</body>
</html>
</html>