feat: Spring Boot 学习脚手架 v2.0

- 新增 IoC 容器学习模块
- 新增 AOP 切面编程学习模块
- 新增 MyBatis 集成学习模块
- 新增事务管理学习模块
- 新增用户/产品/订单 CRUD
- 新增 7 个交互式学习页面
- 集成性能监控切面
This commit is contained in:
likingcode
2026-03-07 08:37:40 +00:00
commit c04235c655
73 changed files with 4978 additions and 0 deletions

View File

@@ -0,0 +1,164 @@
package com.example.scaffold.learning;
import com.example.scaffold.aop.PerformanceAspect;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Scope;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.WebApplicationContext;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import java.util.*;
/**
* IoC 容器学习控制器
*
* 学习要点:
* 1. Bean 的生命周期
* 2. 依赖注入方式
* 3. Bean 作用域
* 4. 条件化配置
*/
@RestController
@RequestMapping("/api/learning/ioc")
@RequiredArgsConstructor
public class IocLearningController {
private final ApplicationContext applicationContext;
private final PerformanceAspect performanceAspect;
// 演示字段注入(不推荐,但可以用)
@Autowired
@Qualifier("learningBean")
private LearningBean learningBean;
/**
* 查看所有 Bean
*/
@GetMapping("/beans")
public Map<String, Object> listBeans() {
String[] beanNames = applicationContext.getBeanDefinitionNames();
Map<String, Object> result = new LinkedHashMap<>();
result.put("total", beanNames.length);
result.put("userBeans", Arrays.stream(beanNames)
.filter(name -> name.startsWith("user") || name.startsWith("learning") ||
name.contains("Service") || name.contains("Controller") || name.contains("Mapper"))
.sorted()
.toList());
result.put("allBeans", Arrays.stream(beanNames).sorted().toList());
return result;
}
/**
* 查看 Bean 详情
*/
@GetMapping("/beans/{name}")
public Map<String, Object> getBeanDetail(@PathVariable String name) {
try {
Object bean = applicationContext.getBean(name);
Class<?> clazz = bean.getClass();
return Map.of(
"name", name,
"type", clazz.getName(),
"simpleName", clazz.getSimpleName(),
"interfaces", Arrays.toString(clazz.getInterfaces()),
"annotations", Arrays.toString(clazz.getAnnotations()),
"scope", applicationContext.isSingleton(name) ? "singleton" : "prototype"
);
} catch (BeansException e) {
return Map.of("error", "Bean not found: " + name);
}
}
/**
* 演示依赖注入方式
*/
@GetMapping("/injection-types")
public Map<String, String> injectionTypes() {
return Map.of(
"构造器注入", "推荐!明确依赖,不可变,易于测试",
"Setter注入", "可选依赖,灵活性高",
"字段注入", "不推荐!隐藏依赖,难以测试",
"本控制器使用", "构造器注入 (RequiredArgsConstructor) + 字段注入演示"
);
}
/**
* 演示 Bean 作用域
*/
@GetMapping("/scopes")
public Map<String, Object> scopes() {
return Map.of(
"singleton", "单例 - 整个应用只有一个实例(默认)",
"prototype", "原型 - 每次请求都创建新实例",
"request", "请求 - 每个 HTTP 请求一个实例",
"session", "会话 - 每个 HTTP 会话一个实例",
"demo", learningBean.getInstanceInfo()
);
}
/**
* 性能统计
*/
@GetMapping("/performance")
public Map<String, Object> getPerformance() {
var stats = performanceAspect.getStats();
Map<String, Object> result = new LinkedHashMap<>();
stats.forEach((key, value) -> {
long totalMs = value.totalTime.get() / 1_000_000;
long avgMs = value.totalCount.get() > 0 ? totalMs / value.totalCount.get() : 0;
result.put(key, Map.of(
"count", value.totalCount.get(),
"errors", value.errorCount.get(),
"totalMs", totalMs,
"avgMs", avgMs,
"maxMs", value.maxTime.get()
));
});
return result;
}
/**
* 重置性能统计
*/
@PostMapping("/performance/reset")
public Map<String, String> resetPerformance() {
performanceAspect.resetStats();
return Map.of("status", "ok", "message", "性能统计已重置");
}
/**
* 学习 Bean - 演示作用域和生命周期
*/
@org.springframework.stereotype.Component("learningBean")
@Scope(WebApplicationContext.SCOPE_SESSION)
public static class LearningBean {
private final String instanceId = UUID.randomUUID().toString().substring(0, 8);
private int accessCount = 0;
@PostConstruct
public void init() {
System.out.println("🟢 [LearningBean @PostConstruct] Bean 初始化: " + instanceId);
}
@PreDestroy
public void destroy() {
System.out.println("🔴 [LearningBean @PreDestroy] Bean 销毁: " + instanceId);
}
public String getInstanceInfo() {
accessCount++;
return String.format("实例ID: %s, 访问次数: %d, 作用域: session", instanceId, accessCount);
}
}
}