feat(reflection): add interactive reflection visualization lab

This commit is contained in:
likingcode
2026-03-10 09:52:44 +08:00
parent 4cc94152a9
commit 4a0737ddeb
4 changed files with 281 additions and 0 deletions

View File

@@ -0,0 +1,110 @@
package com.example.scaffold.learning;
import com.example.scaffold.learning.reflection.ReflectionLabTarget;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/learning/reflection")
public class ReflectionLearningController {
@GetMapping("/overview")
public Map<String, Object> overview() {
return Map.of(
"whatIsReflection", List.of(
"运行时检查类、字段、方法、构造器",
"运行时动态创建对象",
"运行时调用方法、读写字段",
"Spring / MyBatis / Jackson 等框架大量依赖反射"
),
"whyItMatters", List.of(
"理解框架为什么能自动装配、自动绑定、自动序列化",
"理解代理、注解扫描、Bean 创建背后的机制",
"理解反射带来的灵活性与性能成本"
)
);
}
@GetMapping("/class-info")
public Map<String, Object> classInfo() throws Exception {
Class<?> clazz = ReflectionLabTarget.class;
Map<String, Object> result = new LinkedHashMap<>();
result.put("className", clazz.getName());
result.put("simpleName", clazz.getSimpleName());
result.put("constructors", Arrays.stream(clazz.getDeclaredConstructors()).map(Constructor::toString).toList());
result.put("fields", Arrays.stream(clazz.getDeclaredFields()).map(Field::toString).toList());
result.put("methods", Arrays.stream(clazz.getDeclaredMethods()).map(Method::toString).toList());
return result;
}
@GetMapping("/instantiate")
public Map<String, Object> instantiate(@RequestParam(defaultValue = "ref-user") String name,
@RequestParam(defaultValue = "5") int count) throws Exception {
Constructor<ReflectionLabTarget> constructor = ReflectionLabTarget.class.getDeclaredConstructor(String.class, int.class);
ReflectionLabTarget obj = constructor.newInstance(name, count);
return Map.of(
"instance", obj.getClass().getName(),
"identityHashCode", System.identityHashCode(obj),
"name", obj.getName(),
"count", obj.getCount(),
"message", "通过反射构造器动态创建对象成功"
);
}
@GetMapping("/field-access")
public Map<String, Object> fieldAccess(@RequestParam(defaultValue = "changed-by-reflection") String value) throws Exception {
ReflectionLabTarget obj = new ReflectionLabTarget();
Field field = ReflectionLabTarget.class.getDeclaredField("name");
field.setAccessible(true);
Object before = field.get(obj);
field.set(obj, value);
Object after = field.get(obj);
return Map.of(
"field", field.getName(),
"before", before,
"after", after,
"message", "私有字段已通过反射修改"
);
}
@GetMapping("/method-call")
public Map<String, Object> methodCall(@RequestParam(defaultValue = "你好") String prefix) throws Exception {
ReflectionLabTarget obj = new ReflectionLabTarget("Spring", 2);
Method publicMethod = ReflectionLabTarget.class.getDeclaredMethod("greet", String.class);
Object publicResult = publicMethod.invoke(obj, prefix);
Method privateMethod = ReflectionLabTarget.class.getDeclaredMethod("secretFormula", String.class);
privateMethod.setAccessible(true);
Object privateResult = privateMethod.invoke(obj, "debug");
Method staticMethod = ReflectionLabTarget.class.getDeclaredMethod("staticInfo");
Object staticResult = staticMethod.invoke(null);
return Map.of(
"publicMethodResult", publicResult,
"privateMethodResult", privateResult,
"staticMethodResult", staticResult,
"message", "公开方法、私有方法、静态方法都已通过反射调用"
);
}
@GetMapping("/why-frameworks-use-it")
public Map<String, Object> whyFrameworksUseIt() {
return Map.of(
"spring", List.of("扫描注解", "创建 Bean", "依赖注入", "调用生命周期方法"),
"jackson", List.of("读取字段", "调用 getter/setter", "对象序列化/反序列化"),
"mybatis", List.of("结果集映射到对象", "动态代理 Mapper 接口"),
"warning", "反射很强大,但要理解它的性能成本、可读性成本和封装破坏风险"
);
}
}

