Files
springboot-scaffold/target/classes/static/aop.html
likingcode c04235c655 feat: Spring Boot 学习脚手架 v2.0
- 新增 IoC 容器学习模块
- 新增 AOP 切面编程学习模块
- 新增 MyBatis 集成学习模块
- 新增事务管理学习模块
- 新增用户/产品/订单 CRUD
- 新增 7 个交互式学习页面
- 集成性能监控切面
2026-03-07 08:37:40 +00:00

159 lines
9.4 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AOP 切面学习 - 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, #f093fb 0%, #f5576c 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: #f5576c; 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: #f5576c; margin-bottom: 15px; border-bottom: 2px solid #eee; padding-bottom: 10px; }
.advice-box { background: #f8f9fa; border-radius: 8px; padding: 15px; margin: 10px 0; border-left: 4px solid; }
.advice-before { border-color: #28a745; }
.advice-after { border-color: #6c757d; }
.advice-returning { border-color: #17a2b8; }
.advice-throwing { border-color: #dc3545; }
.advice-around { border-color: #ffc107; }
.btn { padding: 10px 20px; background: #f5576c; color: white; border: none; border-radius: 5px; cursor: pointer; margin: 5px; }
.btn:hover { background: #e0465b; }
.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; }
.code-block { background: #f4f4f4; padding: 15px; border-radius: 5px; font-family: monospace; font-size: 0.9em; overflow-x: auto; margin: 10px 0; }
.flow-diagram { display: flex; align-items: center; justify-content: center; flex-wrap: wrap; gap: 10px; padding: 20px; background: #f8f9fa; border-radius: 10px; margin: 15px 0; }
.flow-step { padding: 10px 20px; background: white; border-radius: 20px; border: 2px solid #ddd; }
.flow-arrow { font-size: 1.5em; color: #999; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🔪 AOP 切面编程</h1>
<p>Aspect Oriented Programming - 面向切面编程</p>
</div>
<div class="nav">
<a href="index.html">🏠 首页</a>
<a href="ioc.html">📦 IoC</a>
<a href="aop.html" class="active">🔪 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>
<p><strong>AOP (面向切面编程)</strong>:将横切关注点(日志、权限、事务等)从业务逻辑中分离出来,实现模块化。</p>
<div class="flow-diagram">
<div class="flow-step">目标方法</div>
<div class="flow-arrow"></div>
<div class="flow-step">@Before</div>
<div class="flow-arrow"></div>
<div class="flow-step">@Around (前)</div>
<div class="flow-arrow"></div>
<div class="flow-step">方法执行</div>
<div class="flow-arrow"></div>
<div class="flow-step">@Around (后)</div>
<div class="flow-arrow"></div>
<div class="flow-step">@AfterReturning</div>
<div class="flow-arrow"></div>
<div class="flow-step">@After</div>
</div>
</div>
<div class="card">
<h3>🔔 五种通知类型</h3>
<div class="advice-box advice-before">
<h4>@Before - 前置通知</h4>
<p>方法执行前触发,可用于参数校验、日志记录</p>
<div class="code-block">@Before("execution(* com.example.service..*.*(..))")<br>public void before(JoinPoint jp) {<br> log.info("即将执行: " + jp.getSignature().getName());<br>}</div>
</div>
<div class="advice-box advice-after">
<h4>@After - 后置通知</h4>
<p>方法执行后触发(无论是否异常),可用于资源释放</p>
<div class="code-block">@After("execution(* com.example.service..*.*(..))")<br>public void after(JoinPoint jp) {<br> log.info("执行完成: " + jp.getSignature().getName());<br>}</div>
</div>
<div class="advice-box advice-returning">
<h4>@AfterReturning - 返回通知</h4>
<p>方法成功返回后触发,可获取返回值</p>
<div class="code-block">@AfterReturning(pointcut="...", returning="result")<br>public void afterReturning(Object result) {<br> log.info("返回结果: " + result);<br>}</div>
</div>
<div class="advice-box advice-throwing">
<h4>@AfterThrowing - 异常通知</h4>
<p>方法抛出异常后触发,可用于异常处理</p>
<div class="code-block">@AfterThrowing(pointcut="...", throwing="ex")<br>public void afterThrowing(Exception ex) {<br> log.error("发生异常: " + ex.getMessage());<br>}</div>
</div>
<div class="advice-box advice-around">
<h4>@Around - 环绕通知</h4>
<p>完全控制方法执行,可决定是否执行目标方法</p>
<div class="code-block">@Around("execution(* com.example.controller..*.*(..))")<br>public Object around(ProceedingJoinPoint pjp) throws Throwable {<br> long start = System.currentTimeMillis();<br> Object result = pjp.proceed(); // 执行目标方法<br> long cost = System.currentTimeMillis() - start;<br> log.info("耗时: " + cost + "ms");<br> return result;<br>}</div>
</div>
</div>
<div class="card">
<h3>🧪 在线测试</h3>
<p>调用下面的 API然后查看控制台日志观察 AOP 执行顺序</p>
<button class="btn" onclick="testAop()">测试正常执行</button>
<button class="btn" onclick="testAopError()">测试异常通知</button>
<div id="testResult" class="result"></div>
</div>
<div class="card">
<h3>📝 切入点表达式</h3>
<table style="width:100%;border-collapse:collapse;">
<tr style="background:#f8f9fa;"><th style="padding:10px;text-align:left;">表达式</th><th style="padding:10px;text-align:left;">含义</th></tr>
<tr><td style="padding:10px;border-bottom:1px solid #eee;">execution(* *(..))</td><td style="padding:10px;border-bottom:1px solid #eee;">匹配所有方法</td></tr>
<tr><td style="padding:10px;border-bottom:1px solid #eee;">execution(* com.example.service.*.*(..))</td><td style="padding:10px;border-bottom:1px solid #eee;">service包下所有方法</td></tr>
<tr><td style="padding:10px;border-bottom:1px solid #eee;">execution(* com.example.service..*.*(..))</td><td style="padding:10px;border-bottom:1px solid #eee;">service包及子包所有方法</td></tr>
<tr><td style="padding:10px;border-bottom:1px solid #eee;">execution(public * *(..))</td><td style="padding:10px;border-bottom:1px solid #eee;">所有public方法</td></tr>
<tr><td style="padding:10px;border-bottom:1px solid #eee;">@annotation(org.springframework.transaction.annotation.Transactional)</td><td style="padding:10px;border-bottom:1px solid #eee;">带@Transactional的方法</td></tr>
</table>
</div>
</div>
<script>
async function testAop() {
const result = document.getElementById('testResult');
result.textContent = '测试中...';
try {
const res = await fetch('/api/learning/aop/test?message=HelloAOP');
const data = await res.json();
result.innerHTML = `<strong>✅ 测试成功</strong>\n\n${JSON.stringify(data, null, 2)}\n\n💡 提示: 查看服务器控制台,观察 AOP 通知执行顺序`;
} catch (e) {
result.textContent = '测试失败: ' + e.message;
}
}
async function testAopError() {
const result = document.getElementById('testResult');
result.textContent = '测试中...';
try {
const res = await fetch('/api/learning/aop/test-error?error=true');
const data = await res.json();
result.innerHTML = `<strong>✅ 正常返回</strong>\n\n${JSON.stringify(data, null, 2)}`;
} catch (e) {
result.innerHTML = `<strong>❌ 触发异常(预期行为)</strong>\n\n异常信息: ${e.message}\n\n💡 提示: 查看服务器控制台,观察 @AfterThrowing 通知`;
}
}
</script>
</body>
</html>