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,110 @@
package com.example.scaffold.learning;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import java.util.*;
/**
* AOP 学习控制器
*
* 学习要点:
* 1. 切面概念
* 2. 通知类型
* 3. 切入点表达式
*/
@Slf4j
@RestController
@RequestMapping("/api/learning/aop")
@RequiredArgsConstructor
public class AopLearningController {
/**
* AOP 概念说明
*/
@GetMapping("/concepts")
public Map<String, Object> concepts() {
return Map.of(
"AOP", "面向切面编程 (Aspect Oriented Programming)",
"核心概念", Map.of(
"Aspect (切面)", "横切关注点的模块化",
"JoinPoint (连接点)", "程序执行的某个特定位置",
"Pointcut (切入点)", "匹配连接点的表达式",
"Advice (通知)", "在切入点执行的代码",
"Target (目标对象)", "被通知的对象",
"Proxy (代理)", "AOP 创建的代理对象",
"Weaving (织入)", "将切面应用到目标对象的过程"
),
"通知类型", Map.of(
"@Before", "方法执行前",
"@After", "方法执行后(无论是否异常)",
"@AfterReturning", "方法成功返回后",
"@AfterThrowing", "方法抛出异常后",
"@Around", "环绕 - 完全控制方法执行"
)
);
}
/**
* 切入点表达式语法
*/
@GetMapping("/pointcut-syntax")
public Map<String, Object> pointcutSyntax() {
return Map.of(
"语法", "execution(修饰符? 返回类型 包名.类名.方法名(参数) 异常?)",
"示例", List.of(
"execution(* com.example.service.*.*(..)) - service包下所有方法",
"execution(* com.example.service..*.*(..)) - service包及子包所有方法",
"execution(public * *(..)) - 所有public方法",
"execution(* set*(..)) - 所有set开头的方法",
"execution(* com.example.service.UserService.*(..)) - UserService的所有方法",
"@annotation(org.springframework.transaction.annotation.Transactional) - 带@Transactional的方法"
),
"通配符", Map.of(
"*", "匹配任意字符",
"..", "匹配任意层级的包或任意参数",
"+", "匹配指定类及其子类"
)
);
}
/**
* 测试 AOP - 这个方法会被切面拦截
*/
@GetMapping("/test")
public Map<String, Object> testAop(@RequestParam(defaultValue = "test") String message) {
log.info("📝 [AopLearningController] 测试方法被调用: message={}", message);
return Map.of(
"message", "AOP 测试成功",
"input", message,
"tip", "查看控制台日志,观察 AOP 通知的执行顺序",
"expectedOrder", List.of(
"1. @Around 开始",
"2. @Before",
"3. 方法执行",
"4. @AfterReturning",
"5. @After",
"6. @Around 结束"
)
);
}
/**
* 演示异常通知
*/
@GetMapping("/test-error")
public Map<String, Object> testError(@RequestParam(defaultValue = "false") boolean error) {
log.info("📝 [AopLearningController] 测试异常通知: error={}", error);
if (error) {
throw new RuntimeException("这是一个测试异常,用于触发 @AfterThrowing");
}
return Map.of(
"message", "正常执行",
"tip", "传入 error=true 触发异常,观察 @AfterThrowing"
);
}
}

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);
}
}
}

View File

@@ -0,0 +1,127 @@
package com.example.scaffold.learning;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.web.bind.annotation.*;
import java.util.*;
/**
* MyBatis 学习控制器
*
* 学习要点:
* 1. MyBatis vs JPA 对比
* 2. 动态 SQL
* 3. 缓存机制
* 4. 结果映射
*/
@Slf4j
@RestController
@RequestMapping("/api/learning/mybatis")
@RequiredArgsConstructor
public class MyBatisLearningController {
private final SqlSessionFactory sqlSessionFactory;
/**
* MyBatis 核心概念
*/
@GetMapping("/concepts")
public Map<String, Object> concepts() {
return Map.of(
"MyBatis", "半自动 ORM 框架SQL 与 Java 对象映射",
"核心组件", Map.of(
"SqlSessionFactory", "创建 SqlSession 的工厂",
"SqlSession", "执行 SQL 的会话",
"Mapper", "接口绑定的 SQL 语句",
"Configuration", "MyBatis 配置信息"
),
"缓存", Map.of(
"一级缓存", "SqlSession 级别,默认开启",
"二级缓存", "Mapper 级别,需配置 @CacheNamespace"
)
);
}
/**
* MyBatis vs JPA 对比
*/
@GetMapping("/vs-jpa")
public Map<String, Object> vsJpa() {
return Map.of(
"MyBatis", Map.of(
"优点", List.of("SQL 灵活可控", "性能优化方便", "复杂查询友好"),
"缺点", List.of("SQL 与代码耦合", "数据库迁移成本高", "需要手写 SQL"),
"适用场景", "复杂查询、性能要求高、DBA 参与项目"
),
"JPA/Hibernate", Map.of(
"优点", List.of("面向对象", "数据库无关", "开发效率高"),
"缺点", List.of("复杂查询困难", "性能调优复杂", "学习曲线陡"),
"适用场景", "标准 CRUD、快速开发、领域驱动设计"
),
"建议", "小型项目用 JPA大型/复杂查询项目用 MyBatis"
);
}
/**
* 动态 SQL 语法
*/
@GetMapping("/dynamic-sql")
public Map<String, Object> dynamicSql() {
return Map.of(
"if", "条件判断 <if test='name != null'> AND name = #{name} </if>",
"choose/when/otherwise", "类似 switch-case",
"trim/where/set", "处理 SQL 拼接",
"foreach", "循环遍历 <foreach item='item' collection='list' separator=','>#{item}</foreach>",
"bind", "定义变量 <bind name='pattern' value=\"'%' + name + '%'\" />",
"示例", """
<select id='findUser'>
SELECT * FROM users
<where>
<if test='name != null'>AND name LIKE #{name}</if>
<if test='email != null'>AND email = #{email}</if>
</where>
</select>
"""
);
}
/**
* 缓存演示
*/
@GetMapping("/cache")
public Map<String, Object> cacheDemo() {
return Map.of(
"一级缓存", Map.of(
"范围", "SqlSession 级别",
"默认", "开启",
"失效条件", List.of("执行 insert/update/delete", "调用 sqlSession.clearCache()", "事务提交/回滚"),
"演示", "同一 SqlSession 内连续两次相同查询,第二次不执行 SQL"
),
"二级缓存", Map.of(
"范围", "Mapper 级别",
"配置", "@CacheNamespace 或 XML 配置",
"注意", "实体类需要实现 Serializable",
"本项目", "UserMapper 已启用二级缓存"
),
"验证方式", "查看控制台 SQL 日志,缓存命中时不打印 SQL"
);
}
/**
* 查看当前配置
*/
@GetMapping("/config")
public Map<String, Object> getConfig() {
var config = sqlSessionFactory.getConfiguration();
return Map.of(
"cacheEnabled", config.isCacheEnabled(),
"localCacheScope", config.getLocalCacheScope().name(),
"defaultExecutorType", config.getDefaultExecutorType().name(),
"mapUnderscoreToCamelCase", config.isMapUnderscoreToCamelCase(),
"mappedStatements", config.getMappedStatements().size()
);
}
}