View File

@@ -0,0 +1,39 @@
package com.example.scaffold.learning.reflection;
import java.util.List;
import java.util.Map;
public class ReflectionLabTarget {
private String name = "default-name";
private int count = 3;
private final List<String> tags = List.of("spring", "reflection", "proxy");
public ReflectionLabTarget() {
}
public ReflectionLabTarget(String name, int count) {
this.name = name;
this.count = count;
}
public String greet(String prefix) {
return prefix + ", " + name + " x" + count;
}
private String secretFormula(String input) {
return "secret:" + input + ":" + (count * 7);
}
public static Map<String, Object> staticInfo() {
return Map.of(
"topic", "reflection",
"message", "静态方法也可以通过反射调用"
);
}
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getCount() { return count; }
public void setCount(int count) { this.count = count; }
public List<String> getTags() { return tags; }
}

View File

@@ -98,6 +98,7 @@
<a href="transaction.html">🔄 事务管理</a> <a href="transaction.html">🔄 事务管理</a>
<a href="users.html">👥 用户管理</a> <a href="users.html">👥 用户管理</a>
<a href="advanced.html">🚀 高级功能</a> <a href="advanced.html">🚀 高级功能</a>
<a href="reflection.html">🪞 反射实验室</a>
<a href="auth-lab.html">🔐 鉴权实验室</a> <a href="auth-lab.html">🔐 鉴权实验室</a>
<a href="verify-lab.html">🩺 修复验证实验室</a> <a href="verify-lab.html">🩺 修复验证实验室</a>
<a href="api.html">🔌 API 测试</a> <a href="api.html">🔌 API 测试</a>
@@ -177,6 +178,18 @@
<a href="advanced.html" class="btn">进阶学习 →</a> <a href="advanced.html" class="btn">进阶学习 →</a>
</div> </div>
<div class="card">
<h3>🪞 反射实验室</h3>
<p>动态查看类结构、通过构造器创建对象、修改私有字段、调用私有方法,理解 Spring 等框架底层为什么依赖反射。</p>
<ul class="feature-list">
<li><span class="icon"></span>类/字段/方法元信息</li>
<li><span class="icon"></span>反射构造对象</li>
<li><span class="icon"></span>私有字段修改</li>
<li><span class="icon"></span>公开/私有/静态方法调用</li>
</ul>
<a href="reflection.html" class="btn">开始实验 →</a>
</div>
<div class="card"> <div class="card">
<h3>🔐 鉴权实验室</h3> <h3>🔐 鉴权实验室</h3>
<p>一步步体验登录、带 Token 请求、鉴权放行/拦截,对比 learn / advanced 模式差异。</p> <p>一步步体验登录、带 Token 请求、鉴权放行/拦截,对比 learn / advanced 模式差异。</p>

View File

