commit 539dc418688c9516ebe42e534b1f9e76067d41ff Author: likingcode Date: Sat Mar 7 05:43:15 2026 +0000 feat: Spring Boot示例项目 - 基础Spring Boot配置 - Docker支持 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..dd17c5c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,8 @@ +FROM eclipse-temurin:17-jdk-alpine +WORKDIR /app +RUN apk add --no-cache maven +COPY pom.xml . +COPY src ./src +RUN mvn clean package -DskipTests +EXPOSE 8082 +CMD ["java", "-jar", "target/demo-0.0.1-SNAPSHOT.jar"] diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..977b95e --- /dev/null +++ b/pom.xml @@ -0,0 +1,57 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.2.0 + + + com.example + demo + 0.0.1-SNAPSHOT + springboot-demo + + + 17 + + + + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.boot + spring-boot-starter-aop + + + + + org.springframework.boot + spring-boot-starter-actuator + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + \ No newline at end of file diff --git a/springboot.log b/springboot.log new file mode 100644 index 0000000..fd424ad --- /dev/null +++ b/springboot.log @@ -0,0 +1,38 @@ + + . ____ _ __ _ _ + /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ +( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ + \\/ ___)| |_)| | | | | || (_| | ) ) ) ) + ' |____| .__|_| |_|_| |_\__, | / / / / + =========|_|==============|___/=/_/_/_/ + :: Spring Boot :: (v3.2.0) + +2026-03-06T17:09:02.230Z INFO 1154290 --- [springboot-demo] [ main] com.example.demo.DemoApplication : Starting DemoApplication v0.0.1-SNAPSHOT using Java 21.0.10 with PID 1154290 (/home/llm/projects/springboot-demo/target/demo-0.0.1-SNAPSHOT.jar started by llm in /home/llm/projects/springboot-demo) +2026-03-06T17:09:02.380Z INFO 1154290 --- [springboot-demo] [ main] com.example.demo.DemoApplication : No active profile set, falling back to 1 default profile: "default" +2026-03-06T17:10:05.868Z INFO 1154290 --- [springboot-demo] [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port 8082 (http) +2026-03-06T17:10:06.093Z INFO 1154290 --- [springboot-demo] [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat] +2026-03-06T17:10:06.093Z INFO 1154290 --- [springboot-demo] [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.16] +2026-03-06T17:10:07.996Z INFO 1154290 --- [springb2026-03-06T17:10:12.808Z INFO 1153691 --- [springboot-demo] [ main] o.s.b.a.e.web.EndpointLinksResolver : Exposing 1 endpoint(s) beneath base path '/actuator' +2026-03-06T17:10:14.838Z INFO 1153691 --- [springboot-demo] [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat start2026-03-06T17:10:23.314Z INFO 1154290 --- [springboot-demo] [ main] o.s.b.a.w.s.WelcomePageHandlerMapping : Adding welcome page: class path resource [static/index.html] +2026-03-06T17:10:40.414Z INFO 1154290 --- [springboot-demo] [ main] o.s.b.a.e.web.EndpointLinksResolver : Exposing 1 endpoint(s) beneath base path '/actuator' +2026-03-06T17:10:41.395Z INFO 1154290 --- [springboot-demo] [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8082 (http) with context path '' +2026-03-06T17:10:41.541Z INFO 1154290 --- [springboot-demo] [ main] com.example.demo.DemoApplication : Started DemoApplication in 116.974 seconds (process running for 133.101) +[EventListener] Spring Boot 应用启动完成! +2026-03-06T17:11:05.601Z INFO 1154290 --- [springboot-demo] [nio-8082-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet' +2026-03-06T17:11:05.601Z INFO 1154290 --- [springboot-demo] [nio-8082-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet' +2026-03-06T17:11:05.619Z INFO 1154290 --- [springboot-demo] [nio-8082-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 13 ms +[AOP-Before] Controller 方法开始: root + 参数: [] +[AOP-AfterReturning] 方法返回: root + 返回值: {aop=https://spring.xiaoxiaoluohao.indevs.in/aop, learn=https://spring.xiaoxiaoluohao.indevs.in/learn, api=https://spring.xiaoxiaoluohao.indevs.in/api/users, message=欢迎来到 Spring Boot 学习脚手架!} +[AOP-Performance] LearnController.root() 执行耗时: 18ms +[AOP-Before] Controller 方法开始: root + 参数: [] +[AOP-AfterReturning] 方法返回: root + 返回值: {aop=https://spring.xiaoxiaoluohao.indevs.in/aop, learn=https://spring.xiaoxiaoluohao.indevs.in/learn, api=https://spring.xiaoxiaoluohao.indevs.in/api/users, message=欢迎来到 Spring Boot 学习脚手架!} +[AOP-Performance] LearnController.root() 执行耗时: 0ms +[AOP-Before] Controller 方法开始: root + 参数: [] +[AOP-AfterReturning] 方法返回: root + 返回值: {aop=https://spring.xiaoxiaoluohao.indevs.in/aop, learn=https://spring.xiaoxiaoluohao.indevs.in/learn, api=https://spring.xiaoxiaoluohao.indevs.in/api/users, message=欢迎来到 Spring Boot 学习脚手架!} +[AOP-Performance] LearnController.root() 执行耗时: 3ms diff --git a/src/main/java/com/example/demo/DemoApplication.java b/src/main/java/com/example/demo/DemoApplication.java new file mode 100644 index 0000000..cd0954e --- /dev/null +++ b/src/main/java/com/example/demo/DemoApplication.java @@ -0,0 +1,23 @@ +package com.example.demo; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.annotation.EnableScheduling; + +/** + * Spring Boot 主应用 + * + * 学习点: + * - @SpringBootApplication 组合注解 + * - @EnableAsync 启用异步处理 + * - @EnableScheduling 启用定时任务 + */ +@SpringBootApplication +@EnableAsync +@EnableScheduling +public class DemoApplication { + public static void main(String[] args) { + SpringApplication.run(DemoApplication.class, args); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/demo/aop/LoggingAspect.java b/src/main/java/com/example/demo/aop/LoggingAspect.java new file mode 100644 index 0000000..a588952 --- /dev/null +++ b/src/main/java/com/example/demo/aop/LoggingAspect.java @@ -0,0 +1,75 @@ +package com.example.demo.aop; + +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.*; +import org.springframework.stereotype.Component; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * 日志切面 - 演示 AOP 基础 + * + * 学习点: + * - @Aspect 定义切面 + * - @Pointcut 切入点表达式 + * - @Before / @After / @Around 通知类型 + * - JoinPoint 获取方法信息 + */ +@Aspect +@Component +public class LoggingAspect { + + /** + * 切入点:所有 Controller 方法 + */ + @Pointcut("execution(* com.example.demo.controller.*.*(..))") + public void controllerMethods() {} + + /** + * 切入点:所有 Service 方法 + */ + @Pointcut("execution(* com.example.demo.service.*.*(..))") + public void serviceMethods() {} + + /** + * 前置通知 - 方法执行前 + */ + @Before("controllerMethods()") + public void logBeforeController(JoinPoint joinPoint) { + System.out.println("[AOP-Before] Controller 方法开始: " + + joinPoint.getSignature().getName()); + System.out.println(" 参数: " + Arrays.toString(joinPoint.getArgs())); + } + + /** + * 后置通知 - 方法执行后(无论成功或异常) + */ + @After("serviceMethods()") + public void logAfterService(JoinPoint joinPoint) { + System.out.println("[AOP-After] Service 方法结束: " + + joinPoint.getSignature().getName()); + } + + /** + * 返回通知 - 方法成功返回后 + */ + @AfterReturning(pointcut = "controllerMethods()", returning = "result") + public void logAfterReturning(JoinPoint joinPoint, Object result) { + System.out.println("[AOP-AfterReturning] 方法返回: " + + joinPoint.getSignature().getName()); + System.out.println(" 返回值: " + result); + } + + /** + * 异常通知 - 方法抛出异常后 + */ + @AfterThrowing(pointcut = "controllerMethods() || serviceMethods()", throwing = "error") + public void logAfterThrowing(JoinPoint joinPoint, Throwable error) { + System.out.println("[AOP-AfterThrowing] 方法异常: " + + joinPoint.getSignature().getName()); + System.out.println(" 异常: " + error.getMessage()); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/demo/aop/PerformanceAspect.java b/src/main/java/com/example/demo/aop/PerformanceAspect.java new file mode 100644 index 0000000..5681098 --- /dev/null +++ b/src/main/java/com/example/demo/aop/PerformanceAspect.java @@ -0,0 +1,77 @@ +package com.example.demo.aop; + +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 性能监控切面 - 演示 @Around 环绕通知 + * + * 学习点: + * - @Around 最强大的通知类型 + * - ProceedingJoinPoint 控制方法执行 + * - 方法执行时间统计 + */ +@Aspect +@Component +public class PerformanceAspect { + + // 方法执行时间统计 + private final Map totalTimes = new ConcurrentHashMap<>(); + private final Map callCounts = new ConcurrentHashMap<>(); + + /** + * 环绕通知 - 统计方法执行时间 + */ + @Around("execution(* com.example.demo.controller.*.*(..)) || " + + "execution(* com.example.demo.service.*.*(..))") + public Object measureExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable { + String methodName = joinPoint.getSignature().toShortString(); + + long startTime = System.currentTimeMillis(); + + try { + // 执行目标方法 + Object result = joinPoint.proceed(); + + long endTime = System.currentTimeMillis(); + long duration = endTime - startTime; + + // 记录统计 + totalTimes.merge(methodName, duration, Long::sum); + callCounts.merge(methodName, 1L, Long::sum); + + System.out.println("[AOP-Performance] " + methodName + + " 执行耗时: " + duration + "ms"); + + return result; + } catch (Throwable e) { + long endTime = System.currentTimeMillis(); + System.out.println("[AOP-Performance] " + methodName + + " 异常耗时: " + (endTime - startTime) + "ms"); + throw e; + } + } + + /** + * 获取性能统计 + */ + public Map> getStatistics() { + Map> stats = new HashMap<>(); + + for (String method : totalTimes.keySet()) { + Map methodStats = new HashMap<>(); + methodStats.put("totalTime", totalTimes.get(method)); + methodStats.put("callCount", callCounts.get(method)); + methodStats.put("avgTime", totalTimes.get(method) / callCounts.get(method)); + stats.put(method, methodStats); + } + + return stats; + } +} \ No newline at end of file diff --git a/src/main/java/com/example/demo/aop/RateLimitAspect.java b/src/main/java/com/example/demo/aop/RateLimitAspect.java new file mode 100644 index 0000000..66cf663 --- /dev/null +++ b/src/main/java/com/example/demo/aop/RateLimitAspect.java @@ -0,0 +1,70 @@ +package com.example.demo.aop; + +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicLong; + +/** + * 限流切面 - 演示 AOP 实现横切关注点 + * + * 学习点: + * - @Order 控制切面执行顺序 + * - AOP 实现非功能性需求 + * - 横切关注点与业务逻辑分离 + */ +@Aspect +@Component +@Order(1) // 数字越小优先级越高 +public class RateLimitAspect { + + // 简单的限流计数器(实际项目用 Redis + Token Bucket) + private final Map requestCounts = new ConcurrentHashMap<>(); + private final Map lastResetTime = new ConcurrentHashMap<>(); + + // 每分钟最大请求数 + private static final long MAX_REQUESTS_PER_MINUTE = 100; + + /** + * 限流检查 + */ + @Around("@annotation(com.example.demo.aop.RateLimited)") + public Object enforceRateLimit(ProceedingJoinPoint joinPoint) throws Throwable { + String methodName = joinPoint.getSignature().toShortString(); + + long currentTime = System.currentTimeMillis(); + Long lastReset = lastResetTime.get(methodName); + + // 每分钟重置计数器 + if (lastReset == null || currentTime - lastReset > 60000) { + requestCounts.put(methodName, new AtomicLong(0)); + lastResetTime.put(methodName, currentTime); + } + + AtomicLong count = requestCounts.get(methodName); + long currentCount = count.incrementAndGet(); + + if (currentCount > MAX_REQUESTS_PER_MINUTE) { + throw new RuntimeException("请求过于频繁,请稍后再试 (Rate Limit)"); + } + + System.out.println("[AOP-RateLimit] " + methodName + + " 当前计数: " + currentCount + "/" + MAX_REQUESTS_PER_MINUTE); + + return joinPoint.proceed(); + } + + /** + * 获取限流状态 + */ + public Map getRateLimitStatus() { + Map status = new ConcurrentHashMap<>(); + requestCounts.forEach((k, v) -> status.put(k, v.get())); + return status; + } +} \ No newline at end of file diff --git a/src/main/java/com/example/demo/aop/RateLimited.java b/src/main/java/com/example/demo/aop/RateLimited.java new file mode 100644 index 0000000..efd3afc --- /dev/null +++ b/src/main/java/com/example/demo/aop/RateLimited.java @@ -0,0 +1,28 @@ +package com.example.demo.aop; + +import java.lang.annotation.*; + +/** + * 限流注解 - 自定义注解配合 AOP 使用 + * + * 学习点: + * - 自定义注解定义 + * - @Retention 保留策略 + * - @Target 目标元素 + * - 注解 + AOP 实现声明式功能 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +@Documented +public @interface RateLimited { + + /** + * 每分钟最大请求数 + */ + long value() default 100; + + /** + * 限流提示消息 + */ + String message() default "请求过于频繁,请稍后再试"; +} \ No newline at end of file diff --git a/src/main/java/com/example/demo/controller/AopEventController.java b/src/main/java/com/example/demo/controller/AopEventController.java new file mode 100644 index 0000000..ac2ec67 --- /dev/null +++ b/src/main/java/com/example/demo/controller/AopEventController.java @@ -0,0 +1,177 @@ +package com.example.demo.controller; + +import com.example.demo.aop.PerformanceAspect; +import com.example.demo.aop.RateLimitAspect; +import com.example.demo.aop.RateLimited; +import com.example.demo.event.UserEventPublisher; +import com.example.demo.model.User; +import com.example.demo.service.UserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * AOP 和事件机制学习控制器 + */ +@RestController +@RequestMapping("/aop") +public class AopEventController { + + @Autowired + private UserService userService; + + @Autowired + private UserEventPublisher eventPublisher; + + @Autowired + private PerformanceAspect performanceAspect; + + @Autowired + private RateLimitAspect rateLimitAspect; + + /** + * 学习首页 + */ + @GetMapping + public Map index() { + Map info = new HashMap<>(); + info.put("message", "Spring Boot 学习中心"); + info.put("topics", new String[]{ + "AOP 切面编程", + "事件机制", + "Bean 生命周期" + }); + info.put("endpoints", new String[]{ + "GET /aop - AOP 概念说明", + "GET /aop/stats - 性能统计", + "GET /aop/event - 事件机制说明", + "POST /aop/event/publish - 发布用户事件", + "GET /aop/event/history - 查看事件历史", + "GET /aop/ratelimit - 限流测试" + }); + return info; + } + + // ==================== AOP 示例 ==================== + + /** + * AOP 概念说明 + */ + @GetMapping("/aop") + public Map aopInfo() { + Map info = new HashMap<>(); + info.put("title", "AOP 切面编程"); + info.put("concepts", new String[]{ + "Aspect(切面): 横切关注点的模块化", + "JoinPoint(连接点): 程序执行的某个点", + "Pointcut(切入点): 匹配连接点的表达式", + "Advice(通知): 在连接点执行的动作", + "Weaving(织入): 将切面应用到目标对象" + }); + info.put("adviceTypes", new String[]{ + "@Before - 方法执行前", + "@After - 方法执行后(无论成功或异常)", + "@AfterReturning - 方法成功返回后", + "@AfterThrowing - 方法抛出异常后", + "@Around - 环绕通知(最强大)" + }); + info.put("useCases", new String[]{ + "日志记录", + "性能监控", + "事务管理", + "权限检查", + "限流控制" + }); + return info; + } + + /** + * 查看性能统计 + */ + @GetMapping("/aop/stats") + public Map getPerformanceStats() { + Map result = new HashMap<>(); + result.put("title", "方法性能统计"); + result.put("description", "由 PerformanceAspect 自动收集"); + result.put("statistics", performanceAspect.getStatistics()); + return result; + } + + // ==================== 事件机制示例 ==================== + + /** + * 事件机制说明 + */ + @GetMapping("/event") + public Map eventInfo() { + Map info = new HashMap<>(); + info.put("title", "Spring 事件机制"); + info.put("concepts", new String[]{ + "ApplicationEventPublisher - 事件发布者", + "@EventListener - 事件监听器", + "@Async - 异步处理事件", + "condition - 条件过滤" + }); + info.put("benefits", new String[]{ + "解耦:发布者和监听者互不依赖", + "扩展:新增监听器无需修改发布者", + "异步:耗时操作不阻塞主流程", + "测试:更容易进行单元测试" + }); + info.put("eventTypes", new String[]{ + "CREATED - 用户创建", + "UPDATED - 用户更新", + "DELETED - 用户删除", + "LOGIN - 用户登录" + }); + return info; + } + + /** + * 发布用户事件 + */ + @PostMapping("/event/publish") + public Map publishEvent( + @RequestParam Long userId, + @RequestParam String userName, + @RequestParam(defaultValue = "LOGIN") String eventType + ) { + eventPublisher.publishUserLogin(userId, userName); + + Map result = new HashMap<>(); + result.put("message", "事件已发布"); + result.put("eventType", eventType); + result.put("userId", userId); + result.put("userName", userName); + return result; + } + + /** + * 查看事件历史 + */ + @GetMapping("/event/history") + public Map getEventHistory() { + Map result = new HashMap<>(); + result.put("title", "事件历史记录"); + result.put("note", "由 UserEventListener 自动记录"); + return result; + } + + // ==================== 限流示例 ==================== + + /** + * 限流测试接口 + */ + @GetMapping("/ratelimit") + @RateLimited(value = 10, message = "测试限流:每分钟最多10次") + public Map testRateLimit() { + Map result = new HashMap<>(); + result.put("message", "请求成功"); + result.put("note", "使用 @RateLimited 注解"); + result.put("rateLimitStatus", rateLimitAspect.getRateLimitStatus()); + return result; + } +} \ No newline at end of file diff --git a/src/main/java/com/example/demo/controller/LearnController.java b/src/main/java/com/example/demo/controller/LearnController.java new file mode 100644 index 0000000..ca14d73 --- /dev/null +++ b/src/main/java/com/example/demo/controller/LearnController.java @@ -0,0 +1,116 @@ +package com.example.demo.controller; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.Map; + +/** + * 学习示例控制器 + * + * 学习点: + * - 各种参数接收方式 + * - 配置注入 + * - 响应格式 + */ +@RestController +public class LearnController { + + // 从配置文件注入值 + @Value("${spring.application.name:demo}") + private String appName; + + /** + * 根路径 - 重定向到学习中心 + */ + @GetMapping("/") + public Map root() { + Map info = new HashMap<>(); + info.put("message", "欢迎来到 Spring Boot 学习脚手架!"); + info.put("learn", "https://spring.xiaoxiaoluohao.indevs.in/learn"); + info.put("aop", "https://spring.xiaoxiaoluohao.indevs.in/aop"); + info.put("api", "https://spring.xiaoxiaoluohao.indevs.in/api/users"); + return info; + } + + // GET /learn - API 信息 + @GetMapping("/learn") + public Map info() { + Map info = new HashMap<>(); + info.put("app", appName); + info.put("message", "欢迎学习 Spring Boot!"); + info.put("endpoints", new String[]{ + "GET /learn/params?name=xxx&age=18 - 参数示例", + "POST /learn/body - JSON 请求体示例", + "GET /learn/path/{id} - 路径变量示例", + "GET /learn/header - 请求头示例", + "GET /learn/cookie - Cookie 示例" + }); + return info; + } + + // GET /learn/params?name=xxx&age=18 - 查询参数 + @GetMapping("/learn/params") + public Map params( + @RequestParam(required = false, defaultValue = "游客") String name, + @RequestParam(required = false, defaultValue = "0") Integer age + ) { + Map result = new HashMap<>(); + result.put("name", name); + result.put("age", age); + result.put("tip", "使用 @RequestParam 接收查询参数"); + return result; + } + + // POST /learn/body - 请求体 + @PostMapping("/learn/body") + public Map body(@RequestBody Map data) { + Map result = new HashMap<>(); + result.put("received", data); + result.put("tip", "使用 @RequestBody 接收 JSON 请求体"); + return result; + } + + // GET /learn/path/{id} - 路径变量 + @GetMapping("/learn/path/{id}") + public Map path(@PathVariable String id) { + Map result = new HashMap<>(); + result.put("id", id); + result.put("tip", "使用 @PathVariable 接收路径变量"); + return result; + } + + // GET /learn/header - 请求头 + @GetMapping("/learn/header") + public Map header(@RequestHeader(value = "User-Agent", required = false) String userAgent) { + Map result = new HashMap<>(); + result.put("userAgent", userAgent); + result.put("tip", "使用 @RequestHeader 获取请求头"); + return result; + } + + // GET /learn/cookie - Cookie + @GetMapping("/learn/cookie") + public Map cookie(@CookieValue(value = "JSESSIONID", required = false) String sessionId) { + Map result = new HashMap<>(); + result.put("sessionId", sessionId); + result.put("tip", "使用 @CookieValue 获取 Cookie"); + return result; + } + + // GET /learn/exception - 异常处理 + @GetMapping("/learn/exception") + public String exception() { + throw new RuntimeException("这是一个测试异常"); + } + + // 全局异常处理 + @ExceptionHandler(RuntimeException.class) + public Map handleException(RuntimeException e) { + Map result = new HashMap<>(); + result.put("error", e.getMessage()); + result.put("tip", "使用 @ExceptionHandler 处理异常"); + return result; + } +} \ No newline at end of file diff --git a/src/main/java/com/example/demo/controller/PageController.java b/src/main/java/com/example/demo/controller/PageController.java new file mode 100644 index 0000000..2be7278 --- /dev/null +++ b/src/main/java/com/example/demo/controller/PageController.java @@ -0,0 +1,16 @@ +package com.example.demo.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; + +/** + * 页面控制器 - 返回 HTML 页面 + */ +@Controller +public class PageController { + + @GetMapping("/home") + public String home() { + return "forward:/index.html"; + } +} \ No newline at end of file diff --git a/src/main/java/com/example/demo/controller/UserController.java b/src/main/java/com/example/demo/controller/UserController.java new file mode 100644 index 0000000..758083d --- /dev/null +++ b/src/main/java/com/example/demo/controller/UserController.java @@ -0,0 +1,64 @@ +package com.example.demo.controller; + +import com.example.demo.model.User; +import com.example.demo.service.UserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 用户控制器 - RESTful API 示例 + * + * 学习点: + * - @RestController: 组合了 @Controller 和 @ResponseBody + * - @RequestMapping: 路由映射 + * - @PathVariable: 路径变量 + * - @RequestParam: 查询参数 + * - @RequestBody: 请求体 + */ +@RestController +@RequestMapping("/api/users") +public class UserController { + + @Autowired + private UserService userService; + + // GET /api/users - 获取所有用户 + @GetMapping + public List getAllUsers() { + return userService.findAll(); + } + + // GET /api/users/{id} - 获取单个用户 + @GetMapping("/{id}") + public User getUserById(@PathVariable Long id) { + return userService.findById(id); + } + + // POST /api/users - 创建用户 + @PostMapping + public User createUser(@RequestBody User user) { + return userService.save(user); + } + + // PUT /api/users/{id} - 更新用户 + @PutMapping("/{id}") + public User updateUser(@PathVariable Long id, @RequestBody User user) { + user.setId(id); + return userService.save(user); + } + + // DELETE /api/users/{id} - 删除用户 + @DeleteMapping("/{id}") + public String deleteUser(@PathVariable Long id) { + userService.delete(id); + return "用户 " + id + " 已删除"; + } + + // GET /api/users/search?name=xxx - 搜索用户 + @GetMapping("/search") + public List searchUsers(@RequestParam String name) { + return userService.findByName(name); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/demo/event/UserEventListener.java b/src/main/java/com/example/demo/event/UserEventListener.java new file mode 100644 index 0000000..b121c06 --- /dev/null +++ b/src/main/java/com/example/demo/event/UserEventListener.java @@ -0,0 +1,80 @@ +package com.example.demo.event; + +import com.example.demo.model.UserEvent; +import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.boot.context.event.ApplicationReadyEvent; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * 事件监听器 - 演示如何监听事件 + * + * 学习点: + * - @EventListener 注解 + * - @Async 异步处理 + * - 多监听器协作 + * - 事件驱动架构的优势 + */ +@Component +public class UserEventListener { + + // 存储事件历史(演示用) + private final List eventHistory = new CopyOnWriteArrayList<>(); + + /** + * 监听用户事件 - 日志记录 + */ + @EventListener + public void handleUserEvent(UserEvent event) { + System.out.println("[EventListener] 收到事件: " + event.getType() + + " - 用户: " + event.getUserName() + + " - 时间: " + event.getTimestamp()); + + // 记录到历史 + eventHistory.add(event); + } + + /** + * 监听用户创建事件 - 发送欢迎邮件(模拟) + */ + @EventListener(condition = "#event.type == T(com.example.demo.model.UserEvent$Type).CREATED") + @Async + public void handleUserCreated(UserEvent event) { + System.out.println("[EmailService] 发送欢迎邮件给: " + event.getUserName()); + // 模拟邮件发送耗时 + try { + Thread.sleep(100); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + System.out.println("[EmailService] 欢迎邮件发送完成"); + } + + /** + * 监听用户登录事件 - 更新登录统计 + */ + @EventListener(condition = "#event.type == T(com.example.demo.model.UserEvent$Type).LOGIN") + public void handleUserLogin(UserEvent event) { + System.out.println("[LoginTracker] 记录用户登录: " + event.getUserName()); + } + + /** + * 监听应用启动完成事件 + */ + @EventListener + public void onApplicationReady(ApplicationReadyEvent event) { + System.out.println("[EventListener] Spring Boot 应用启动完成!"); + } + + /** + * 获取事件历史 + */ + public List getEventHistory() { + return new ArrayList<>(eventHistory); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/demo/event/UserEventPublisher.java b/src/main/java/com/example/demo/event/UserEventPublisher.java new file mode 100644 index 0000000..fc13ef9 --- /dev/null +++ b/src/main/java/com/example/demo/event/UserEventPublisher.java @@ -0,0 +1,55 @@ +package com.example.demo.event; + +import com.example.demo.model.UserEvent; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Component; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * 事件发布器 - 演示如何发布事件 + * + * 学习点: + * - ApplicationEventPublisher 接口 + * - 依赖注入发布器 + * - 解耦:发布者不需要知道监听者 + */ +@Component +public class UserEventPublisher { + + @Autowired + private ApplicationEventPublisher eventPublisher; + + /** + * 发布用户事件 + */ + public void publishEvent(UserEvent event) { + System.out.println("[EventPublisher] 发布事件: " + event.getType() + " - " + event.getUserName()); + eventPublisher.publishEvent(event); + } + + /** + * 便捷方法:发布用户创建事件 + */ + public void publishUserCreated(Long userId, String userName) { + UserEvent event = new UserEvent( + UserEvent.Type.CREATED, + userId, + userName, + "新用户注册成功" + ); + publishEvent(event); + } + + /** + * 便捷方法:发布用户登录事件 + */ + public void publishUserLogin(Long userId, String userName) { + UserEvent event = new UserEvent( + UserEvent.Type.LOGIN, + userId, + userName, + "用户登录成功" + ); + publishEvent(event); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/demo/model/User.java b/src/main/java/com/example/demo/model/User.java new file mode 100644 index 0000000..a3fe4d1 --- /dev/null +++ b/src/main/java/com/example/demo/model/User.java @@ -0,0 +1,34 @@ +package com.example.demo.model; + +/** + * 用户实体类 + * + * 学习点: + * - JavaBean / POJO 设计模式 + * - Lombok 可以简化 getter/setter(这里用原生写法演示) + */ +public class User { + private Long id; + private String name; + private String email; + private Integer age; + + public User() {} + + public User(Long id, String name, String email, Integer age) { + this.id = id; + this.name = name; + this.email = email; + this.age = age; + } + + // Getters and Setters + public Long getId() { return id; } + public void setId(Long id) { this.id = id; } + public String getName() { return name; } + public void setName(String name) { this.name = name; } + public String getEmail() { return email; } + public void setEmail(String email) { this.email = email; } + public Integer getAge() { return age; } + public void setAge(Integer age) { this.age = age; } +} \ No newline at end of file diff --git a/src/main/java/com/example/demo/model/UserEvent.java b/src/main/java/com/example/demo/model/UserEvent.java new file mode 100644 index 0000000..fe1bd29 --- /dev/null +++ b/src/main/java/com/example/demo/model/UserEvent.java @@ -0,0 +1,51 @@ +package com.example.demo.model; + +import java.time.LocalDateTime; + +/** + * 用户事件 - 演示 Spring 事件机制 + * + * 学习点: + * - ApplicationEvent 基类 + * - 事件驱动架构 + * - 观察者模式 + */ +public class UserEvent { + + public enum Type { + CREATED, // 用户创建 + UPDATED, // 用户更新 + DELETED, // 用户删除 + LOGIN // 用户登录 + } + + private Type type; + private Long userId; + private String userName; + private LocalDateTime timestamp; + private String detail; + + public UserEvent() { + this.timestamp = LocalDateTime.now(); + } + + public UserEvent(Type type, Long userId, String userName, String detail) { + this.type = type; + this.userId = userId; + this.userName = userName; + this.detail = detail; + this.timestamp = LocalDateTime.now(); + } + + // Getters and Setters + public Type getType() { return type; } + public void setType(Type type) { this.type = type; } + public Long getUserId() { return userId; } + public void setUserId(Long userId) { this.userId = userId; } + public String getUserName() { return userName; } + public void setUserName(String userName) { this.userName = userName; } + public LocalDateTime getTimestamp() { return timestamp; } + public void setTimestamp(LocalDateTime timestamp) { this.timestamp = timestamp; } + public String getDetail() { return detail; } + public void setDetail(String detail) { this.detail = detail; } +} \ No newline at end of file diff --git a/src/main/java/com/example/demo/service/UserService.java b/src/main/java/com/example/demo/service/UserService.java new file mode 100644 index 0000000..e68dbb5 --- /dev/null +++ b/src/main/java/com/example/demo/service/UserService.java @@ -0,0 +1,69 @@ +package com.example.demo.service; + +import com.example.demo.model.User; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Collectors; + +/** + * 用户服务 - 业务逻辑层 + * + * 学习点: + * - @Service: 标记为服务层组件,自动注册为 Bean + * - 依赖注入:Controller 通过 @Autowired 注入此服务 + * - 分层架构:Controller -> Service -> Repository + */ +@Service +public class UserService { + + // 内存存储(演示用,实际项目用数据库) + private final List users = new ArrayList<>(); + private final AtomicLong idGenerator = new AtomicLong(1); + + public UserService() { + // 初始化一些测试数据 + users.add(new User(idGenerator.getAndIncrement(), "张三", "zhangsan@example.com", 25)); + users.add(new User(idGenerator.getAndIncrement(), "李四", "lisi@example.com", 30)); + users.add(new User(idGenerator.getAndIncrement(), "王五", "wangwu@example.com", 28)); + } + + public List findAll() { + return new ArrayList<>(users); + } + + public User findById(Long id) { + return users.stream() + .filter(u -> u.getId().equals(id)) + .findFirst() + .orElse(null); + } + + public List findByName(String name) { + return users.stream() + .filter(u -> u.getName().contains(name)) + .collect(Collectors.toList()); + } + + public User save(User user) { + if (user.getId() == null) { + user.setId(idGenerator.getAndIncrement()); + users.add(user); + } else { + // 更新 + for (int i = 0; i < users.size(); i++) { + if (users.get(i).getId().equals(user.getId())) { + users.set(i, user); + break; + } + } + } + return user; + } + + public void delete(Long id) { + users.removeIf(u -> u.getId().equals(id)); + } +} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..9206665 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,2 @@ +server.port=8082 +spring.application.name=springboot-demo diff --git a/src/main/resources/static/aop.html b/src/main/resources/static/aop.html new file mode 100644 index 0000000..a10416a --- /dev/null +++ b/src/main/resources/static/aop.html @@ -0,0 +1,177 @@ + + + + + + AOP 切面编程 - Spring Boot + + + + + +

🔪 AOP 切面编程

+ +
+

📊 实时性能统计

+

AOP 自动统计所有 Controller 和 Service 方法的执行时间

+ +
点击按钮查看...
+
+ +

📚 AOP 核心概念

+ +
+

1. 什么是 AOP?

+

AOP (Aspect-Oriented Programming) 面向切面编程,是将横切关注点业务逻辑分离的编程范式。

+
+ 横切关注点:日志、事务、安全、性能监控等,散布在多个模块中的公共功能。 +
+
+ +
+

2. 核心术语

+ + + + + + + +
术语说明
Aspect (切面)横切关注点的模块化封装
JoinPoint (连接点)程序执行的某个点(方法调用、异常抛出等)
Pointcut (切入点)匹配连接点的表达式
Advice (通知)在连接点执行的动作
Weaving (织入)将切面应用到目标对象的过程
+
+ +
+

3. 五种通知类型

+ + + + + + + +
注解执行时机用途
@Before方法执行前参数校验、权限检查
@After方法执行后(无论成功或异常)资源清理
@AfterReturning方法成功返回后结果处理、日志记录
@AfterThrowing方法抛出异常后异常处理、错误日志
@Around环绕方法执行(最强大)性能统计、事务管理
+
+ +

💻 代码示例

+ +
+

日志切面示例

+
@Aspect
+@Component
+public class LoggingAspect {
+    
+    // 切入点:匹配所有 Controller 方法
+    @Pointcut("execution(* com.example.demo.controller.*.*(..))")
+    public void controllerMethods() {}
+    
+    // 前置通知
+    @Before("controllerMethods()")
+    public void logBefore(JoinPoint jp) {
+        System.out.println("[AOP-Before] 方法开始: " + jp.getSignature().getName());
+    }
+    
+    // 返回通知
+    @AfterReturning(pointcut = "controllerMethods()", returning = "result")
+    public void logAfterReturning(JoinPoint jp, Object result) {
+        System.out.println("[AOP-AfterReturning] 返回值: " + result);
+    }
+}
+
+ +
+

性能监控切面 (@Around)

+
@Aspect
+@Component
+public class PerformanceAspect {
+    
+    @Around("execution(* com.example.demo..*.*(..))")
+    public Object measureTime(ProceedingJoinPoint pjp) throws Throwable {
+        long start = System.currentTimeMillis();
+        
+        try {
+            Object result = pjp.proceed(); // 执行目标方法
+            long duration = System.currentTimeMillis() - start;
+            System.out.println("[AOP] " + pjp.getSignature() + " 耗时: " + duration + "ms");
+            return result;
+        } catch (Throwable e) {
+            System.out.println("[AOP] 方法异常: " + e.getMessage());
+            throw e;
+        }
+    }
+}
+
+ +
+

切入点表达式语法

+
// 匹配任意公共方法
+execution(public * *(..))
+
+// 匹配 com.example 包下所有方法
+execution(* com.example.*.*(..))
+
+// 匹配 Controller 层所有方法
+execution(* com.example.demo.controller.*.*(..))
+
+// 匹配所有 Service 层的 save 开头的方法
+execution(* com.example.demo.service.*.save*(..))
+
+// 匹配带有 @Service 注解的类
+@within(org.springframework.stereotype.Service)
+
+// 匹配带有自定义注解的方法
+@annotation(com.example.demo.aop.RateLimited)
+
+ +

🎯 实际应用场景

+ +
+ + + + + + + + +
场景实现方式
日志记录@Before + @AfterReturning
性能监控@Around
事务管理@Around (Spring 已内置)
权限检查@Before
限流控制@Around + 自定义注解
缓存@Around (Spring Cache)
+
+ +

← 返回学习中心

+ + + + \ No newline at end of file diff --git a/src/main/resources/static/events.html b/src/main/resources/static/events.html new file mode 100644 index 0000000..391dc9e --- /dev/null +++ b/src/main/resources/static/events.html @@ -0,0 +1,213 @@ + + + + + + 事件机制 - Spring Boot + + + + + +

📡 Spring 事件机制

+ +
+

🎉 事件发布演示

+

模拟用户登录事件,观察事件发布和监听过程

+
+ + + +
+
等待事件发布...
+
+ +

🔄 事件机制流程

+ +
+ 发布者 + + ApplicationEventPublisher + + 事件 + + 监听者 +
+ +
+ 核心优势: +
    +
  • 解耦:发布者和监听者互不依赖
  • +
  • 扩展:新增监听器无需修改发布者
  • +
  • 异步:耗时操作不阻塞主流程
  • +
+
+ +

💻 代码实现

+ +
+

1. 定义事件

+
public class UserEvent {
+    public enum Type { CREATED, UPDATED, DELETED, LOGIN }
+    
+    private Type type;
+    private Long userId;
+    private String userName;
+    private LocalDateTime timestamp;
+    
+    // constructor, getters...
+}
+
+ +
+

2. 发布事件

+
@Component
+public class UserEventPublisher {
+    
+    @Autowired
+    private ApplicationEventPublisher eventPublisher;
+    
+    public void publishUserLogin(Long userId, String userName) {
+        UserEvent event = new UserEvent(
+            UserEvent.Type.LOGIN,
+            userId,
+            userName,
+            "用户登录成功"
+        );
+        eventPublisher.publishEvent(event);
+    }
+}
+
+ +
+

3. 监听事件

+
@Component
+public class UserEventListener {
+    
+    // 基础监听
+    @EventListener
+    public void handleUserEvent(UserEvent event) {
+        System.out.println("收到事件: " + event.getType());
+    }
+    
+    // 条件监听 - 只处理登录事件
+    @EventListener(condition = "#event.type == T(com.example.demo.model.UserEvent$Type).LOGIN")
+    public void handleLogin(UserEvent event) {
+        System.out.println("用户登录: " + event.getUserName());
+    }
+    
+    // 异步监听 - 不阻塞主流程
+    @Async
+    @EventListener
+    public void sendWelcomeEmail(UserEvent event) {
+        // 发送邮件...
+    }
+}
+
+ +
+

4. 控制器中使用

+
@RestController
+@RequestMapping("/aop")
+public class AopEventController {
+    
+    @Autowired
+    private UserEventPublisher eventPublisher;
+    
+    @PostMapping("/event/publish")
+    public Map<String, Object> publishEvent(
+            @RequestParam Long userId,
+            @RequestParam String userName) {
+        
+        // 发布事件
+        eventPublisher.publishUserLogin(userId, userName);
+        
+        return Map.of(
+            "message", "事件已发布",
+            "userId", userId,
+            "userName", userName
+        );
+    }
+}
+
+ +

🎯 应用场景

+ +
+ + + + + + +
场景事件类型处理逻辑
用户注册UserCreatedEvent发送欢迎邮件、初始化数据
订单创建OrderCreatedEvent扣库存、发送通知
支付成功PaymentSuccessEvent更新订单状态、发送短信
用户登录UserLoginEvent记录登录日志、更新在线状态
+
+ +
+

💡 最佳实践

+
    +
  • 事件类应该是不可变的(只读属性)
  • +
  • 使用 @Async 处理耗时操作
  • +
  • 避免在监听器中抛出异常
  • +
  • 使用 condition 过滤不需要的事件
  • +
  • 复杂场景考虑使用消息队列(RabbitMQ/Kafka)
  • +
+
+ +

← 返回学习中心

+ + + + \ No newline at end of file diff --git a/src/main/resources/static/index.html b/src/main/resources/static/index.html new file mode 100644 index 0000000..937b474 --- /dev/null +++ b/src/main/resources/static/index.html @@ -0,0 +1,221 @@ + + + + + + Spring Boot 学习中心 + + + +

🍃 Spring Boot 学习中心

+ + + + + +

🔗 快速链接

+ + +

🧪 接口测试

+ +
+

GET 参数示例

+
+ + + +
+
+

GET /learn/params?name=xxx&age=18

+
+ +
+

路径变量示例

+
+ + +
+
+

GET /learn/path/{id}

+
+ +
+

POST JSON 示例

+
+ + +
+
+

POST /learn/body

+
+ +

📖 学习路径

+ +
+

1. IOC 容器

+
    +
  • @Component, @Service, @Repository, @Controller
  • +
  • @Autowired 依赖注入
  • +
  • @Configuration + @Bean 配置类
  • +
+
+ +
+

2. Web 开发

+
    +
  • @RestController = @Controller + @ResponseBody
  • +
  • @RequestMapping, @GetMapping, @PostMapping
  • +
  • @PathVariable, @RequestParam, @RequestBody
  • +
+
+ +
+

3. AOP 切面编程

+
@Aspect
+@Component
+public class LoggingAspect {
+    @Before("execution(* com.example.*.*(..))")
+    public void logBefore(JoinPoint jp) {
+        System.out.println("方法调用: " + jp.getSignature());
+    }
+}
+
+ +
+

4. 事件机制

+
// 发布事件
+@Autowired
+ApplicationEventPublisher publisher;
+publisher.publishEvent(new UserEvent(...));
+
+// 监听事件
+@EventListener
+public void onEvent(UserEvent event) {
+    // 处理事件
+}
+
+ +

📁 项目结构

+
+
├── src/main/java/com/example/demo/
+│   ├── DemoApplication.java       # 启动类
+│   ├── controller/                # 控制器层
+│   │   ├── LearnController.java   # 学习示例
+│   │   ├── UserController.java    # 用户 API
+│   │   └── AopEventController.java
+│   ├── service/                   # 业务逻辑层
+│   ├── model/                     # 实体类
+│   ├── aop/                       # AOP 切面
+│   │   ├── LoggingAspect.java
+│   │   ├── PerformanceAspect.java
+│   │   └── RateLimitAspect.java
+│   └── event/                     # 事件机制
+│       ├── UserEventPublisher.java
+│       └── UserEventListener.java
+├── src/main/resources/
+│   ├── static/                    # 静态资源
+│   │   ├── index.html
+│   │   ├── users.html
+│   │   ├── aop.html
+│   │   └── events.html
+│   └── application.properties     # 配置文件
+└── pom.xml                        # Maven 配置
+
+ + + + + + \ No newline at end of file diff --git a/src/main/resources/static/users.html b/src/main/resources/static/users.html new file mode 100644 index 0000000..56a30e5 --- /dev/null +++ b/src/main/resources/static/users.html @@ -0,0 +1,225 @@ + + + + + + 用户管理 - Spring Boot + + + +

👥 用户管理 - RESTful API 示例

+ +
+
+

用户列表

+ +
+ + + + + + + + + + + +
ID姓名邮箱年龄操作
+
+ +
+

📖 学习要点

+
+ RESTful API 设计: +
    +
  • GET /api/users - 获取所有用户
  • +
  • GET /api/users/{id} - 获取单个用户
  • +
  • POST /api/users - 创建用户
  • +
  • PUT /api/users/{id} - 更新用户
  • +
  • DELETE /api/users/{id} - 删除用户
  • +
+
+ +

Controller 代码示例

+
@RestController
+@RequestMapping("/api/users")
+public class UserController {
+    
+    @GetMapping
+    public List<User> getAllUsers() { ... }
+    
+    @GetMapping("/{id}")
+    public User getUserById(@PathVariable Long id) { ... }
+    
+    @PostMapping
+    public User createUser(@RequestBody User user) { ... }
+    
+    @PutMapping("/{id}")
+    public User updateUser(@PathVariable Long id, @RequestBody User user) { ... }
+    
+    @DeleteMapping("/{id}")
+    public String deleteUser(@PathVariable Long id) { ... }
+}
+
+ +
+

🔧 Spring 注解说明

+ + + + + + + + + +
注解说明
@RestController= @Controller + @ResponseBody
@RequestMapping定义路由映射
@GetMappingGET 请求映射
@PostMappingPOST 请求映射
@PathVariable获取路径变量
@RequestBody获取请求体 JSON
@RequestParam获取查询参数
+
+ +

← 返回学习中心

+ + + + + + + \ No newline at end of file diff --git a/target/classes/application.properties b/target/classes/application.properties new file mode 100644 index 0000000..9206665 --- /dev/null +++ b/target/classes/application.properties @@ -0,0 +1,2 @@ +server.port=8082 +spring.application.name=springboot-demo diff --git a/target/classes/com/example/demo/DemoApplication.class b/target/classes/com/example/demo/DemoApplication.class new file mode 100644 index 0000000..7984485 Binary files /dev/null and b/target/classes/com/example/demo/DemoApplication.class differ diff --git a/target/classes/com/example/demo/aop/LoggingAspect.class b/target/classes/com/example/demo/aop/LoggingAspect.class new file mode 100644 index 0000000..2436ce8 Binary files /dev/null and b/target/classes/com/example/demo/aop/LoggingAspect.class differ diff --git a/target/classes/com/example/demo/aop/PerformanceAspect.class b/target/classes/com/example/demo/aop/PerformanceAspect.class new file mode 100644 index 0000000..c9081f4 Binary files /dev/null and b/target/classes/com/example/demo/aop/PerformanceAspect.class differ diff --git a/target/classes/com/example/demo/aop/RateLimitAspect.class b/target/classes/com/example/demo/aop/RateLimitAspect.class new file mode 100644 index 0000000..adbca1d Binary files /dev/null and b/target/classes/com/example/demo/aop/RateLimitAspect.class differ diff --git a/target/classes/com/example/demo/aop/RateLimited.class b/target/classes/com/example/demo/aop/RateLimited.class new file mode 100644 index 0000000..d6c0b24 Binary files /dev/null and b/target/classes/com/example/demo/aop/RateLimited.class differ diff --git a/target/classes/com/example/demo/controller/AopEventController.class b/target/classes/com/example/demo/controller/AopEventController.class new file mode 100644 index 0000000..1016611 Binary files /dev/null and b/target/classes/com/example/demo/controller/AopEventController.class differ diff --git a/target/classes/com/example/demo/controller/LearnController.class b/target/classes/com/example/demo/controller/LearnController.class new file mode 100644 index 0000000..0be2d65 Binary files /dev/null and b/target/classes/com/example/demo/controller/LearnController.class differ diff --git a/target/classes/com/example/demo/controller/PageController.class b/target/classes/com/example/demo/controller/PageController.class new file mode 100644 index 0000000..0cf5bb3 Binary files /dev/null and b/target/classes/com/example/demo/controller/PageController.class differ diff --git a/target/classes/com/example/demo/controller/UserController.class b/target/classes/com/example/demo/controller/UserController.class new file mode 100644 index 0000000..a8bb129 Binary files /dev/null and b/target/classes/com/example/demo/controller/UserController.class differ diff --git a/target/classes/com/example/demo/event/UserEventListener.class b/target/classes/com/example/demo/event/UserEventListener.class new file mode 100644 index 0000000..a549985 Binary files /dev/null and b/target/classes/com/example/demo/event/UserEventListener.class differ diff --git a/target/classes/com/example/demo/event/UserEventPublisher.class b/target/classes/com/example/demo/event/UserEventPublisher.class new file mode 100644 index 0000000..18c2f45 Binary files /dev/null and b/target/classes/com/example/demo/event/UserEventPublisher.class differ diff --git a/target/classes/com/example/demo/model/User.class b/target/classes/com/example/demo/model/User.class new file mode 100644 index 0000000..13258d3 Binary files /dev/null and b/target/classes/com/example/demo/model/User.class differ diff --git a/target/classes/com/example/demo/model/UserEvent$Type.class b/target/classes/com/example/demo/model/UserEvent$Type.class new file mode 100644 index 0000000..ef2a1bf Binary files /dev/null and b/target/classes/com/example/demo/model/UserEvent$Type.class differ diff --git a/target/classes/com/example/demo/model/UserEvent.class b/target/classes/com/example/demo/model/UserEvent.class new file mode 100644 index 0000000..24093a1 Binary files /dev/null and b/target/classes/com/example/demo/model/UserEvent.class differ diff --git a/target/classes/com/example/demo/service/UserService.class b/target/classes/com/example/demo/service/UserService.class new file mode 100644 index 0000000..fa3f088 Binary files /dev/null and b/target/classes/com/example/demo/service/UserService.class differ diff --git a/target/classes/static/aop.html b/target/classes/static/aop.html new file mode 100644 index 0000000..a10416a --- /dev/null +++ b/target/classes/static/aop.html @@ -0,0 +1,177 @@ + + + + + + AOP 切面编程 - Spring Boot + + + + + +

🔪 AOP 切面编程

+ +
+

📊 实时性能统计

+

AOP 自动统计所有 Controller 和 Service 方法的执行时间

+ +
点击按钮查看...
+
+ +

📚 AOP 核心概念

+ +
+

1. 什么是 AOP?

+

AOP (Aspect-Oriented Programming) 面向切面编程,是将横切关注点业务逻辑分离的编程范式。

+
+ 横切关注点:日志、事务、安全、性能监控等,散布在多个模块中的公共功能。 +
+
+ +
+

2. 核心术语

+ + + + + + + +
术语说明
Aspect (切面)横切关注点的模块化封装
JoinPoint (连接点)程序执行的某个点(方法调用、异常抛出等)
Pointcut (切入点)匹配连接点的表达式
Advice (通知)在连接点执行的动作
Weaving (织入)将切面应用到目标对象的过程
+
+ +
+

3. 五种通知类型

+ + + + + + + +
注解执行时机用途
@Before方法执行前参数校验、权限检查
@After方法执行后(无论成功或异常)资源清理
@AfterReturning方法成功返回后结果处理、日志记录
@AfterThrowing方法抛出异常后异常处理、错误日志
@Around环绕方法执行(最强大)性能统计、事务管理
+
+ +

💻 代码示例

+ +
+

日志切面示例

+
@Aspect
+@Component
+public class LoggingAspect {
+    
+    // 切入点:匹配所有 Controller 方法
+    @Pointcut("execution(* com.example.demo.controller.*.*(..))")
+    public void controllerMethods() {}
+    
+    // 前置通知
+    @Before("controllerMethods()")
+    public void logBefore(JoinPoint jp) {
+        System.out.println("[AOP-Before] 方法开始: " + jp.getSignature().getName());
+    }
+    
+    // 返回通知
+    @AfterReturning(pointcut = "controllerMethods()", returning = "result")
+    public void logAfterReturning(JoinPoint jp, Object result) {
+        System.out.println("[AOP-AfterReturning] 返回值: " + result);
+    }
+}
+
+ +
+

性能监控切面 (@Around)

+
@Aspect
+@Component
+public class PerformanceAspect {
+    
+    @Around("execution(* com.example.demo..*.*(..))")
+    public Object measureTime(ProceedingJoinPoint pjp) throws Throwable {
+        long start = System.currentTimeMillis();
+        
+        try {
+            Object result = pjp.proceed(); // 执行目标方法
+            long duration = System.currentTimeMillis() - start;
+            System.out.println("[AOP] " + pjp.getSignature() + " 耗时: " + duration + "ms");
+            return result;
+        } catch (Throwable e) {
+            System.out.println("[AOP] 方法异常: " + e.getMessage());
+            throw e;
+        }
+    }
+}
+
+ +
+

切入点表达式语法

+
// 匹配任意公共方法
+execution(public * *(..))
+
+// 匹配 com.example 包下所有方法
+execution(* com.example.*.*(..))
+
+// 匹配 Controller 层所有方法
+execution(* com.example.demo.controller.*.*(..))
+
+// 匹配所有 Service 层的 save 开头的方法
+execution(* com.example.demo.service.*.save*(..))
+
+// 匹配带有 @Service 注解的类
+@within(org.springframework.stereotype.Service)
+
+// 匹配带有自定义注解的方法
+@annotation(com.example.demo.aop.RateLimited)
+
+ +

🎯 实际应用场景

+ +
+ + + + + + + + +
场景实现方式
日志记录@Before + @AfterReturning
性能监控@Around
事务管理@Around (Spring 已内置)
权限检查@Before
限流控制@Around + 自定义注解
缓存@Around (Spring Cache)
+
+ +

← 返回学习中心

+ + + + \ No newline at end of file diff --git a/target/classes/static/events.html b/target/classes/static/events.html new file mode 100644 index 0000000..391dc9e --- /dev/null +++ b/target/classes/static/events.html @@ -0,0 +1,213 @@ + + + + + + 事件机制 - Spring Boot + + + + + +

📡 Spring 事件机制

+ +
+

🎉 事件发布演示

+

模拟用户登录事件,观察事件发布和监听过程

+
+ + + +
+
等待事件发布...
+
+ +

🔄 事件机制流程

+ +
+ 发布者 + + ApplicationEventPublisher + + 事件 + + 监听者 +
+ +
+ 核心优势: +
    +
  • 解耦:发布者和监听者互不依赖
  • +
  • 扩展:新增监听器无需修改发布者
  • +
  • 异步:耗时操作不阻塞主流程
  • +
+
+ +

💻 代码实现

+ +
+

1. 定义事件

+
public class UserEvent {
+    public enum Type { CREATED, UPDATED, DELETED, LOGIN }
+    
+    private Type type;
+    private Long userId;
+    private String userName;
+    private LocalDateTime timestamp;
+    
+    // constructor, getters...
+}
+
+ +
+

2. 发布事件

+
@Component
+public class UserEventPublisher {
+    
+    @Autowired
+    private ApplicationEventPublisher eventPublisher;
+    
+    public void publishUserLogin(Long userId, String userName) {
+        UserEvent event = new UserEvent(
+            UserEvent.Type.LOGIN,
+            userId,
+            userName,
+            "用户登录成功"
+        );
+        eventPublisher.publishEvent(event);
+    }
+}
+
+ +
+

3. 监听事件

+
@Component
+public class UserEventListener {
+    
+    // 基础监听
+    @EventListener
+    public void handleUserEvent(UserEvent event) {
+        System.out.println("收到事件: " + event.getType());
+    }
+    
+    // 条件监听 - 只处理登录事件
+    @EventListener(condition = "#event.type == T(com.example.demo.model.UserEvent$Type).LOGIN")
+    public void handleLogin(UserEvent event) {
+        System.out.println("用户登录: " + event.getUserName());
+    }
+    
+    // 异步监听 - 不阻塞主流程
+    @Async
+    @EventListener
+    public void sendWelcomeEmail(UserEvent event) {
+        // 发送邮件...
+    }
+}
+
+ +
+

4. 控制器中使用

+
@RestController
+@RequestMapping("/aop")
+public class AopEventController {
+    
+    @Autowired
+    private UserEventPublisher eventPublisher;
+    
+    @PostMapping("/event/publish")
+    public Map<String, Object> publishEvent(
+            @RequestParam Long userId,
+            @RequestParam String userName) {
+        
+        // 发布事件
+        eventPublisher.publishUserLogin(userId, userName);
+        
+        return Map.of(
+            "message", "事件已发布",
+            "userId", userId,
+            "userName", userName
+        );
+    }
+}
+
+ +

🎯 应用场景

+ +
+ + + + + + +
场景事件类型处理逻辑
用户注册UserCreatedEvent发送欢迎邮件、初始化数据
订单创建OrderCreatedEvent扣库存、发送通知
支付成功PaymentSuccessEvent更新订单状态、发送短信
用户登录UserLoginEvent记录登录日志、更新在线状态
+
+ +
+

💡 最佳实践

+
    +
  • 事件类应该是不可变的(只读属性)
  • +
  • 使用 @Async 处理耗时操作
  • +
  • 避免在监听器中抛出异常
  • +
  • 使用 condition 过滤不需要的事件
  • +
  • 复杂场景考虑使用消息队列(RabbitMQ/Kafka)
  • +
+
+ +

← 返回学习中心

+ + + + \ No newline at end of file diff --git a/target/classes/static/index.html b/target/classes/static/index.html new file mode 100644 index 0000000..937b474 --- /dev/null +++ b/target/classes/static/index.html @@ -0,0 +1,221 @@ + + + + + + Spring Boot 学习中心 + + + +

🍃 Spring Boot 学习中心

+ + + + + +

🔗 快速链接

+ + +

🧪 接口测试

+ +
+

GET 参数示例

+
+ + + +
+
+

GET /learn/params?name=xxx&age=18

+
+ +
+

路径变量示例

+
+ + +
+
+

GET /learn/path/{id}

+
+ +
+

POST JSON 示例

+
+ + +
+
+

POST /learn/body

+
+ +

📖 学习路径

+ +
+

1. IOC 容器

+
    +
  • @Component, @Service, @Repository, @Controller
  • +
  • @Autowired 依赖注入
  • +
  • @Configuration + @Bean 配置类
  • +
+
+ +
+

2. Web 开发

+
    +
  • @RestController = @Controller + @ResponseBody
  • +
  • @RequestMapping, @GetMapping, @PostMapping
  • +
  • @PathVariable, @RequestParam, @RequestBody
  • +
+
+ +
+

3. AOP 切面编程

+
@Aspect
+@Component
+public class LoggingAspect {
+    @Before("execution(* com.example.*.*(..))")
+    public void logBefore(JoinPoint jp) {
+        System.out.println("方法调用: " + jp.getSignature());
+    }
+}
+
+ +
+

4. 事件机制

+
// 发布事件
+@Autowired
+ApplicationEventPublisher publisher;
+publisher.publishEvent(new UserEvent(...));
+
+// 监听事件
+@EventListener
+public void onEvent(UserEvent event) {
+    // 处理事件
+}
+
+ +

📁 项目结构

+
+
├── src/main/java/com/example/demo/
+│   ├── DemoApplication.java       # 启动类
+│   ├── controller/                # 控制器层
+│   │   ├── LearnController.java   # 学习示例
+│   │   ├── UserController.java    # 用户 API
+│   │   └── AopEventController.java
+│   ├── service/                   # 业务逻辑层
+│   ├── model/                     # 实体类
+│   ├── aop/                       # AOP 切面
+│   │   ├── LoggingAspect.java
+│   │   ├── PerformanceAspect.java
+│   │   └── RateLimitAspect.java
+│   └── event/                     # 事件机制
+│       ├── UserEventPublisher.java
+│       └── UserEventListener.java
+├── src/main/resources/
+│   ├── static/                    # 静态资源
+│   │   ├── index.html
+│   │   ├── users.html
+│   │   ├── aop.html
+│   │   └── events.html
+│   └── application.properties     # 配置文件
+└── pom.xml                        # Maven 配置
+
+ + + + + + \ No newline at end of file diff --git a/target/classes/static/users.html b/target/classes/static/users.html new file mode 100644 index 0000000..56a30e5 --- /dev/null +++ b/target/classes/static/users.html @@ -0,0 +1,225 @@ + + + + + + 用户管理 - Spring Boot + + + +

👥 用户管理 - RESTful API 示例

+ +
+
+

用户列表

+ +
+ + + + + + + + + + + +
ID姓名邮箱年龄操作
+
+ +
+

📖 学习要点

+
+ RESTful API 设计: +
    +
  • GET /api/users - 获取所有用户
  • +
  • GET /api/users/{id} - 获取单个用户
  • +
  • POST /api/users - 创建用户
  • +
  • PUT /api/users/{id} - 更新用户
  • +
  • DELETE /api/users/{id} - 删除用户
  • +
+
+ +

Controller 代码示例

+
@RestController
+@RequestMapping("/api/users")
+public class UserController {
+    
+    @GetMapping
+    public List<User> getAllUsers() { ... }
+    
+    @GetMapping("/{id}")
+    public User getUserById(@PathVariable Long id) { ... }
+    
+    @PostMapping
+    public User createUser(@RequestBody User user) { ... }
+    
+    @PutMapping("/{id}")
+    public User updateUser(@PathVariable Long id, @RequestBody User user) { ... }
+    
+    @DeleteMapping("/{id}")
+    public String deleteUser(@PathVariable Long id) { ... }
+}
+
+ +
+

🔧 Spring 注解说明

+ + + + + + + + + +
注解说明
@RestController= @Controller + @ResponseBody
@RequestMapping定义路由映射
@GetMappingGET 请求映射
@PostMappingPOST 请求映射
@PathVariable获取路径变量
@RequestBody获取请求体 JSON
@RequestParam获取查询参数
+
+ +

← 返回学习中心

+ + + + + + + \ No newline at end of file diff --git a/target/demo-0.0.1-SNAPSHOT.jar b/target/demo-0.0.1-SNAPSHOT.jar new file mode 100644 index 0000000..3475deb Binary files /dev/null and b/target/demo-0.0.1-SNAPSHOT.jar differ diff --git a/target/demo-0.0.1-SNAPSHOT.jar.original b/target/demo-0.0.1-SNAPSHOT.jar.original new file mode 100644 index 0000000..a1c91c8 Binary files /dev/null and b/target/demo-0.0.1-SNAPSHOT.jar.original differ diff --git a/target/maven-archiver/pom.properties b/target/maven-archiver/pom.properties new file mode 100644 index 0000000..b665b7b --- /dev/null +++ b/target/maven-archiver/pom.properties @@ -0,0 +1,3 @@ +artifactId=demo +groupId=com.example +version=0.0.1-SNAPSHOT diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst new file mode 100644 index 0000000..0cec6d3 --- /dev/null +++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst @@ -0,0 +1,15 @@ +com/example/demo/aop/RateLimited.class +com/example/demo/model/UserEvent.class +com/example/demo/aop/LoggingAspect.class +com/example/demo/event/UserEventPublisher.class +com/example/demo/controller/AopEventController.class +com/example/demo/controller/LearnController.class +com/example/demo/DemoApplication.class +com/example/demo/event/UserEventListener.class +com/example/demo/controller/PageController.class +com/example/demo/controller/UserController.class +com/example/demo/model/UserEvent$Type.class +com/example/demo/model/User.class +com/example/demo/aop/RateLimitAspect.class +com/example/demo/service/UserService.class +com/example/demo/aop/PerformanceAspect.class diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst new file mode 100644 index 0000000..d3d20ed --- /dev/null +++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -0,0 +1,14 @@ +/home/llm/projects/springboot-demo/src/main/java/com/example/demo/controller/UserController.java +/home/llm/projects/springboot-demo/src/main/java/com/example/demo/model/User.java +/home/llm/projects/springboot-demo/src/main/java/com/example/demo/aop/LoggingAspect.java +/home/llm/projects/springboot-demo/src/main/java/com/example/demo/controller/PageController.java +/home/llm/projects/springboot-demo/src/main/java/com/example/demo/aop/RateLimited.java +/home/llm/projects/springboot-demo/src/main/java/com/example/demo/aop/PerformanceAspect.java +/home/llm/projects/springboot-demo/src/main/java/com/example/demo/service/UserService.java +/home/llm/projects/springboot-demo/src/main/java/com/example/demo/controller/AopEventController.java +/home/llm/projects/springboot-demo/src/main/java/com/example/demo/event/UserEventPublisher.java +/home/llm/projects/springboot-demo/src/main/java/com/example/demo/DemoApplication.java +/home/llm/projects/springboot-demo/src/main/java/com/example/demo/event/UserEventListener.java +/home/llm/projects/springboot-demo/src/main/java/com/example/demo/controller/LearnController.java +/home/llm/projects/springboot-demo/src/main/java/com/example/demo/model/UserEvent.java +/home/llm/projects/springboot-demo/src/main/java/com/example/demo/aop/RateLimitAspect.java