View File

@@ -0,0 +1,135 @@
package com.example.scaffold.learning;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import java.util.*;
/**
* 事务学习控制器
*
* 学习要点:
* 1. 事务传播行为
* 2. 事务隔离级别
* 3. 事务回滚
*/
@Slf4j
@RestController
@RequestMapping("/api/learning/transaction")
@RequiredArgsConstructor
public class TransactionLearningController {
/**
* 事务概念
*/
@GetMapping("/concepts")
public Map<String, Object> concepts() {
return Map.of(
"ACID", Map.of(
"Atomicity (原子性)", "事务是不可分割的工作单位",
"Consistency (一致性)", "事务必须使数据库从一个一致性状态变换到另一个一致性状态",
"Isolation (隔离性)", "多个用户并发访问数据库时,数据库为每个用户开启的事务不能被其他事务干扰",
"Durability (持久性)", "事务一旦提交,对数据库的改变是永久性的"
),
"Spring事务", "@Transactional 注解实现声明式事务管理"
);
}
/**
* 传播行为详解
*/
@GetMapping("/propagation")
public Map<String, Object> propagation() {
return Map.of(
"REQUIRED (默认)", Map.of(
"描述", "有事务则加入,无则新建",
"场景", "最常用,大多数业务方法",
"示例", "A 调用 BB 加入 A 的事务"
),
"REQUIRES_NEW", Map.of(
"描述", "总是新建事务,挂起当前事务",
"场景", "日志记录、独立子任务",
"示例", "A 调用 BB 在新事务执行A 回滚不影响 B"
),
"SUPPORTS", Map.of(
"描述", "有事务则加入,无则以非事务运行",
"场景", "查询方法"
),
"NOT_SUPPORTED", Map.of(
"描述", "以非事务运行,挂起当前事务",
"场景", "不需要事务的操作"
),
"MANDATORY", Map.of(
"描述", "必须在事务中运行,否则抛异常",
"场景", "强制要求事务"
),
"NEVER", Map.of(
"描述", "不能在事务中运行,否则抛异常",
"场景", "确保无事务"
),
"NESTED", Map.of(
"描述", "嵌套事务,可独立回滚",
"场景", "部分失败不影响整体"
)
);
}
/**
* 隔离级别详解
*/
@GetMapping("/isolation")
public Map<String, Object> isolation() {
return Map.of(
"DEFAULT", "使用数据库默认隔离级别",
"READ_UNCOMMITTED", Map.of(
"描述", "读未提交",
"问题", "脏读、不可重复读、幻读",
"性能", "最高"
),
"READ_COMMITTED", Map.of(
"描述", "读已提交",
"问题", "不可重复读、幻读",
"性能", "较高",
"场景", "大多数数据库默认"
),
"REPEATABLE_READ", Map.of(
"描述", "可重复读",
"问题", "幻读",
"性能", "中等",
"场景", "MySQL 默认"
),
"SERIALIZABLE", Map.of(
"描述", "串行化",
"问题", "",
"性能", "最低",
"场景", "数据一致性要求极高"
),
"问题说明", Map.of(
"脏读", "读到其他事务未提交的数据",
"不可重复读", "同一事务两次读取结果不同(修改导致)",
"幻读", "同一事务两次读取结果不同(新增/删除导致)"
)
);
}
/**
* 回滚规则
*/
@GetMapping("/rollback")
public Map<String, Object> rollback() {
return Map.of(
"默认行为", "只对 RuntimeException 和 Error 回滚",
"rollbackFor", "指定需要回滚的异常类型",
"noRollbackFor", "指定不需要回滚的异常类型",
"示例", """
@Transactional(rollbackFor = Exception.class)
@Transactional(noRollbackFor = BusinessException.class)
""",
"测试API", Map.of(
"创建订单", "POST /api/orders",
"创建订单(回滚)", "POST /api/orders?rollback=true"
)
);
}
}