@@ -0,0 +1,119 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>反射可视化实验室 - 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:1200px; margin:0 auto; padding:20px; }
.header { background:linear-gradient(135deg,#0f766e,#14b8a6); color:#fff; padding:28px 20px; border-radius:12px; text-align:center; margin-bottom:20px; }
.nav { display:flex; gap:10px; flex-wrap:wrap; justify-content:center; margin-bottom:20px; }
.nav a { padding:10px 18px; background:#fff; border-radius:20px; text-decoration:none; color:#333; }
.nav a.active { background:#0f766e; color:#fff; }
.lab { background:#fff7e6; border-left:4px solid #fa8c16; padding:15px; border-radius:8px; margin-bottom:20px; }
.grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(320px,1fr)); gap:16px; }
.card { background:#fff; border-radius:12px; padding:18px; box-shadow:0 2px 10px rgba(0,0,0,.08); }
.card h3 { color:#0f766e; margin-bottom:10px; }
.result { background:#111827; color:#d1d5db; padding:12px; border-radius:8px; min-height:180px; white-space:pre-wrap; font-family:monospace; margin-top:10px; overflow:auto; }
button { padding:10px 14px; background:#0f766e; color:#fff; border:none; border-radius:6px; cursor:pointer; margin-right:8px; margin-bottom:8px; }
input { padding:10px; border:1px solid #ddd; border-radius:6px; margin-right:8px; margin-bottom:8px; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🪞 反射可视化实验室</h1>
<p>不只说“反射很重要”,而是让你自己查看类信息、动态构造对象、读写字段、调用私有方法。</p>
</div>
<div class="nav">
<a href="index.html">🏠 首页</a>
<a href="ioc.html">📦 IoC</a>
<a href="reflection.html" class="active">🪞 反射</a>
<a href="verify-lab.html">🩺 修复验证</a>
</div>
<div class="lab">
<strong>实验任务卡</strong>
<ul style="padding-left:20px; line-height:1.8; margin-top:8px;">
<li>先看类信息,理解反射能拿到哪些元数据</li>
<li>再用构造器动态创建对象,观察实例内容</li>
<li>再读写私有字段,理解为什么框架可以“跳过表面上的访问限制”</li>
<li>最后调用私有方法和静态方法,感受反射为什么是框架底层利器</li>
</ul>
</div>
<div class="grid">
<div class="card">
<h3>概念总览</h3>
<button onclick="callApi('/api/learning/reflection/overview','overview')">加载总览</button>
<div id="overview" class="result"></div>
</div>
<div class="card">
<h3>类元信息</h3>
<button onclick="callApi('/api/learning/reflection/class-info','classInfo')">查看类信息</button>
<div id="classInfo" class="result"></div>
</div>
</div>
<div class="grid">
<div class="card">
<h3>动态构造对象</h3>
<input id="ctorName" value="ref-user" placeholder="name">
<input id="ctorCount" value="5" placeholder="count">
<button onclick="instantiate()">反射创建对象</button>
<div id="ctorResult" class="result"></div>
</div>
<div class="card">
<h3>私有字段读写</h3>
<input id="fieldValue" value="changed-by-reflection" placeholder="new field value">
<button onclick="fieldAccess()">修改私有字段</button>
<div id="fieldResult" class="result"></div>
</div>
</div>
<div class="grid">
<div class="card">
<h3>方法调用实验</h3>
<input id="prefix" value="你好" placeholder="prefix">
<button onclick="methodCall()">调用公开/私有/静态方法</button>
<div id="methodResult" class="result"></div>
</div>
<div class="card">
<h3>框架为什么依赖反射</h3>
<button onclick="callApi('/api/learning/reflection/why-frameworks-use-it','frameworkResult')">查看框架视角</button>
<div id="frameworkResult" class="result"></div>
</div>
</div>
</div>
<script>
async function callApi(url, id) {
const el = document.getElementById(id);
el.textContent = '加载中...';
try {
const res = await fetch(url);
const data = await res.json();
el.textContent = JSON.stringify(data, null, 2);
} catch (e) {
el.textContent = '失败: ' + e.message;
}
}
async function instantiate() {
const name = encodeURIComponent(document.getElementById('ctorName').value);
const count = encodeURIComponent(document.getElementById('ctorCount').value);
callApi(`/api/learning/reflection/instantiate?name=${name}&count=${count}`, 'ctorResult');
}
async function fieldAccess() {
const value = encodeURIComponent(document.getElementById('fieldValue').value);
callApi(`/api/learning/reflection/field-access?value=${value}`, 'fieldResult');
}
async function methodCall() {
const prefix = encodeURIComponent(document.getElementById('prefix').value);
callApi(`/api/learning/reflection/method-call?prefix=${prefix}`, 'methodResult');
}
callApi('/api/learning/reflection/overview','overview');
</script>
</body>
</html>