🌐 Spring MVC 知识地图一、核心架构与组件Spring MVC 核心 ├── DispatcherServlet（前端控制器） ├── HandlerMapping（处理器映射） │ ├── RequestMappingHandlerMapping （注解映射，最常用） │ ├── BeanNameUrlHandlerMapping │ └── SimpleUrlHandlerMapping ├── HandlerAdapter（处理器适配器） │ ├── RequestMappingHandlerAdapter │ ├── HttpRequestHandlerAdapter │ └── SimpleControllerHandlerAdapter ├── Handler / Controller（处理器） ├── ModelAndView（模型与视图封装） ├── ViewResolver（视图解析器） │ ├── InternalResourceViewResolver（JSP） │ ├── BeanNameViewResolver │ ├── XmlViewResolver │ ├── ContentNegotiatingViewResolver │ └── …… └── View（视图） ├── JSP / JSTL ├── Thymeleaf ├── FreeMarker └── ……二、请求处理完整流程客户端请求 → DispatcherServlet 接收 ├── 1. 通过 HandlerMapping 查找 Handler（返回 HandlerExecutionChain，包含拦截器） ├── 2. 通过 HandlerAdapter 执行 Handler（Controller） ├── 3. Handler 执行业务逻辑，返回 ModelAndView ├── 4. 通过 ViewResolver 解析 ModelAndView 得到 View 对象 └── 5. View 渲染，将模型数据填充到视图，生成响应三、控制器开发Controller 开发 ├── 注解 │ ├── @Controller / @RestController │ ├── @RequestMapping │ │ ├── @GetMapping │ │ ├── @PostMapping │ │ ├── @PutMapping │ │ ├── @DeleteMapping │ │ └── @PatchMapping │ └── @CrossOrigin（跨域） ├── 请求参数绑定 │ ├── @RequestParam │ ├── @PathVariable（路径变量） │ ├── @RequestHeader │ ├── @CookieValue │ ├── @MatrixVariable │ ├── @RequestBody（JSON/XML 等） │ ├── @ModelAttribute（表单/查询参数绑定到对象） │ ├── HttpServletRequest / HttpServletResponse │ └── MultipartFile（文件上传） ├── 返回类型 │ ├── String（视图名，配合 Model） │ ├── ModelAndView │ ├── void（直接操作 HttpServletResponse） │ ├── @ResponseBody / ResponseEntity（JSON/XML 响应） │ └── View、Model、Map 等 └── 异步处理 ├── Callable<T> ├── DeferredResult<T> └── ResponseBodyEmitter / SseEmitter / StreamingResponseBody四、数据绑定、类型转换与校验数据绑定与校验 ├── 类型转换 │ ├── PropertyEditor │ ├── Converter / ConverterFactory │ ├── Formatter（日期/数字等格式化） │ └── @InitBinder（自定义绑定规则，注册编辑器/校验器） ├── 数据校验 │ ├── JSR-380（Bean Validation）：@Valid / @Validated │ ├── BindingResult / Errors（校验结果） │ └── 自定义校验注解 └── WebDataBinder └── 控制绑定行为（允许/禁止字段等）五、RESTful Web 服务REST 支持 ├── @RestController = @Controller + @ResponseBody ├── HTTP 方法映射注解 ├── 内容协商 ContentNegotiationManager ├── HttpMessageConverter 消息转换器 │ ├── MappingJackson2HttpMessageConverter（JSON） │ ├── Jaxb2RootElementHttpMessageConverter（XML） │ ├── StringHttpMessageConverter │ └── FormHttpMessageConverter 等 ├── 请求/响应体处理 │ ├── @RequestBody │ └── @ResponseBody / ResponseEntity / HttpEntity ├── 跨域 @CrossOrigin / 全局 CORS 配置 └── HATEOAS 支持（Spring HATEOAS）六、视图解析与渲染视图技术 ├── JSP + JSTL │ └── InternalResourceViewResolver 配置 ├── Thymeleaf │ └── SpringTemplateEngine + ThymeleafViewResolver ├── FreeMarker │ └── FreeMarkerViewResolver ├── 重定向与转发 │ ├── "redirect:/path"（RedirectView） │ └── "forward:/path"（InternalResourceView） ├── 视图解析链（多个 ViewResolver 顺序解析） └── ContentNegotiatingViewResolver（根据请求类型选择视图）七、拦截器与过滤器拦截器 HandlerInterceptor（Spring MVC 层面） ├── 方法 │ ├── preHandle（控制器执行前） │ ├── postHandle（控制器执行后，视图渲染前） │ └── afterCompletion（视图渲染后） ├── 配置方式 │ ├── XML 配置 <mvc:interceptors> │ └── Java 配置实现 WebMvcConfigurer.addInterceptors() └── 与 Servlet Filter 的区别 ├── Filter 在 Servlet 容器层面，不依赖 Spring └── Interceptor 可访问 Handler 对象、ModelAndView，更贴合 MVC八、异常处理异常处理体系 ├── @ExceptionHandler（方法级，仅处理当前 Controller） ├── @ControllerAdvice / @RestControllerAdvice（全局异常处理） │ └── + @ExceptionHandler、@InitBinder、@ModelAttribute ├── HandlerExceptionResolver 接口 │ ├── SimpleMappingExceptionResolver（映射异常到错误视图） │ ├── DefaultHandlerExceptionResolver（处理标准 Spring 异常） │ └── ResponseStatusExceptionResolver ├── @ResponseStatus（标注异常，返回对应 HTTP 状态码） └── 自定义异常 + 全局处理九、文件上传与下载文件操作 ├── 上传 │ ├── MultipartResolver │ │ ├── CommonsMultipartResolver（需 Apache Commons FileUpload） │ │ └── StandardServletMultipartResolver（Servlet 3.0+ 标准） │ └── @RequestParam("file") MultipartFile ├── 下载 │ ├── ResponseEntity<byte[]> 配合 InputStreamResource │ ├── StreamingResponseBody（流式大文件下载） │ └── 设置 Content-Disposition 响应头 └── 多文件上传（MultipartFile[] 或 List<MultipartFile>）十、国际化与主题i18n & 主题 ├── MessageSource（国际化消息源） │ ├── ResourceBundleMessageSource │ └── ReloadableResourceBundleMessageSource（可热加载） ├── LocaleResolver（区域解析器） │ ├── AcceptHeaderLocaleResolver │ ├── SessionLocaleResolver │ ├── CookieLocaleResolver │ └── FixedLocaleResolver ├── LocaleChangeInterceptor（切换语言拦截器） ├── 主题 Theme │ ├── ThemeResolver │ └── ThemeSource（ResourceBundleThemeSource）十一、配置方式配置方式演进 ├── XML 时代 │ ├── web.xml 配置 DispatcherServlet │ ├── [name]-servlet.xml 配置 │ ├── <mvc:annotation-driven/> │ ├── <mvc:resources>（静态资源处理） │ └── <mvc:view-controller>（无逻辑视图跳转） ├── Servlet 3.0+ 纯 Java 配置 │ ├── WebApplicationInitializer / AbstractAnnotationConfigDispatcherServletInitializer │ ├── @Configuration + @EnableWebMvc │ ├── 实现 WebMvcConfigurer 接口进行定制 │ └── 静态资源 addResourceHandlers() └── Spring Boot 自动配置 ├── spring-boot-starter-web ├── 自动配置 DispatcherServlet、HttpMessageConverters、ViewResolver 等 ├── application.properties / yml 微调 └── 使用 @Configuration + WebMvcConfigurer 扩展十二、测试支持测试 ├── MockMvc（独立测试控制器，不启动完整服务器） │ ├── mockMvc.perform(get("/api")) │ ├── .andExpect(status().isOk()) │ └── .andExpect(jsonPath("$.name").value("test")) ├── @WebMvcTest（Spring Boot 切片测试，仅加载 MVC 层） ├── TestRestTemplate / WebTestClient（集成测试） └── Mock 服务依赖（@MockBean）十三、高级与周边高级特性 ├── CORS 全局配置（WebMvcConfigurer.addCorsMappings） ├── HTTP 缓存控制（CacheControl, ETag, Last-Modified） ├── Servlet 异步支持 / 响应式桥梁 ├── WebSocket 集成（@MessageMapping, SimpMessagingTemplate） ├── Swagger / OpenAPI 3 文档生成 ├── 多版本 API 管理 └── 自定义 HandlerMethodArgumentResolver / HandlerMethodReturnValueHandler如果需要某个分支更详细的逐层展开，或想看 Spring MVC 与 Spring Boot 整合的实例脑图，可以告诉我。
Spring MVC 请求生命周期SpringBoot 2.7 + Java8 企业级开发一、整体架构图 —— 一次请求的完整流水线HTTP Request -> http://你的项目/getUser?id=1 │ ▼ ┌─────────────────────┐ │ Tomcat │ 内嵌 Servlet 容器，最早接收请求 │ Servlet Container │ └─────────────────────┘ │ ▼ ┌─────────────────────┐ │ Filter 过滤器链 │ Servlet 级别的“门卫”，工作在 Spring MVC 之前 └─────────────────────┘ │ ▼ ┌─────────────────────┐ │ DispatcherServlet │ Spring MVC 的“总控”，分发请求的核心 └─────────────────────┘ │ ▼ ┌─────────────────────┐ │ HandlerInterceptor │ Spring 自己的拦截器，更懂 Controller │ preHandle │ 请求到达 Controller 前执行 └─────────────────────┘ │ ▼ ┌─────────────────────┐ │ Controller │ 你写的接口，@RestController / @GetMapping └─────────────────────┘ │ ▼ ┌─────────────────────┐ │ Service │ 业务逻辑层 └─────────────────────┘ │ ▼ ┌─────────────────────┐ │ DAO/JdbcTemplate │ 数据访问层，执行 SQL └─────────────────────┘ │ ▼ ┌─────────────────────┐ │ MySQL │ 数据库 └─────────────────────┘ │ ▼ Controller返回结果 返回 Java 对象 │ ▼ Interceptor.postHandle 所有拦截器的 postHandle │ ▼ ViewResolver / JSON序列化 前后端分离时转成 JSON 字符串 │ ▼ Interceptor.afterCompletion 资源清理、日志记录 │ ▼ Response → 浏览器二、企业项目真实执行顺序（必背）虚假的顺序：Tomcat -> Interceptor -> Controller实际严格顺序：Tomcat -> Filter -> DispatcherServlet -> Interceptor -> Controller -> Service -> DAO -> MySQL为什么是这个顺序？Filter 是 Servlet 规范 定义的，工作在容器级别，比 Spring 更底层；Interceptor 是 Spring MVC 内部 的，只有在 DispatcherServlet 接收请求之后才生效。也就是说：Filter 可以拦截所有请求（包括静态资源），而 Interceptor 只能拦截进入 Spring MVC 的请求。三、Filter 和 Interceptor 区别 —— 两个不同级别的“保安”1. Filter（Servlet 级别）定义方式：实现 javax.servlet.Filter 接口执行时机：Tomcat → Filter → Spring MVC特点：工作在容器层，不依赖 Spring，拿不到 Controller、方法等 Spring 上下文信息。典型用途：JWT 登录校验（直接解析 token，失败直接返回 401）XSS 过滤（清洗 等危险字符）请求日志（记录每个请求的耗时、URL）跨域处理（CorsFilter 统一设置响应头）2. Interceptor（Spring MVC 级别）定义方式：实现 org.springframework.web.servlet.HandlerInterceptor 接口执行时机：DispatcherServlet → Interceptor → Controller特点：能获取到 Handler（Controller 方法）、参数、返回结果等，是 Spring 生态内的拦截手段。典型用途：用户信息注入（从 token 解析用户信息，放入 UserContext）权限校验（admin / user 角色判断）操作日志（记录“谁在何时做了什么”）API 访问统计（PV / UV 计数）面试标准答案Filter 先执行，Interceptor 后执行。 Filter 是 Servlet 规范，Intercetpor 是 Spring MVC 专属。四、DispatcherServlet 到底干了什么 —— 核心方法 doDispatch()设计模式：前端控制器模式（Front Controller Pattern）所有请求都会先被 DispatcherServlet 接管，它自己不干活，只负责指挥。源码简化流程：doDispatch() { // 1. 根据请求找到 Handler（Controller 方法）并包装成 HandlerExecutionChain HandlerExecutionChain chain = getHandler(request); // 2. 执行所有拦截器的 preHandle chain.applyPreHandle(request, response); // 3. 真正调用 Controller 方法 HandlerAdapter adapter = getHandlerAdapter(handler); ModelAndView mv = adapter.handle(request, response, handler); // 4. 执行所有拦截器的 postHandle chain.applyPostHandle(request, response, mv); // 5. 处理返回结果（JSON 序列化、视图渲染等） processDispatchResult(request, response, chain, mv, exception); // 6. 触发拦截器的 afterCompletion（总是执行） chain.triggerAfterCompletion(request, response, exception); }五、HandlerExecutionChain —— Controller + 拦截器的组合体请求到达 DispatcherServlet 后，getHandler() 方法会将你的 Controller 方法和所有适用的拦截器打包成一个执行链。结构示意：HandlerExecutionChain ├── Handler（你的 Controller 方法） ├── Interceptor A ├── Interceptor B └── Interceptor C执行顺序（链式调用，具有栈特性）：进入时：A.pre → B.pre → C.pre → Controller返回时：C.post → B.post → A.post最终完成：C.after → B.after → A.after（即使前面出异常也会执行）六、@RequestScope 的底层原理 —— 代理对象，而非每次都 new很多人以为 @RequestScope 每次请求都会创建一个全新的 Bean，实际上 Spring 注入的是一个代理对象。特别提醒：代理对象通过当前线程的 ThreadLocal 拿到绑定的请求上下文，从而找到属于当前请求的那个真实 @RequestScope 实例。如果在 Controller 里用 @Async 等开了新线程，原请求的 ThreadLocal 会丢失，导致代理找不到请求上下文而直接报错。@Autowired private UserContext userContext;实际注入的是 UserContext$$SpringProxy 代理类。该代理对象内部并不持有真实数据，当真正调用其方法时，它会从当前请求的 RequestAttributes 中动态获取属于当前请求的真实实例。流程：当前线程 → RequestAttributes → 真实的 UserContext好处：即便 Controller 是单例（Singleton），也能安全地使用 @RequestScope 的 Bean，因为每次调用都会通过代理去获取当前请求对应的真实对象。七、ThreadLocal 与 @RequestScope —— 如何安全地存放用户信息在企业项目中，经常需要在任何地方（Service、DAO）获取当前登录用户，常用两种方案。方案实现方式优点缺点最佳实践ThreadLocalUserHolder.set(user) / UserHolder.get()极快，线程隔离必须手动 remove()，否则线程池复用时会内存泄漏或数据串人在拦截器的 afterCompletion 中清理@RequestScope注解在 Spring 管理的 Bean 上Spring 自动管理生命周期，请求结束自动销毁，无泄漏风险性能比 ThreadLocal 多一点点反射/代理开销优先推荐，简单安全推荐：存放用户上下文信息时，优先选择 @RequestScope。只有在对性能要求极高、并发极大的场景下，才考虑 ThreadLocal，并确保在拦截器的 afterCompletion 中执行 remove()。八、Controller 参数解析过程 —— 为什么参数能自动绑定你写的 public Result get(Long id)，Spring 能自动从请求里拿到 id，靠的是 HandlerMethodArgumentResolver 接口。该解析器会遍历判断当前参数是否能被自己解析，若能则从 request 中取值并绑定。常见注解与对应解析器：注解解析器@RequestParamRequestParamMethodArgumentResolver@PathVariablePathVariableMethodArgumentResolver@RequestBodyRequestResponseBodyMethodProcessor@RequestHeaderRequestHeaderMethodArgumentResolver例如：@RequestHeader("token") String token 背后就是 RequestHeaderMethodArgumentResolver 在干活。你也可以实现该接口并注册，来支持自定义的参数注入（比如自动注入当前登录用户对象）。九、JSON 如何变成 Java 对象 —— HttpMessageConverter前端发送 JSON：POST { "name":"Tom" }到后端自动变成 UserDTO 对象，核心组件是 HttpMessageConverter。默认使用的实现是 MappingJackson2HttpMessageConverter，底层依赖 Jackson。请求方向：请求体 JSON → Jackson 反序列化 → Java 对象 → Controller 参数响应方向：Controller 返回的 Java 对象 → Jackson 序列化 → JSON 字符串 → HTTP 响应因此 Jackson 是 Spring Boot 中处理 JSON 的事实标准，排查问题时经常要关注它。十、统一异常处理 —— 别再到处 try-catch在企业项目中，应使用 @RestControllerAdvice 进行全局异常拦截，避免在每个 Controller 方法中写重复的 try-catch。@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(Exception.class) public Result handleException(Exception e) { log.error("系统异常", e); return Result.fail(e.getMessage()); } @ExceptionHandler(BusinessException.class) public Result handleBusinessException(BusinessException e) { return Result.fail(e.getCode(), e.getMessage()); } }执行流程：Controller 抛出异常 ↓ HandlerExceptionResolver 捕获 ↓ 找到匹配的 @ExceptionHandler 方法 ↓ 返回统一的错误响应这样 Controller 只需关注正常业务逻辑，所有异常都会流入全局处理器，返回规范的接口格式。十一、完整认证流程 —— 不要只相信 Token真正的安全认证绝不能只解析 token，而必须结合 Redis 等存储做二次校验。标准流程：1. 请求携带 JWT token 2. Interceptor 解析 token 得到 userId 3. 去 Redis 查询该 userId 对应的完整用户信息（含权限、状态） 4. 如果 Redis 中不存在或 token 已失效 → 直接返回 401 5. 将用户信息放入 UserContext（@RequestScope 管理的 Bean） 6. Controller 直接从 UserContext 获取用户信息，无需再解析 token为什么要加 Redis？Token 可能被伪造（弱签名或密钥泄露）用户可能已被管理员禁用，但 token 还未过期用户执行了“退出登录”，token 理应立刻失效总结：Token 只负责提供身份标识（userId），真正的用户资料和权限应从 Redis（或数据库）获取，并在请求上下文中传递。十二、Spring MVC 八大核心组件（高级理解）DispatcherServlet 只是总控，真正复杂的工作由这些组件完成：组件作用重要性HandlerMapping根据请求 URL 找到对应的 Handler（Controller 方法）★★★HandlerAdapter真正执行 Handler，适配不同类型的处理器★★★HandlerExceptionResolver处理异常，分发给 @ExceptionHandler 或 @ControllerAdvice★★★ViewResolver解析视图名，找到具体视图（前后端分离时用得少）★MultipartResolver处理文件上传请求★LocaleResolver国际化，解析客户端的语言/地区★ThemeResolver主题切换（老旧项目中常见）★FlashMapManager用于重定向时保存临时数据★其中最核心的三个：HandlerMapping、HandlerAdapter、HandlerExceptionResolver，理解它们才能真正把握 Spring MVC 的执行原理。总结 —— 一条线全部串起来完整请求处理链路：Tomcat → Filter（登录校验、XSS、日志、跨域） → DispatcherServlet → HandlerMapping（找到 Controller 方法） → HandlerInterceptor.preHandle（权限、用户注入） → HandlerAdapter（执行方法） → Controller → Service → DAO / JdbcTemplate → MySQL → （若有异常）HandlerExceptionResolver → @RestControllerAdvice 统一处理 → HttpMessageConverter（Java 对象 ↔ JSON） → HandlerInterceptor.postHandle / afterCompletion → 返回 Response日常企业开发（SpringBoot 2.7 + JdbcTemplate + MySQL + Redis + JWT）中最核心的10个知识点：Filter — 容器层拦截，适合安全校验、跨域、日志Interceptor — Spring MVC 拦截，适合用户注入、权限、统计@RequestScope — 请求作用域 Bean，底层用代理实现ThreadLocal — 线程隔离的上下文存储，需手动清理HandlerMethodArgumentResolver — 参数解析器，实现自动注入HttpMessageConverter — JSON / 对象互转，底层 Jackson@RestControllerAdvice — 全局异常处理，不要到处 try-catchDispatcherServlet — 前端总控制器，调度所有请求HandlerExecutionChain — 拦截器与 Controller 的打包执行链Spring MVC 九大组件 — 理解项目架构离不开这些支撑组件
1. 策略模式 ⭐⭐⭐⭐⭐消灭 if-else 的核心武器，基于 Spring 容器自动选择策略。步骤：定义策略接口public interface Strategy { String getType(); void execute(); }实现多个策略并用 @Component 注册@Component public class StrategyA implements Strategy { @Override public String getType() { return "A"; } @Override public void execute() { /* ... */ } } @Component public class StrategyB implements Strategy { @Override public String getType() { return "B"; } @Override public void execute() { /* ... */ } }创建策略选择器@Component public class StrategySelector { private final Map<String, Strategy> strategyMap = new HashMap<>(); // 4. Spring自动注入所有Strategy实现到Map（详见文档末尾“注入条件”） public StrategySelector(Map<String, Strategy> strategyMap) { // 遍历注入的Map，按业务key重组 strategyMap.values().forEach(s -> this.strategyMap.put(s.getType(), s)); } // 5. 选择策略 public Strategy getStrategy(String type) { Strategy strategy = strategyMap.get(type); if (strategy == null) { throw new IllegalArgumentException("No strategy for: " + type); } return strategy; } }2. 工厂模式 ⭐⭐⭐⭐⭐创建对象的“万能工厂”，通过 Spring 容器自动装配产品族。步骤：定义产品接口public interface Product { String getType(); void doSomething(); }多个产品实现并注册@Component public class ProductA implements Product { @Override public String getType() { return "A"; } @Override public void doSomething() { /*...*/ } } @Component public class ProductB implements Product { @Override public String getType() { return "B"; } @Override public void doSomething() { /*...*/ } }创建工厂类（Spring自动注入所有产品）@Component public class ProductFactory { private final Map<String, Product> productMap; // 构造器注入：Spring会将所有Product的实现注入到Map中（key=bean名称，默认类名首字母小写） public ProductFactory(Map<String, Product> productMap) { this.productMap = productMap; } public Product create(String type) { Product product = productMap.get(type); // 或自定义getType()映射 if (product == null) throw new IllegalArgumentException("Unknown product type"); return product; } }与策略模式的区别：工厂侧重于创建/获取对象，策略侧重行为互换，但 Spring 集成写法相似。3. 责任链模式 ⭐⭐⭐⭐⭐多个处理器依次尝试，直到有人处理。步骤：定义请求上下文public class RequestContext { private boolean approved = false; private String rejectReason; private String approvedBy; // getters & setters... }编写 Handler 接口public interface Handler { boolean handle(RequestContext context); // true=处理完毕，不再传递 }具体处理器（@Component + @Order 控制顺序）@Component @Order(1) public class ManagerHandler implements Handler { public boolean handle(RequestContext context) { // 逻辑：通过则设置approved=true，返回true；否则返回false return true; } } @Component @Order(2) public class DirectorHandler implements Handler { /* ... */ }组装责任链（@PostConstruct 确保只排序一次）@Component public class HandlerChain { private final List<Handler> handlers; public HandlerChain(List<Handler> handlers) { this.handlers = handlers; } @PostConstruct public void init() { AnnotationAwareOrderComparator.sort(handlers); } public void process(RequestContext context) { for (Handler handler : handlers) { if (handler.handle(context)) break; } } }使用@Autowired HandlerChain chain; // ... RequestContext ctx = new RequestContext(); chain.process(ctx);4. 模板方法模式 ⭐⭐⭐⭐⭐父类定义骨架，子类填写细节。步骤：抽象模板类public abstract class AbstractExportService { public final void export() { prepareData(); writeFile(); cleanUp(); } protected abstract void prepareData(); protected abstract void writeFile(); protected void cleanUp() { /* 钩子：默认空实现 */ } }具体子类@Component public class ExcelExportService extends AbstractExportService { @Override protected void prepareData() { /*...*/ } @Override protected void writeFile() { /*...*/ } }Controller中使用（注入所有子类）@RestController public class ExportController { private final Map<String, AbstractExportService> exportServiceMap; // 注入时 key 为 bean 名称（类名首字母小写，或用@Service("excel")指定） public ExportController(Map<String, AbstractExportService> exportServiceMap) { this.exportServiceMap = exportServiceMap; } @GetMapping("/export/{type}") public void doExport(@PathVariable String type) { exportServiceMap.get(type).export(); } }5. 代理模式 ⭐⭐⭐⭐⭐控制对象访问，增强功能（Spring AOP 的基础）。Spring 推荐方式：声明式：@Transactional、@Cacheable 等本质是动态代理。手动示例：统计方法耗时。@Component public class SimpleService implements IService { @Override public void work() { /* ... */ } } @Component public class ServiceProxy implements IService { private final IService target; public ServiceProxy(@Qualifier("simpleService") IService target) { this.target = target; } @Override public void work() { long start = System.currentTimeMillis(); target.work(); System.out.println("cost: " + (System.currentTimeMillis() - start)); } }更常用的是直接使用 Spring AOP（@Aspect + @Around）实现动态代理。6. 状态模式 ⭐⭐⭐⭐⭐将状态逻辑拆分为独立的状态对象，避免大量 if-else。步骤（订单状态机）：状态接口public interface OrderState { void pay(OrderContext ctx); void ship(OrderContext ctx); }具体状态@Component public class PendingPaymentState implements OrderState { public void pay(OrderContext ctx) { // 支付成功后切换状态 ctx.setState(ctx.getPaidState()); } public void ship(OrderContext ctx) { throw new UnsupportedOperationException(); } } // PaidState, ShippedState... 同理上下文持有状态@Component public class OrderContext { private OrderState current; // 注入所有状态 @Autowired private PendingPaymentState pendingPaymentState; @Autowired private PaidState paidState; //... getters/setters，初始化为 pendingPaymentState }Spring 中可使用状态枚举配合 Map<OrderStatus, OrderState> 自动注入。7. 观察者模式 ⭐⭐⭐⭐一对多通知，Spring 事件机制天然支持。步骤：定义事件public class OrderCreatedEvent extends ApplicationEvent { private final Long orderId; public OrderCreatedEvent(Object source, Long orderId) { super(source); this.orderId = orderId; } // getter }发布事件@Component public class OrderService { @Autowired private ApplicationEventPublisher publisher; public void createOrder() { // 业务... publisher.publishEvent(new OrderCreatedEvent(this, orderId)); } }监听事件（可使用 @EventListener 或 @TransactionalEventListener）@Component public class SendSmsListener { @EventListener public void handleOrderCreated(OrderCreatedEvent event) { // 发短信 } }8. 门面模式 ⭐⭐⭐⭐为复杂子系统提供统一入口，Service 层常见模式。示例： 下单门面聚合库存、支付、物流三个服务。@Component public class OrderFacade { @Autowired private InventoryService inventoryService; @Autowired private PaymentService paymentService; @Autowired private LogisticsService logisticsService; public void placeOrder(OrderDTO dto) { inventoryService.reduceStock(dto.getSku()); paymentService.charge(dto.getAmount()); logisticsService.scheduleDelivery(dto.getOrderId()); } }9. 装饰器模式 ⭐⭐⭐⭐动态附加责任，比继承更灵活。示例： 对数据读写组件增加缓存装饰器。public interface DataService { String read(); } @Component("basicDataService") public class BasicDataService implements DataService { /*...*/ } @Component public class CacheDecorator implements DataService { private final DataService delegate; public CacheDecorator(@Qualifier("basicDataService") DataService delegate) { this.delegate = delegate; } @Override public String read() { // 先查缓存，没有则调用delegate并缓存 return delegate.read(); } }10. 适配器模式 ⭐⭐⭐⭐兼容两个不匹配的接口，常用于整合外部系统。// 目标接口 public interface Logger { void log(String msg); } // 被适配的第三方类 public class ExternalLogLib { public void writeLog(String text) { /*...*/ } } // 适配器 @Component public class ExternalLogAdapter implements Logger { @Autowired private ExternalLogLib externalLogLib; @Override public void log(String msg) { externalLogLib.writeLog(msg); } }11. DDD（领域驱动设计） ⭐⭐⭐⭐⭐⭐聚焦核心业务复杂度，分层架构与战略设计。核心步骤（简化分层）：用户接口层：Controller，接收请求并调用应用服务。应用层：@Service 应用服务，编排领域对象，不包含业务逻辑。领域层：实体（Entity）、值对象（Value Object）、聚合根（Aggregate Root）、领域服务（Domain Service）、仓储接口（Repository Interface）。基础设施层：仓储实现（@Repository），将聚合持久化到数据库。关键原则：领域层无任何 Spring 注解，纯 POJO。通过构造器注入将仓储接口注入领域服务或应用服务。聚合内通过根实体确保一致性。12. Event Driven（事件驱动架构） ⭐⭐⭐⭐⭐⭐通过事件解耦服务，提升伸缩性和最终一致性。Spring 实现：本地事件：ApplicationEventPublisher + @EventListener（见观察者模式）。分布式事件：集成 Kafka / RabbitMQ。// 发布领域事件到消息队列 @Autowired private KafkaTemplate<String, DomainEvent> kafkaTemplate; public void publish(DomainEvent event) { kafkaTemplate.send("topic-name", event); } // 消费 @KafkaListener(topics = "topic-name") public void handle(DomainEvent event) { /*...*/ }推荐配合 @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) 保证事务提交后再发消息。13. Saga ⭐⭐⭐⭐⭐⭐分布式事务的最终一致性方案，通过协调正向/补偿操作。编排型 Saga（借助事件驱动）：每个微服务监听前序事件，执行本地事务并发布新事件。若失败，发布补偿事件，上游监听并回滚。// 服务A发布订单创建事件，服务B监听并扣库存 // 若扣库存失败，服务B发布库存不足事件，服务A监听后进行补偿（取消订单）实现时可配合 Spring Cloud Stream / Axon Framework。14. CQRS（命令查询职责分离） ⭐⭐⭐⭐⭐⭐读写模型分离，最大化性能与扩展性。简单实现：命令侧：应用服务接收 Command，调用领域逻辑，写入主库。查询侧：@Service 直接从读库或缓存查询 DTO。// 命令 @PostMapping public void createOrder(@RequestBody CreateOrderCmd cmd) { commandService.handle(cmd); } // 查询（独立Controller或同一模块内分离） @GetMapping("/orders/{id}") public OrderView queryOrder(@PathVariable Long id) { return queryService.findById(id); }高级玩法：事件溯源，将所有状态变更存储为事件流，查询视图由事件投影构建。SpringBoot 2.7 注入条件（集合/Map注入）核心行为： Spring 会自动将所有实现了某个接口或父类的 Bean 注入到 List 或 Map 中。注入 List： @Autowired List<SomeInterface> list; 会按 Order 排序包含所有实现类的 Bean。注入 Map： @Autowired Map<String, SomeInterface> map; key 为 Bean 的名称（默认类名首字母小写，若用 @Service("myName") 则用指定名称），value 为实例。没有实现类时： Spring 默认会注入一个空集合（EmptyList/EmptyMap），不会报错。若希望强制存在才注入，可使用 @Autowired(required = true)，但注入集合时 required=true 仍允许空集合，除非你将它用在非集合字段上。推荐方式： 构造器注入，显式声明 final 字段，避免 @Autowired 注解（Spring 4.3+ 单构造器自动注入）。@Component public class StrategySelector { private final Map<String, Strategy> strategyMap = new HashMap<>(); // 构造器注入：Spring 自动将所有的 Strategy 实现类注入 Map public StrategySelector(Map<String, Strategy> allStrategies) { allStrategies.values().forEach(s -> strategyMap.put(s.getType(), s)); } }在 Spring Boot 2.7 (Spring Framework 5.3.x) 下此行为完全稳定，完美支持策略/工厂等模式的自动装配。
反射 + AOP + 注解 讲解目录注解（Annotation）什么是注解元注解与自定义注解如何在反射中读取注解反射（Reflection）反射的核心类：Class、Method、Field动态创建对象、调用方法、访问字段反射与注解的结合AOP（面向切面编程）AOP 的核心概念Spring AOP 的实现原理（代理）通知类型与切入点表达式如何在切面中使用反射和注解三剑合璧实战案例案例1：自定义 @Log 日志记录案例2：自定义 @RequirePermission 权限控制总结与关系图谱一、注解（Annotation）1.1 什么是注解？注解是 Java 5 引入的一种元数据，它本身不影响代码逻辑，但可以被编译、运行时工具或框架读取，从而赋予代码额外的语义或行为。常见注解如 @Override、@Deprecated、@Autowired 等。1.2 元注解定义注解时需要用到的注解，共有四个：元注解作用@Target指定注解能用在哪里（类、方法、字段等）@Retention指定注解的存活周期（源码、字节码、运行时）@Documented是否包含在 JavaDoc 中@Inherited是否允许子类继承父类的注解@Retention 的取值：RetentionPolicy.SOURCE：只在源码中存在，编译时丢弃（如 @Override）RetentionPolicy.CLASS：在字节码中存在，但运行时不可见（默认）RetentionPolicy.RUNTIME：运行时可通过反射读取（自定义注解必须选这个）1.3 自定义一个注解import java.lang.annotation.*; @Target(ElementType.METHOD) // 只能用在方法上 @Retention(RetentionPolicy.RUNTIME) // 运行时保留 public @interface Loggable { String value() default ""; // 注解属性 }使用：@Loggable("删除用户") public void deleteUser(Long id) { ... }1.4 在反射中读取注解无论是类、方法还是字段，反射 API 都提供了读取注解的方法：getAnnotation(Class<T> annotationClass) —— 获取指定类型的注解getAnnotations() —— 获取所有注解（含继承）getDeclaredAnnotations() —— 获取直接声明的注解（不含继承）isAnnotationPresent(Class<?> annotationClass) —— 判断是否存在某注解示例：读取方法上的 @LoggableMethod method = ...; // 通过反射拿到方法对象 Loggable log = method.getAnnotation(Loggable.class); if (log != null) { System.out.println("描述：" + log.value()); }二、反射（Reflection）2.1 反射的核心类类作用获取方式Class类的元信息对象.getClass()、类名.class、Class.forName("全类名")Method方法信息Class 对象的 getMethod(...)、getDeclaredMethods() 等Field字段信息Class 对象的 getField(...)、getDeclaredFields() 等Constructor构造器信息Class 对象的 getConstructor(...)、getDeclaredConstructors() 等2.2 获取 Class 对象的三种方式// 1. 通过类名.class Class<String> clz1 = String.class; // 2. 通过实例的 getClass() String s = "hello"; Class<?> clz2 = s.getClass(); // 3. 通过完全限定名字符串 Class<?> clz3 = Class.forName("java.lang.String");2.3 获取 Method 对象方法说明getMethod(name, paramTypes...)获取指定名称和参数的 public 方法（含继承）getMethods()获取所有 public 方法（含继承）getDeclaredMethod(name, paramTypes...)获取本类中声明的方法（不含继承，可访问私有）getDeclaredMethods()获取本类中声明的所有方法（不含继承）示例：获取 deleteUser(Long) 方法Method method = UserService.class.getMethod("deleteUser", Long.class);2.4 动态调用方法UserService target = new UserService(); Method method = UserService.class.getMethod("deleteUser", Long.class); Object result = method.invoke(target, 123L); // 相当于 target.deleteUser(123L)如果方法是私有的，需要先 setAccessible(true)。2.5 反射与注解的结合反射的 Method、Field、Class 等都实现了 AnnotatedElement 接口，因此可以直接读取注解。Method method = ...; if (method.isAnnotationPresent(Loggable.class)) { Loggable log = method.getAnnotation(Loggable.class); System.out.println("需要记录日志：" + log.value()); }应用场景：框架（Spring、Hibernate）通过反射扫描注解，完成依赖注入、ORM 映射等。自定义工具类，根据注解动态生成 SQL、校验参数等。三、AOP（面向切面编程）3.1 AOP 核心概念切面（Aspect）：封装横切逻辑的模块（比如日志、权限）。连接点（JoinPoint）：程序执行过程中的一个点（方法调用、字段访问等），Spring AOP 只支持方法连接点。切入点（Pointcut）：匹配连接点的表达式，决定哪些方法会被增强。通知（Advice）：在切入点处执行的动作（具体逻辑）。目标对象（Target）：被代理的原始对象。代理（Proxy）：包裹目标对象，加入切面逻辑后生成的对象。3.2 Spring AOP 的实现Spring AOP 基于动态代理：如果目标类实现了接口，默认使用 JDK 动态代理（java.lang.reflect.Proxy）。如果没有接口，则使用 CGLIB 生成子类作为代理。从 Spring Boot 2.0 开始，默认使用 CGLIB 代理（即使有接口也会用 CGLIB）。3.3 通知类型与切入点表达式通知类型注解说明@Before目标方法执行前@AfterReturning目标方法正常返回后@AfterThrowing目标方法抛出异常后@After目标方法结束后（类似 finally）@Around包围目标方法，可控制是否执行常用切入点表达式表达式含义execution(* com.example.service.*.*(..))匹配 service 包下的所有方法@annotation(com.example.Loggable)匹配带有 @Loggable 注解的方法execution(public * *(..))匹配所有 public 方法within(com.example.service.*)匹配 service 包下的所有类的所有方法3.4 切面中如何使用反射和注解在 @Around 通知中，通过 ProceedingJoinPoint 可以获取方法签名（MethodSignature），然后通过反射拿到 Method 对象，进而读取注解。@Around("@annotation(loggable)") public Object log(ProceedingJoinPoint pjp, Loggable loggable) throws Throwable { // 获得方法签名 MethodSignature signature = (MethodSignature) pjp.getSignature(); Method method = signature.getMethod(); // 反射 Method 对象 // 可以读取更多注解 AnotherAnnotation anno = method.getAnnotation(AnotherAnnotation.class); // 执行原方法 return pjp.proceed(); }为什么要 MethodSignature？ 因为 pjp.getSignature() 返回的是通用的 Signature 接口，只有强转成 MethodSignature 才能获取反射 Method 以及参数名、返回类型等详细信息。3.5 JoinPoint 信息树（速查）JoinPoint (ProceedingJoinPoint 继承它) ├─ getTarget() → 目标对象（被代理的原始 Bean） ├─ getThis() → 当前代理对象 ├─ getArgs() → 方法参数值数组 ├─ getSignature() → Signature (实际是 MethodSignature) │ ├─ getName() → 方法名 │ ├─ getDeclaringTypeName() → 声明该方法的类全名 │ └─ (MethodSignature 特有) │ ├─ getMethod() → 反射 Method ★ │ │ └─ getAnnotation(..) → 获取注解 │ ├─ getParameterNames() → 参数名数组（需编译配置） │ ├─ getReturnType() → 返回类型 │ └─ getParameterTypes() → 参数类型数组 └─ getStaticPart() → 同 getSignature()四、三剑合璧实战案例4.1 案例 1：@Log 注解实现方法日志记录需求：在方法执行时自动打印入参、出参和耗时。步骤：自定义 @Log 注解编写切面 LogAspect，使用 @Around + @annotation在切面中通过 MethodSignature 获取方法名，通过 getArgs() 获取参数值// 注解 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Log { } // 切面 @Aspect @Component public class LogAspect { @Around("@annotation(com.example.Log)") public Object around(ProceedingJoinPoint pjp) throws Throwable { MethodSignature sig = (MethodSignature) pjp.getSignature(); String method = sig.getDeclaringTypeName() + "." + sig.getName(); Object[] args = pjp.getArgs(); System.out.println(">>> 调用 " + method + "，参数：" + Arrays.toString(args)); long start = System.currentTimeMillis(); Object result = pjp.proceed(); long time = System.currentTimeMillis() - start; System.out.println("<<< 返回：" + result + " 耗时：" + time + "ms"); return result; } } // 业务使用 @Service public class UserService { @Log public String getUser(Long id) { return "User#" + id; } }4.2 案例 2：@RequirePermission 权限控制需求：标记方法需要某权限，没有则拒绝访问。实现思路：定义注解 @RequirePermission("user:delete")在拦截器中把用户权限存入 ThreadLocal切面中读取注解所需的权限值，与用户权限对比，不满足抛异常// 注解 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface RequirePermission { String value(); } // 权限上下文 public class UserContext { private static final ThreadLocal<List<String>> PERMISSIONS = new ThreadLocal<>(); public static void set(List<String> p) { PERMISSIONS.set(p); } public static List<String> get() { return PERMISSIONS.get() != null ? PERMISSIONS.get() : Collections.emptyList(); } public static void clear() { PERMISSIONS.remove(); } } // 切面 @Aspect @Component public class PermissionAspect { @Around("@annotation(RequirePermission)") public Object check(ProceedingJoinPoint pjp) throws Throwable { MethodSignature sig = (MethodSignature) pjp.getSignature(); Method method = sig.getMethod(); RequirePermission permission = method.getAnnotation(RequirePermission.class); String required = permission.value(); if (!UserContext.get().contains(required)) { throw new RuntimeException("无权限：" + required); } return pjp.proceed(); } } // 拦截器（简化） public class AuthInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, ...) { String user = request.getHeader("X-User"); List<String> permissions = loadPermissions(user); // 从数据库查 UserContext.set(permissions); return true; } @Override public void afterCompletion(...) { UserContext.clear(); } }五、总结与关系图谱三者关系：注解 是标记，贴在代码上描述元数据。反射 是读取这些标记和操作代码结构（类、方法）的能力。AOP 是利用反射机制，在运行时动态地将横切逻辑织入到带有特定注解（或符合表达式）的方法周围。常见协作模式：自定义一个注解（如 @Log）在 AOP 切面中，通过切入点表达式 @annotation(Log) 拦截所有标注了该注解的方法在切面通知方法里，利用 JoinPoint/ProceedingJoinPoint 获取方法签名（MethodSignature）通过 MethodSignature.getMethod() 拿到反射 Method 对象，再用 getAnnotation() 读取注解的具体值，实现差异化逻辑学习路径建议：先熟练 反射（Class、Method、注解读取）理解 代理模式（JDK 动态代理、CGLIB）掌握 Spring AOP 的基本用法（切入点表达式、通知类型）最后将三者结合，实现自定义注解驱动切面当你遇到如下代码时，大脑能快速解析：MethodSignature sig = (MethodSignature) joinPoint.getSignature(); Method method = sig.getMethod(); MyAnnotation ann = method.getAnnotation(MyAnnotation.class);它正在用 AOP 的连接点 获取 反射的 Method，然后 读取自定义注解，从而执行相应逻辑——这。
Spring JDBC + JdbcTemplate 学习笔记（方法驱动格式）本笔记聚焦于 JdbcTemplate 中常用的数据库操作方法。每个方法均按以下结构介绍：方法：方法签名/名称作用：该方法解决什么问题参数：主要参数的含义和类型方法案例：代码示例（可直接运行）实例说明：对案例的详细解释，包括注意事项前置环境：Spring 6 + HikariCP + HSQLDB，配置了 DataSource 和 JdbcTemplate Bean。第一章：准备工作与环境配置1.1 Maven 依赖<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>6.0.0</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>6.0.0</version> </dependency> <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> <version>5.0.1</version> </dependency> <dependency> <groupId>org.hsqldb</groupId> <artifactId>hsqldb</artifactId> <version>2.7.1</version> </dependency>1.2 Spring 配置类（AppConfig）@Configuration @ComponentScan @PropertySource("jdbc.properties") public class AppConfig { @Value("${jdbc.url}") String jdbcUrl; @Value("${jdbc.username}") String jdbcUsername; @Value("${jdbc.password}") String jdbcPassword; @Bean DataSource dataSource() { HikariConfig config = new HikariConfig(); config.setJdbcUrl(jdbcUrl); config.setUsername(jdbcUsername); config.setPassword(jdbcPassword); return new HikariDataSource(config); } @Bean JdbcTemplate jdbcTemplate(DataSource dataSource) { return new JdbcTemplate(dataSource); } }1.3 数据库初始化（自动建表）@Component public class DatabaseInitializer { @Autowired JdbcTemplate jdbcTemplate; @PostConstruct void init() { jdbcTemplate.update("CREATE TABLE IF NOT EXISTS users (" + "id BIGINT IDENTITY PRIMARY KEY, " + "email VARCHAR(100) NOT NULL UNIQUE, " + "password VARCHAR(100), " + "name VARCHAR(100))"); } }第二章：核心方法详解（按格式组织）方法一：queryForObject() – 查询单行记录并映射为对象作用：执行查询 SQL，期望返回一条记录，并将结果集的第一行通过 RowMapper 转换成 Java 对象。若查询结果为空或大于一行则抛出异常。参数：String sql – SQL 语句（可使用 ? 占位符）RowMapper<T> rowMapper – 将 ResultSet 当前行映射为 T 对象的函数式接口Object... args – 可变参数，按顺序替换 SQL 中的 ?方法案例：根据 email 查询用户public User getUserByEmail(String email) { String sql = "SELECT id, email, password, name FROM users WHERE email = ?"; return jdbcTemplate.queryForObject(sql, (rs, rowNum) -> new User( rs.getLong("id"), rs.getString("email"), rs.getString("password"), rs.getString("name") ), email ); }实例说明：RowMapper 的 lambda 表达式接收 ResultSet 和当前行号（通常忽略行号），手动构建 User 对象。如果数据库中不存在该 email，会抛出 EmptyResultDataAccessException，需要捕获处理或改为返回 null。也适用于聚合查询，例如统计总数：public long countUsers() { String sql = "SELECT COUNT(*) FROM users"; return jdbcTemplate.queryForObject(sql, (rs, rowNum) -> rs.getLong(1)); }方法二：query() – 查询多行记录，返回 List作用：执行查询 SQL，将结果集中的每一行都通过 RowMapper 转换为对象，最后返回 List<T>。结果集为空时返回空列表（不抛异常）。参数：String sql – SQL 语句RowMapper<T> rowMapper – 行映射器Object... args – 参数列表方法案例：分页查询用户public List<User> getUsersByPage(int pageIndex, int pageSize) { String sql = "SELECT * FROM users LIMIT ? OFFSET ?"; int offset = (pageIndex - 1) * pageSize; return jdbcTemplate.query(sql, (rs, rowNum) -> new User( rs.getLong("id"), rs.getString("email"), rs.getString("password"), rs.getString("name") ), pageSize, offset ); }实例说明：使用 BeanPropertyRowMapper 可自动映射（要求列名与属性名一致或下划线转驼峰）：public List<User> getAllUsers() { return jdbcTemplate.query("SELECT * FROM users", new BeanPropertyRowMapper<>(User.class)); }BeanPropertyRowMapper 适合字段较多的简单映射；复杂映射仍需手动 RowMapper。方法三：update() – 执行插入、更新、删除作用：执行 DML 语句（INSERT/UPDATE/DELETE），返回受影响的行数。参数：String sql – DML 语句Object... args – 参数列表返回值：int – 受影响的行数方法案例 1：更新用户名称public int updateUserName(Long id, String newName) { String sql = "UPDATE users SET name = ? WHERE id = ?"; return jdbcTemplate.update(sql, newName, id); }实例说明：调用方可根据返回值判断是否更新成功（例如返回值 == 0 表示用户不存在）。方法案例 2：删除用户public boolean deleteUser(Long id) { String sql = "DELETE FROM users WHERE id = ?"; int rows = jdbcTemplate.update(sql, id); return rows > 0; }方法案例 3：插入用户（不返回主键）public void addUserWithoutKey(String name, String email) { String sql = "INSERT INTO users (name, email) VALUES (?, ?)"; jdbcTemplate.update(sql, name, email); }方法四：update() + KeyHolder – 插入并返回自增主键作用：执行插入语句后，获取数据库自动生成的主键（如 IDENTITY 或 AUTO_INCREMENT）。参数：PreparedStatementCreator psc – 用于创建 PreparedStatement，必须指定 Statement.RETURN_GENERATED_KEYSKeyHolder keyHolder – 用于接收生成的主键方法案例：public User addUserAndGetId(String name, String email, String password) { String sql = "INSERT INTO users (name, email, password) VALUES (?, ?, ?)"; KeyHolder keyHolder = new GeneratedKeyHolder(); jdbcTemplate.update(connection -> { PreparedStatement ps = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); ps.setString(1, name); ps.setString(2, email); ps.setString(3, password); return ps; }, keyHolder); Long newId = keyHolder.getKey().longValue(); return new User(newId, email, password, name); }实例说明：PreparedStatementCreator 是一个函数式接口，需手动设置参数并返回带 RETURN_GENERATED_KEYS 的 PreparedStatement。执行后通过 keyHolder.getKey() 获取主键，通常为 Number 类型，需转换为 Long 或 Integer。适用于大多数关系型数据库（MySQL、HSQLDB、PostgreSQL、SQL Server 等）。方法五：batchUpdate() – 批量操作作用：一次性执行多条相同结构的 SQL（通常是批量插入或批量更新），性能远高于循环单条执行。参数：String sql – 带占位符的 SQL 模板List<Object[]> batchArgs – 每个元素是一个 Object[]，代表一次执行对应的参数返回值：int[] – 数组长度等于批处理次数，每个元素为对应操作影响的行数方法案例：批量插入多个用户public int[] batchAddUsers(List<User> users) { String sql = "INSERT INTO users (name, email, password) VALUES (?, ?, ?)"; List<Object[]> batch = new ArrayList<>(); for (User u : users) { batch.add(new Object[]{u.getName(), u.getEmail(), u.getPassword()}); } return jdbcTemplate.batchUpdate(sql, batch); }实例说明：批量操作会减少 JDBC 与数据库之间的网络往返次数，显著提升吞吐量。注意：某些数据库（如 HSQLDB）对单次批处理的大小有限制，可分批提交。方法六：execute() – 执行任意 SQL（DDL 或存储过程）作用：执行 DDL（如 CREATE TABLE）或任何不希望返回结果集的 SQL，也可以用于执行存储过程。参数：String sql – 要执行的 SQL 字符串返回值：无（或者可重载为 execute(Callback) 形式返回自定义结果）方法案例：创建日志表public void createLogTable() { jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS sys_log (" + "id INT PRIMARY KEY, " + "message VARCHAR(255), " + "create_time TIMESTAMP)"); }实例说明：execute(String sql) 不关心返回结果，适合做数据库初始化、清空表、重建索引等管理操作。方法七：execute(ConnectionCallback) – 最底层扩展作用：获取原生 JDBC Connection 对象，执行任意复杂数据库操作（如设置超时、调用存储过程、使用原生 API 等）。参数：ConnectionCallback<T> action – 回调接口，接收 Connection 并返回 T返回值：由回调决定的 T方法案例：使用原生 PreparedStatement 和手动控制事务public User getUserWithNativeConnection(Long id) { return jdbcTemplate.execute((Connection conn) -> { try (PreparedStatement ps = conn.prepareStatement("SELECT * FROM users WHERE id = ?")) { ps.setLong(1, id); try (ResultSet rs = ps.executeQuery()) { if (rs.next()) { return new User( rs.getLong("id"), rs.getString("email"), rs.getString("password"), rs.getString("name") ); } return null; } } }); }实例说明：JdbcTemplate 会自动释放 Connection，但回调内创建的 PreparedStatement 和 ResultSet 必须由开发者手动释放（建议使用 try-with-resources）。一般情况下优先使用更高级的 query()、update() 等方法，只有非常规需求时才使用 ConnectionCallback。方法八：execute(PreparedStatementCallback) – 灵活处理 PreparedStatement作用：由 JdbcTemplate 创建 PreparedStatement，回调中可自行执行查询、更新并处理结果集。参数：String sql – SQL 模板PreparedStatementCallback<T> action – 回调接收 PreparedStatement，返回 T方法案例：判断用户是否存在public boolean existsUserByName(String name) { return jdbcTemplate.execute("SELECT 1 FROM users WHERE name = ?", (PreparedStatement ps) -> { ps.setString(1, name); try (ResultSet rs = ps.executeQuery()) { return rs.next(); // 有记录返回 true } }); }实例说明：此方法比 ConnectionCallback 更高层，无需创建 PreparedStatement，但依然需要手动执行查询和处理 ResultSet。第三章：RowMapper 详解（两个常用实现）3.1 手动 RowMapper（Lambda）作用：将 ResultSet 当前行的数据提取并组装成业务对象。格式：(ResultSet rs, int rowNum) -> { ... }案例：已在 queryForObject 中展示。3.2 BeanPropertyRowMapper作用：自动将结果集的列名（支持下划线转驼峰）映射到 JavaBean 的同名属性（需有 setter）。参数：BeanPropertyRowMapper.newInstance(Class<T>) 或构造函数。案例：List<User> users = jdbcTemplate.query("SELECT id, email, name FROM users", BeanPropertyRowMapper.newInstance(User.class));实例说明：要求数据库列名 email 对应 Java 属性 email；若列名为 user_name，则对应 userName。非常便捷，适合大多数查询场景。第四章：声明式事务管理核心注解：@Transactional作用：将方法（或类中的所有方法）标记为事务性方法。Spring 会在方法执行前开启事务，执行成功则提交，抛出未捕获异常则回滚。参数（常用）：propagation – 传播行为（默认 REQUIRED）isolation – 隔离级别（默认数据库默认级别）rollbackFor – 指定哪些异常触发回滚（默认 RuntimeException 和 Error）方法案例：银行转账（两个更新操作必须在同一事务）@Service public class AccountService { @Autowired JdbcTemplate jdbcTemplate; @Transactional public void transfer(Long fromId, Long toId, BigDecimal amount) { jdbcTemplate.update("UPDATE account SET balance = balance - ? WHERE id = ?", amount, fromId); // 模拟异常：如果转账金额过大，回滚两个更新 if (amount.compareTo(BigDecimal.valueOf(10000)) > 0) { throw new RuntimeException("转账金额超过限额"); } jdbcTemplate.update("UPDATE account SET balance = balance + ? WHERE id = ?", amount, toId); } }实例说明：需要先在配置类上添加 @EnableTransactionManagement 并注册 DataSourceTransactionManager。默认情况下，只有 RuntimeException 会触发回滚；若希望检查型异常也回滚，需设置 @Transactional(rollbackFor = Exception.class)。事务边界默认基于 AOP 代理，内部方法调用（this.method()）不会触发事务。第五章：实战示例 – RESTful API 完整流程5.1 User 实体public class User { private Long id; private String name; private String email; private String password; // getter/setter/构造方法 省略 }5.2 UserService（使用上述方法）@Service public class UserService { @Autowired JdbcTemplate jdbcTemplate; public User addUser(String name, String email, String pwd) { KeyHolder kh = new GeneratedKeyHolder(); jdbcTemplate.update(conn -> { PreparedStatement ps = conn.prepareStatement( "INSERT INTO users (name, email, password) VALUES (?,?,?)", Statement.RETURN_GENERATED_KEYS); ps.setString(1, name); ps.setString(2, email); ps.setString(3, pwd); return ps; }, kh); return new User(kh.getKey().longValue(), email, pwd, name); } public User getUser(Long id) { try { return jdbcTemplate.queryForObject("SELECT * FROM users WHERE id = ?", BeanPropertyRowMapper.newInstance(User.class), id); } catch (EmptyResultDataAccessException e) { return null; } } public List<User> listUsers() { return jdbcTemplate.query("SELECT * FROM users", BeanPropertyRowMapper.newInstance(User.class)); } public int updateUser(Long id, String newName) { return jdbcTemplate.update("UPDATE users SET name = ? WHERE id = ?", newName, id); } public int deleteUser(Long id) { return jdbcTemplate.update("DELETE FROM users WHERE id = ?", id); } }5.3 UserController@RestController @RequestMapping("/api/users") public class UserController { @Autowired UserService userService; @PostMapping public User add(@RequestParam String name, @RequestParam String email, @RequestParam String pwd) { return userService.addUser(name, email, pwd); } @GetMapping("/{id}") public User get(@PathVariable Long id) { return userService.getUser(id); } @GetMapping public List<User> list() { return userService.listUsers(); } @PutMapping("/{id}") public String update(@PathVariable Long id, @RequestParam String name) { int rows = userService.updateUser(id, name); return rows > 0 ? "success" : "user not found"; } @DeleteMapping("/{id}") public String delete(@PathVariable Long id) { int rows = userService.deleteUser(id); return rows > 0 ? "deleted" : "not exist"; } }5.4 测试命令# 新增 curl -X POST "http://localhost:8080/api/users?name=张三&email=zs@test.com&pwd=123" # 查询单个 curl "http://localhost:8080/api/users/1" # 查询全部 curl "http://localhost:8080/api/users" # 更新 curl -X PUT "http://localhost:8080/api/users/1?name=张三丰" # 删除 curl -X DELETE "http://localhost:8080/api/users/1"第六章：常见问题与注意事项queryForObject 空结果抛异常：需捕获 EmptyResultDataAccessException 或改用 query 方法加判断。BeanPropertyRowMapper 要求属性有 setter：否则映射会忽略该列。批量插入时数据库限制：例如 HSQLDB 默认批量最大 100 条，可分批调用 batchUpdate。事务失效场景：同一个类内非 @Transactional 方法调用 @Transactional 方法（无代理）方法被 private 修饰异常被 try-catch 吞掉且没有重新抛出使用 KeyHolder 时：必须通过 Statement.RETURN_GENERATED_KEYS 创建 PreparedStatement。附录：JdbcTemplate 方法速查表方法名作用适用场景queryForObject单行，返回对象根据主键查询、聚合函数query多行，返回 List列表查询、分页update单条 DML插入/更新/删除update + KeyHolder插入并返回主键获取自增 IDbatchUpdate批量 DML批量导入数据execute(String)DDL/存储过程建表、清空表execute(ConnectionCallback)原生 JDBC 操作特殊需求、存储过程---。
注意：Java 8 环境下，javax.annotation（@PostConstruct、@PreDestroy、@Resource）是 JDK 自带的，无需额外添加依赖。Spring Boot 2.7 使用 Spring Framework 5.x，完美支持 JSR-250 规范。一、Bean 的作用域（Scope）Spring 中的 Bean 默认是 单例（Singleton），即整个容器内只有一个实例。也可以通过 @Scope 指定为 原型（Prototype），每次获取都创建新实例。1.1 Singleton（单例）—— 默认创建时机：容器启动时（除非设置为 @Lazy）。销毁时机：容器正常关闭时，会调用 @PreDestroy 方法。获取结果：每次注入或 getBean() 返回同一个对象。@Component // 默认 singleton public class MailService { // ... }1.2 Prototype（原型）行为：每次获取（@Autowired 或 getBean）都会创建一个全新的实例。生命周期差异：容器负责创建 → 依赖注入 → 调用 @PostConstruct。容器不负责销毁：即使你在原型 Bean 中写了 @PreDestroy，Spring 不会自动调用它。需要你自己写代码清理（例如手动调用销毁方法）。声明方式：@Component @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) // 或 @Scope("prototype") public class MailSession { @PostConstruct public void init() { System.out.println("原型 Bean 初始化，每次都会调用"); } @PreDestroy public void destroy() { System.out.println("原型 Bean 销毁 — 不会被 Spring 自动调用"); } }最佳实践：如果没有特殊需求，优先使用 singleton。prototype 适合那些有状态且不宜共享的对象，但要记得手动管理资源释放。二、注入 List —— 自动收集同一类型的所有 Bean当你需要在一个地方使用某个接口的 所有实现类 时，可以直接注入 List<T>，Spring 会自动把该接口的所有 Bean 放进列表。示例：多个 Validator 实现public interface Validator { void validate(String email, String password, String name); } @Component // 自动被收集 public class EmailValidator implements Validator { @Override public void validate(String email, String password, String name) { // email 校验逻辑 } } @Component public class PasswordValidator implements Validator { @Override public void validate(String email, String password, String name) { // password 校验逻辑 } } @Component public class NameValidator implements Validator { @Override public void validate(String email, String password, String name) { // name 校验逻辑 } }注入 List@Component public class Validators { @Autowired private List<Validator> validators; // Spring 自动注入所有 Validator 实现 public void validate(String email, String password, String name) { for (Validator v : validators) { v.validate(email, password, name); } } }控制顺序 —— @Order默认顺序不确定，可以用 @Order 注解指定（数字越小优先级越高）。@Component @Order(1) public class EmailValidator implements Validator { ... } @Component @Order(2) public class PasswordValidator implements Validator { ... } @Component @Order(3) public class NameValidator implements Validator { ... }注入的 List<Validator> 会按照 @Order 的值升序排列。三、可选注入 —— @Autowired(required = false)默认情况下，如果某个依赖的 Bean 不存在，Spring 会抛出 NoSuchBeanDefinitionException。如果你允许该依赖为 null，可以设置 required = false。@Component public class MailService { @Autowired(required = false) private ZoneId zoneId = ZoneId.systemDefault(); // 找不到 ZoneId 时使用默认值 }典型场景：某些配置是可选的（如时区、外部服务），若有定义则使用，否则退回到默认值。四、创建第三方 Bean —— @Bean 方法对于不在我们包内的类（例如 JDK 的 ZoneId、第三方库的 DataSource），无法用 @Component 扫描。此时需要在 @Configuration 类中编写一个 工厂方法，并标记 @Bean。@Configuration @ComponentScan public class AppConfig { @Bean public ZoneId zoneId() { return ZoneId.of("Z"); } @Bean("customRestTemplate") // 指定 Bean 名称 public RestTemplate myRestTemplate() { return new RestTemplate(); } }Spring 保证该方法 只被调用一次（默认单例）。方法名默认作为 Bean 的名称，也可用 @Bean("name") 显式命名。可以依赖其他 Bean：方法参数会自动注入。@Bean public DataSource dataSource() { return HikariDataSource(...); } @Bean public JdbcTemplate jdbcTemplate(DataSource dataSource) { // 自动注入上面创建的 DataSource return new JdbcTemplate(dataSource); }本质上，@Bean 方法就是一种 工厂方法，比实现 FactoryBean 接口更简洁。五、Bean 的初始化和销毁 —— @PostConstruct 和 @PreDestroy5.1 为什么需要初始化：在依赖注入完成后，执行一些启动逻辑（打开连接、启动后台线程、校验配置）。销毁：在容器关闭前，释放资源（关闭连接池、停止线程、清理临时文件）。5.2 使用方式（JSR-250，Java 8 原生支持）@Component public class MailService { @Autowired(required = false) private ZoneId zoneId = ZoneId.systemDefault(); @PostConstruct public void init() { System.out.println("初始化：依赖已注入，zoneId = " + zoneId); } @PreDestroy public void shutdown() { System.out.println("销毁前：释放资源（如关闭连接池）"); } }5.3 执行顺序初始化流程：调用构造方法创建实例依赖注入（@Autowired、@Resource 等）调用 @PostConstruct 方法（如果实现了 InitializingBean）调用 afterPropertiesSet()（如果配置了 init-method）调用自定义方法销毁流程（容器正常关闭时）：调用 @PreDestroy 方法（如果实现了 DisposableBean）调用 destroy()（如果配置了 destroy-method）调用自定义方法5.4 重要注意事项方法必须 无参数，返回值任意（通常 void）。方法名任意，不要求 init/destroy。prototype 作用域的 Bean，Spring 只调用 @PostConstruct，不会自动调用 @PreDestroy。如果原型 Bean 需要清理，你必须自己设计清理逻辑（例如提供 cleanup() 方法并手动调用）。5.5 “全自动”的条件在 Spring Boot 2.7（Java 8）中，只要满足以下条件，@PostConstruct 和 @PreDestroy 就是 全自动 的：Bean 是 singleton 作用域（默认）。Spring Boot 应用正常关闭（按 Ctrl+C、收到 SIGTERM、程序正常结束）。Spring Boot 会自动注册 JVM 关闭钩子，触发容器关闭，从而调用 @PreDestroy。不要使用 kill -9 强行杀死进程（那样 JVM 直接退出，不会执行关闭钩子）。@SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); // 按 Ctrl+C 时，会看到 @PreDestroy 的输出 } }六、处理多个同类型 Bean —— 别名、限定符、主次6.1 问题描述在 @Configuration 中定义多个同类型的 @Bean：@Configuration public class AppConfig { @Bean ZoneId createZoneOfZ() { return ZoneId.of("Z"); } @Bean ZoneId createZoneOfUTC8() { return ZoneId.of("UTC+08:00"); } }直接注入 ZoneId 会报错：NoUniqueBeanDefinitionException。6.2 解决方案(1) 为每个 Bean 起一个名字（别名）@Bean("z") ZoneId createZoneOfZ() { ... } @Bean @Qualifier("utc8") ZoneId createZoneOfUTC8() { ... }(2) 注入时用 @Qualifier 指定名称@Component public class MailService { @Autowired @Qualifier("z") // 精确指定名称为 "z" 的那个 ZoneId private ZoneId zoneId; }或者使用 JSR-250 的 @Resource(name = "z")：@Component public class MailService { @Resource(name = "z") private ZoneId zoneId; }(3) 使用 @Primary 指定主要 Bean当没有使用 @Qualifier 时，Spring 会注入标记 @Primary 的那个 Bean。@Bean @Primary // 主要候选 @Qualifier("z") ZoneId createZoneOfZ() { ... } @Bean @Qualifier("utc8") ZoneId createZoneOfUTC8() { ... }注入时：@Component public class MailService { @Autowired // 无 @Qualifier，默认注入 @Primary 的那个（即 "z"） private ZoneId zoneId; }6.3 经典应用：多数据源@Configuration public class DataSourceConfig { @Bean @Primary DataSource masterDataSource() { ... } @Bean @Qualifier("slave") DataSource slaveDataSource() { ... } }默认注入主数据源（如 DAO 层无需特殊指定）。需要从库时：@Qualifier("slave") 精确注入。七、FactoryBean —— 工厂模式创建 Bean（了解即可）7.1 是什么FactoryBean 是 Spring 提供的工厂接口，允许你自定义 Bean 的创建逻辑。Spring 会先创建 FactoryBean 实例，再调用其 getObject() 方法返回真正的 Bean。@Component public class ZoneIdFactoryBean implements FactoryBean<ZoneId> { private String zone = "Z"; @Override public ZoneId getObject() throws Exception { return ZoneId.of(zone); } @Override public Class<?> getObjectType() { return ZoneId.class; } }注入/获取时拿到的是 getObject() 返回的 ZoneId。如果需要获取 FactoryBean 本身，可以在名称前加 &：context.getBean("&zoneIdFactoryBean")。7.2 现状在 Spring Boot 2.7 中，@Bean 方法已经足够简洁强大，完全可以替代 FactoryBean 的大部分场景。除非你要编写非常复杂的创建逻辑，或需要与旧框架集成（如 MyBatis 的 MapperFactoryBean），否则推荐直接使用 @Bean 方法。八、综合对比与最佳实践功能推荐方案关键点普通 Bean@Component默认 singleton原型 Bean@Scope("prototype")每次新建，容器不负责 @PreDestroy收集所有实现@Autowired List<Interface>配合 @Order 控制顺序可选依赖@Autowired(required=false)避免异常，可设默认值第三方 Bean@Bean 方法在 @Configuration 中定义初始化逻辑@PostConstruct依赖注入后自动执行销毁逻辑@PreDestroy容器关闭时自动执行（singleton 有效）同类型多个 Bean@Qualifier + 命名精确注入；@Primary 设置默认复杂工厂@Bean 方法一般不用 FactoryBean九、Java 8 + Spring Boot 2.7 特别提醒注解包：@PostConstruct、@PreDestroy、@Resource 使用 javax.annotation，无需添加任何额外依赖。Spring Boot 自动关闭：Spring Boot 应用默认注册 JVM 关闭钩子，只要你正常停止应用（Ctrl+C、kill 不带 -9、System.exit()），@PreDestroy 就会被调用。prototype 的陷阱：如果你的 Bean 是 prototype 且需要释放资源，请考虑改用 singleton，或自行设计清理方法（如实现 DisposableBean 并手动调用）。@Bean 方法的默认名称：方法名即 Bean 名称，注意避免重名（同一类型不同方法名）。
一、常用工具与集合操作1.1 Arrays.asList()作用：将数组转换为 List。注意：返回的 List 是固定大小的，不支持 add/remove 操作。List<String> list = Arrays.asList("a", "b", "c");1.2 CollUtil.toMap()（Hutool 工具）把一个 List 转换成 Map。参数1：要转换的列表。参数2：key 的生成方式（如 Stock::getSkuId）。参数3：value 的生成方式，Function.identity() 表示用对象本身作为 value。Map<Long, Stock> map = CollUtil.toMap(stocks, Stock::getSkuId, Function.identity());1.3 IdentityHashMap比较冷门，判断 key 是否相同依据 对象的引用地址（==），而不是 equals() / hashCode()。二、数据查询策略2.1 联表查询 vs 分开查 + Map 组装方式适用场景联表查询数据量不大、关联简单、不需要对查出的用户数据做额外处理分开查 + Map 组装数据分布在不同的服务器、微服务架构，或者需要灵活组装三、Java 集合框架3.1 集合体系概览Collection 是除 Map 外所有集合类的根接口。java.util 包主要提供三种类型的集合：List、Set、Map。统一使用 Iterator 遍历集合。3.2 ListArrayList：基于动态数组，内存连续。LinkedList：基于双向链表，节点有前后指针。List 允许添加 null。使用 List.of() 创建不可变 List（不支持 null 元素）。List<Integer> list = List.of(1, 2, 5);Iterator 遍历：boolean hasNext() 判断是否有下一个元素。E next() 返回下一个元素。boolean contains(Object o) 判断是否包含某元素。int indexOf(Object o) 返回元素索引，不存在返回 -1。重要：使用 contains()、indexOf() 等方法时，元素类必须正确覆写 equals()。String、Integer 等标准库类已经正确实现了 equals()，所以可以直接使用。3.3 编写 equals() 方法理论原则：自反性、对称性、传递性、一致性。实践写法（直接套用）：先确定实例相等的逻辑（哪些字段相等就算相等）。用 instanceof 判断类型，类型匹配后再比较字段。使用 Objects.equals() 比较引用类型字段，避免空指针。public boolean equals(Object o) { if (o instanceof Person p) { return Objects.equals(this.name, p.name) && this.age == p.age; } return false; }3.4 MapMap 遍历是无序的。底层是哈希表，通过哈希函数将 key 转换成数组下标，直接定位，查找速度极快 O(1)。作为 key 的对象必须正确覆写 equals() 和 hashCode()，相等的 key 调用 equals 必须返回 true。3.5 Queue 队列实现先进先出（FIFO）的数据结构。常用方法：add() / offer()：添加元素到队尾。remove() / poll()：从队首获取并删除元素。element() / peek()：从队首获取但不删除。避免将 null 添加到队列。四、编码与安全4.1 URL 编码使用 java.net.URLEncoder 对任意字符串进行 URL 编码。4.2 哈希算法目的：验证原始数据是否被篡改。MessageDigest 常见用途：计算文件校验和实现文件秒传存储用户密码防止数据重复生成 API 签名4.3 AES 加密最可靠的 AES 加密模式：AES-GCM。五、多线程与并发5.1 线程基础守护线程（Daemon）JVM 退出时不等待守护线程结束，直接强制终止。非守护线程（用户线程）结束时 JVM 才会退出。设置方法：在 start() 前调用 setDaemon(true)。核心业务线程不能设置为守护线程。典型用途：垃圾回收、日志监控、心跳检测。t.join()让当前线程等待 t 线程执行结束。生产环境禁用，容易造成阻塞，推荐用 Future 等异步方式。Thread.sleep(long millis)静态方法，哪个线程调用，哪个线程睡觉。线程中断线程结束即 run() 方法执行完毕。目标线程通过检查 isInterrupted() 标志判断自身是否已被中断。volatile 与内存可见性变量的值保存在主内存，线程访问时会拷贝到工作内存。修改后的值何时回写主内存不确定，因此会出现可见性问题。synchronized 不能修饰变量，要保证变量跨线程可见性，使用 volatile。注意：volatile 只能保证可见性和禁止指令重排，不保证原子性。5.2 同步机制synchronizedJava 语言层面语法，不需要手动释放锁（退出同步块自动释放）。修饰方法时，锁对象是 this（实例方法）或类对象（静态方法）。配合 wait() / notifyAll() 实现线程等待/唤醒（了解即可，实际多用 Lock 体系）。ReentrantLockJava 代码实现的锁，必须在 finally 中手动释放。标准用法：private final Lock lock = new ReentrantLock(); lock.lock(); try { // 同步代码 } finally { lock.unlock(); }配合 Condition 的 await() / signalAll() 实现等待/通知。ReadWriteLock提升读多写少场景的并发性能。规则：读-读 可共存写-写 互斥读-写 互斥（要么全是读，要么只有一个写）实现：private final ReadWriteLock rwlock = new ReentrantReadWriteLock(); private final Lock rlock = rwlock.readLock(); private final Lock wlock = rwlock.writeLock(); // 写操作 wlock.lock(); try { ... } finally { wlock.unlock(); } // 读操作 rlock.lock(); try { ... } finally { rlock.unlock(); }注意：读锁是悲观锁，频繁读可能导致写线程饥饿。StampedLock三种模式：写锁、悲观读锁、乐观读。乐观读 tryOptimisticRead() 完全不阻塞写线程，先获取版本号再读数据，读完后验证版本号有无变化，变化则重试或升级为悲观读。不可重入（一个线程持有写锁再尝试获取读锁会死锁）。Semaphore信号量，限制同时访问某个资源的线程数量。用法：final Semaphore semaphore = new Semaphore(3); // 最多3个许可 public String access() throws Exception { semaphore.acquire(); // 获取许可，可能阻塞 try { // 业务逻辑 } finally { semaphore.release(); // 释放许可 } }tryAcquire(long timeout, TimeUnit unit) 可指定等待时间。5.3 线程安全集合接口非线程安全线程安全替代ListArrayListCopyOnWriteArrayListMapHashMapConcurrentHashMapSetHashSet / TreeSetCopyOnWriteArraySetQueueArrayDeque / LinkedListArrayBlockingQueue / LinkedBlockingQueueDequeArrayDeque / LinkedListLinkedBlockingDeque5.4 线程池ExecutorService经典用法：ExecutorService executor = Executors.newFixedThreadPool(3); executor.submit(task1); ... executor.shutdown(); // 等待任务完成后关闭常用线程池及底层原理：FixedThreadPool：核心线程数 = 最大线程数 + 无界队列CachedThreadPool：核心线程数 0，最大线程数无限 + 同步移交队列SingleThreadExecutor：核心和最大线程数均为 1 + 无界队列ScheduledThreadPool：ScheduledThreadPoolExecutor，支持定时调度生产推荐：直接使用 ThreadPoolExecutor 定制参数，队列必须有界。public ThreadPoolExecutor( int corePoolSize, // CPU密集：n+1 IO密集：2n int maximumPoolSize, // ≥ corePoolSize long keepAliveTime, // 非核心线程空闲存活时间，常用30-60s TimeUnit unit, // 时间单位 BlockingQueue<Runnable> workQueue, // 有界队列，如 ArrayBlockingQueue ThreadFactory threadFactory, // 线程工厂 RejectedExecutionHandler handler // 拒绝策略 )拒绝策略策略行为AbortPolicy（默认）丢弃任务，抛 RejectedExecutionExceptionCallerRunsPolicy由提交任务的线程自行执行，可减缓提交速度（推荐）DiscardPolicy静默丢弃任务，不抛异常DiscardOldestPolicy丢弃队列最老的任务，重新提交当前任务推荐配置：CallerRunsPolicy + 监控告警。注意事项创建线程只有一种方式：new Thread().start()。生产环境只用线程池提交任务，禁止手动 new Thread()。for 循环中调用 t.start() 会立即返回，不会等待线程 run() 执行完。5.5 Future 与异步编程Callable 与 Runnable 的区别：Callable 有返回值，可抛出异常。提交 Callable 返回 Future<V>。get()：获取结果（可能阻塞）get(long timeout, TimeUnit unit)：限时获取cancel(boolean mayInterruptIfRunning)：取消任务isDone()：判断任务是否完成CompletableFuture实现 Future 和 CompletionStage，支持函数式异步编程。方法引用示例：Main::fetchPrice 等价于 () -> fetchPrice()，常用在 CompletableFuture.supplyAsync(Main::fetchPrice) 中。六、JDBC 简介各数据库厂商使用统一接口，Java 代码无需针对不同数据库分别开发。Maven 依赖：<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> <scope>runtime</scope> </dependency>建立连接：DriverManager.getConnection()（自动扫描 classpath 中的驱动）。重要提示：数据库自身就是并发专家，不要用 Java 层锁去干涉数据库操作，除非操作对象是内存数据。
Java 反射与动态代理 详细笔记一、反射的核心思想反射：在程序运行时，动态获取某个类的完整信息（类名、方法、字段、父类、接口等），并能动态创建对象、调用方法、访问字段。不需要在编译时知道具体类名，可以在运行时根据字符串类名加载类。常用于框架（Spring、MyBatis）、通用工具（IDE 自动补全）、动态代理等。二、Class 类：一切反射的入口2.1 每个类都有唯一的 Class 对象JVM 在第一次加载一个类时，会在内存中为该类创建一个 Class 实例（俗称“说明书”）。这个 Class 实例保存了该类的全部信息（类名、包、方法、字段、父类、接口等）。无论通过哪种方式获取，同一个类的 Class 实例是同一个对象（可用 == 比较）。2.2 获取 Class 实例的三种方法// 方法1：通过类名.class（最直接，编译时已知类） Class<String> cls1 = String.class; // 方法2：通过实例的 getClass() 方法 String s = "hello"; Class<?> cls2 = s.getClass(); // 实际返回 String.class // 方法3：通过 Class.forName(完整类名)（运行时根据字符串加载） Class<?> cls3 = Class.forName("java.lang.String");三者获得的是同一个 Class 对象：cls1 == cls2 == cls3三、通过反射访问字段（Field）3.1 获取字段的方法（返回值是 java.lang.reflect.Field）方法说明能否得到父类字段getField(String name)获取 public 字段（包括父类）✅ 能getDeclaredField(String name)获取当前类声明的任意字段（不限访问修饰符）❌ 不能，只看当前类getFields()获取所有 public 字段（包括父类）✅ 能getDeclaredFields()获取当前类声明的所有字段（不包括父类）❌ 不能3.2 访问私有字段通过 getDeclaredField 拿到私有 Field 对象后，调用 setAccessible(true) 打破封装。然后可以 get(obj) / set(obj, value) 读写该私有字段。class Person { private String name; } Class<Person> cls = Person.class; Field field = cls.getDeclaredField("name"); field.setAccessible(true); // 允许访问私有字段 Person p = new Person(); field.set(p, "Alice"); String name = (String) field.get(p); // "Alice"四、通过反射调用方法（Method）4.1 获取方法的方法（返回值是 java.lang.reflect.Method）方法说明包括父类getMethod(String name, Class<?>... parameterTypes)获取某个 public 方法✅ 包括getDeclaredMethod(...)获取当前类声明的某个方法（不限修饰符）❌ 不包括getMethods()获取所有 public 方法✅ 包括getDeclaredMethods()获取当前类声明的所有方法❌ 不包括4.2 调用方法拿到 Method 对象后，调用 invoke(Object obj, Object... args)。如果方法是 static，第一个参数传 null。// 示例：调用 String 的 length() 方法 String str = "hello"; Method m = String.class.getMethod("length"); int len = (int) m.invoke(str); // len = 5五、通过反射调用构造器（Constructor）5.1 获取构造器方法说明getConstructor(Class<?>... parameterTypes)获取某个 public 构造器getDeclaredConstructor(...)获取任意修饰符的构造器（包括 private）getConstructors() / getDeclaredConstructors()获取所有构造器5.2 创建实例通过 Constructor.newInstance(Object... initargs) 创建对象。比 Class.newInstance() 更灵活（可以传参数，可以调用非 public 构造器）。// 调用带参数的构造器 Constructor<String> cons = String.class.getConstructor(String.class); String s = cons.newInstance("abc"); // 相当于 new String("abc") // 调用私有构造器（单例模式等） Constructor<MyClass> cons2 = MyClass.class.getDeclaredConstructor(); cons2.setAccessible(true); MyClass obj = cons2.newInstance();注意：Class.newInstance() 已过时（只能调用无参 public 构造器），推荐使用 Constructor.newInstance()。六、获取继承关系方法作用示例Class.getSuperclass()返回父类的 Class 对象Integer.class.getSuperclass() → Number.classClass.getInterfaces()返回当前类直接实现的接口数组Integer.class.getInterfaces() → Comparable, Constable, ConstantDescClass.isAssignableFrom(Class<?> cls)判断当前类是否可接收 cls 类型的对象（即 cls 能否向上转型为当前类）Number.class.isAssignableFrom(Integer.class) → true6.1 关于接口的特殊说明对接口调用 getSuperclass() → 返回 null。获取接口的父接口必须用 getInterfaces()。接口的 isAssignableFrom 用法与类一致：Serializable.class.isAssignableFrom(Integer.class) → true。6.2 instanceof 与 isAssignableFrom 的区别instanceof 需要对象实例：obj instanceof NumberisAssignableFrom 需要Class 对象：Number.class.isAssignableFrom(Integer.class)七、动态代理（Dynamic Proxy）7.1 解决的问题不手动编写接口的实现类，就能在运行时动态创建实现了该接口的对象。典型应用：AOP（面向切面编程）、RPC 框架、日志/事务拦截等。7.2 限制条件只能代理接口（不能代理普通类）。如果需要代理普通类，可使用 CGLIB 或 ByteBuddy。7.3 核心 APIjava.lang.reflect.Proxyjava.lang.reflect.InvocationHandler（函数式接口）7.4 实现步骤定义接口（必须，告知代理对象有哪些方法）。实现 InvocationHandler：重写 invoke 方法，定义当接口方法被调用时实际执行的代码。调用 Proxy.newProxyInstance 创建代理实例。将代理实例转型为接口类型，然后正常调用方法。7.5 示例代码（完整）// 1. 接口 public interface Hello { void morning(String name); } // 2. 使用动态代理（不写 HelloWorld 类） import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class DynamicProxyDemo { public static void main(String[] args) { // 2.1 定义调用处理器 InvocationHandler handler = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.getName().equals("morning")) { String name = (String) args[0]; System.out.println("Good morning, " + name); } return null; // 方法返回 void，所以返回 null } }; // 2.2 创建代理对象 Hello hello = (Hello) Proxy.newProxyInstance( Hello.class.getClassLoader(), // 类加载器 new Class[]{Hello.class}, // 要实现的接口列表 handler // 调用处理器 ); // 2.3 调用方法（实际执行 handler.invoke） hello.morning("Bob"); // 输出：Good morning, Bob } }7.6 动态代理内部原理Proxy.newProxyInstance 会在运行时动态生成一个新的类（例如 $Proxy0）。这个类实现了指定的接口（Hello），并且每个方法的实现都是：public void morning(String name) { super.h.invoke(this, method_morning, new Object[]{name}); }super.h 就是你传入的 InvocationHandler。然后通过 ClassLoader 加载这个类，并实例化返回。换句话说：动态代理 = JVM 自动帮你写了一个实现类，并用反射调用你的 handler。7.7 动态代理与反射的关系动态代理生成的新类（$Proxy0）本身也是一个类，JVM 会为它创建 Class 对象。你可以对这个 Class 对象进行任何反射操作。InvocationHandler.invoke 方法中收到的 Method 参数就是通过反射得到的。所以动态代理是反射机制的一个典型应用，而不是替代品。八、总结：什么时候用什么技术？场景推荐技术已知类名，需要调用它的方法/访问字段直接调用，无需反射类名字符串在运行时才确定，需要创建实例Class.forName() + Constructor.newInstance()需要遍历一个对象的所有字段/方法（例如写序列化工具）getDeclaredFields() / getDeclaredMethods()不写实现类，仅通过接口定义行为动态代理需要在方法调用前后统一添加逻辑（日志、事务）动态代理 + InvocationHandler最后记住一句话：反射让你在运行时“看见”并“操作”一个类；动态代理让你在运行时“凭空造出”一个实现了接口的类，并把所有方法调用委托给你写的处理器。
AI Native 实战开发流程：以“管理员后台”为例（通用模板）本流程基于《AI Native 开发治理笔记》的治理理念，以 v3 管理后台 的一个典型功能“退款审核”为案例，展示如何用 AI 工程化方法交付一个安全、合规、可积累的生产级功能。该流程可复用到任何管理员后台的 CRUD、审批、报表、导出等场景。一、总体开发阶段概览阶段核心动作参与角色产出物（存入对应目录）1. 知识准备补充原始资料与业务规则人docs/、business/2. 任务规划编写 task 与选择 workflow人ai/tasks/current/、ai/workflows/3. AI 分析对齐让 AI 只分析，不写代码，对齐方案AI + 人分析报告（对话产物）4. AI 开发实现AI 按约束编码AI代码变更（apps/）5. 质量审查AI 自检 + 人复核AI + 人ai/reviews/6. 知识沉淀提炼通用经验与开发日志AI + 人ai/engineering-knowledge/、ai/development-logs/7. 交付上线最终验证与合并人可运行代码二、前置条件：工程骨架已搭建假设团队已经按治理笔记建立了以下目录和基础文件：v3-system/ ├── apps/ # 现有管理后台代码 ├── docs/ # 已有数据库 DDL、API 文档 ├── business/ # 已有 order/wallet/refund 等业务规则 ├── ai/ │ ├── tasks/current/ │ ├── workflows/ │ ├── reviews/ │ ├── engineering-knowledge/ │ └── development-logs/ ├── AGENTS.md # 已编写全局 AI 规则 └── CLAUDE.md如果还没有，必须先按笔记完成骨架搭建，否则后续流程会因为缺少约束而失控。三、实战步骤详解（以退款审核为例）阶段 1：知识准备——让 AI 有据可依目标：确保 AI 看到的“真相”是完整且准确的。人需要做的事情：检查 docs/是否存在 refund_order 表的 DDL？字段、索引、注释是否完整？是否有关联的 wallet、wallet_transaction 表结构？支付回调相关的第三方文档是否可访问？如果不全，立即用 mysqldump --no-data 导出最新结构放入 docs/db/。编写/更新 business/refund/rules.md 按照笔记模板，补充退款专属规则，例如：# 退款规则 1. 仅支付成功订单可退款 2. 退款必须审核（状态机：PENDING_AUDIT → APPROVED / REJECTED） 3. 退款执行时原子增加用户余额（UPDATE wallet SET balance = balance + ? WHERE user_id = ?） 4. 幂等：退款单 APPROVED 后重复请求直接返回成功 5. 记录 wallet_transaction 类型为 REFUND检查 business/wallet/invariants.md 确认余额不变量（balance >= 0）等规则已存在。产出物：更新后的 docs/ 和 business/，AI 的“知识底座”建立完毕。阶段 2：任务规划——画好 AI 的“活动圈”目标：精确控制 AI 本次能做什么、不能做什么。人需要做的事情：创建任务文件 ai/tasks/current/TASK-REFUND-APPROVE.md 按模板填写（关键字段见笔记第七章），示例要点：Goal：开发退款审核通过接口Allowed Modules：RefundController、RefundService、RefundDao，表 refund_order, wallet, wallet_transactionForbidden Modules：auth, distributor, goods，以及任何前端代码Technical Requirements：JdbcTemplate、@Transactional、Redis 锁、幂等Deliverables：修改的文件、SQL 变更、风险点选择或编写 workflow 如果 ai/workflows/add-admin-api.md 已存在，则沿用；否则参考笔记第八章编写一个。workflow 定义了标准开发步骤：分析 → 方案 → 编码 → 自检。产出物：TASK-REFUND-APPROVE.md 和确认使用的 workflow。阶段 3：AI 分析对齐——让 AI 先“说出来”再动手目标：AI 在不写代码的前提下，给出完整技术方案，由人确认，避免方向错误。操作步骤：新建 AI 会话，使用分析 Prompt（复制以下内容）请按顺序读取以下文件： 1. AGENTS.md 2. business/refund/rules.md 3. business/wallet/invariants.md 4. ai/workflows/add-admin-api.md 5. ai/tasks/current/TASK-REFUND-APPROVE.md 目标：理解系统约束和本次任务边界。 现在，仅进行分析设计，**禁止编写任何实现代码**。 请输出： 1. API 设计草案（POST /api/admin/refund/approve，请求体 refundOrderId，返回体通用Result） 2. SQL 设计草案（查询退款单、更新状态、原子更新钱包余额、插入流水） 3. 事务边界：退款单状态更新 + 余额更新 + 流水插入必须在同一事务 4. 并发控制方案：Redis分布式锁，key = lock:refund:approve:{refundOrderId}，超时30s 5. 风险点分级（如：余额并发更新 -> 已用原子SQL规避；锁未释放导致死锁 -> finally释放） 6. 需要澄清的业务点（如：退款金额是否包含优惠券部分？我们假设仅退现金）审核 AI 输出的分析报告 重点检查：SQL 是否使用原子更新而非 select-then-update？事务是否覆盖了所有需要一致性的操作？幂等逻辑是否只是状态检查（可靠吗？）？结合 Redis 锁后的行为？权限校验是否遗漏（Controller 上要加 RBAC 注解）？修改方案直到满意，若有业务不确定点，拉产品经理确认。产出物：一份人工确认过的技术方案（可保存在 ai/reviews/ 下作为设计记录）。阶段 4：AI 开发实现——戴着手铐跳舞目标：AI 严格遵守约束，产出符合规范的代码。操作步骤：在同一会话（或保留上下文的新会话）发送开发 Prompt基于我们已确认的方案，现在开始编码。 请严格遵守以下约束： - 仅修改 task 中 Allowed Modules 内的文件 - 遵守 AGENTS.md 所有规则（SQL规则、事务规则、架构分层等） - 使用 JdbcTemplate，保持与现有代码风格一致 - 实现幂等、分布式锁、事务、原子更新 - 任何新增文件需先说明用途 请按顺序产出： 1. DAO 层方法（JdbcTemplate实现） 2. Service 层逻辑（含事务和锁） 3. Controller 层（参数校验、调用Service） 4. 针对每个 SQL 给出 EXPLAIN 输出 5. 自认为的实现风险点监控 AI 产出 人随时检查 AI 是否有越界行为（比如偷偷修改了 WalletController），一旦发现立即叫停并纠正。 通常 AI 会直接给出代码块，将其复制到对应文件或通过工具应用。产出物：修改的 Java 文件（及可能的 SQL 迁移脚本），AI 提供的 EXPLAIN 结果。阶段 5：质量审查——AI 自首 + 人工终审目标：系统性捕捉缺陷，防止带病上线。操作步骤：发送 Review Prompt请对我们刚刚实现的退款审核接口进行完整 Review，严格遵守 AGENTS.md 的 Review 规则。 逐一检查： - SQL 注入 - EXPLAIN 分析（再次确认索引命中） - 事务是否覆盖正确 - 空指针可能点 - 业务不变量（balance >= 0, 状态机） - 幂等性 - Redis 锁的使用（正确加锁、解锁、超时） - RBAC 权限注解是否添加 - 缓存一致性（如果涉及缓存） 请输出结构化 Review 报告，标注每项的通过/不通过/风险及建议。人复核 Review 报告对阻断性问题，让 AI 立即修复，然后重新走一遍轻量 review。存档报告到 ai/reviews/REVIEW-REFUND-APPROVE-20260527.md。产出物：Review 报告，以及修复后的代码。阶段 6：知识沉淀——把教训变成 AI 的长期记忆目标：让这次开发的经验在未来项目中自动生效。操作步骤：提炼 engineering-knowledge 从 Review 报告和开发过程中的坑点提炼通用知识，例如：refund-atomic-balance.md（退款余额原子更新）redis-lock-finally-unlock.md（锁必须 finally 释放）人可手动编写或让 AI 生成：从本次 Review 和开发日志中提取可复用的工程知识，按 ai/engineering-knowledge/ 模板创建文件。生成 development-log请生成本次开发日志，包含已完成功能、遗留风险、关键决策、下一步计划，存入 ai/development-logs/LOG-20260527-REFUND-APPROVE.md。产出物：新的 knowledge 文件和开发日志。阶段 7：交付上线——完成闭环目标：功能正式合入主分支。运行单元测试、集成测试。进行 Code Review（可能还有人工同事）。部署到测试环境验证。上线生产，监控日志和报警。此时，TASK-REFUND-APPROVE.md 移入 ai/tasks/archive/。四、对管理员后台的通用性说明上述流程不限于退款审核，任何后台功能开发皆可套用：新增列表查询：task 约束仅修改列表相关 DAO/Service，workflow 引用 add-admin-query.md，business 关注深分页、数据脱敏规则。导出报表：约束文件生成、异步任务、限流规则。权限/角色管理：重点关注 RBAC 规则、审计日志要求。配置管理：关注缓存刷新、并发修改。只需按照功能点创建对应的 business 规则和 task，选择或定制 workflow，其余审查、知识沉淀、开发日志步骤完全一致。五、核心成功要素回顾约束先行：business、AGENTS、task 必须写清楚再让 AI 动手。分析分离：先出方案、对齐，再编码，避免返工。审查强制：SQL、事务、幂等、并发必须逐项过关。知识积累：每次开发结束必须产出 knowledge，形成复利。记忆外挂：development-log 让 AI 随时恢复上下文，支撑连续迭代。坚持这套流程 2~3 个迭代后，团队会发现：新增后台功能的 AI 出错率大幅下降，开发速度稳步提升，而维护成本反而降低——因为所有规则都已编码在知识体系中，AI 越来越像一个懂业务的资深工程师。
目标：你不是学习：“怎么让 AI 帮你写几段代码”而是学习：“怎么真正用 AI 开发一个长期项目”下面这份。我会完全按：真实开发流程给你讲。一、先理解：AI 开发真正的问题是什么？很多新人以为：AI开发是：“会 Prompt”其实不是。真正问题是：AI 有三个巨大问题问题结果AI 会乱改代码改到别的模块AI 会忘记业务状态机错误AI 会忘记上下文第二天继续不了所以：真正的 AI 开发：本质是：管理 AI不是：单纯使用 AI二、真正 AI 工程的核心思想你以后：不要这样：错误方式“帮我开发后台”因为：AI 会：无限扩散改很多文件忘记事务忘记权限忘记 review正确方式你以后：每次只做：一个明确任务例如：开发用户分页接口不是：开发整个后台三、真正完整目录（你以后就这样）v3-system/ ├── apps/ ├── docs/ ├── business/ ├── ai/ │ ├── tasks/ │ ├── workflows/ │ ├── reviews/ │ ├── engineering-knowledge/ │ └── development-logs/ ├── AGENTS.md └── CLAUDE.md下面。我一个一个给你解释。四、docs（原始资料）作用存：项目原始资料例如：docs/ ├── auth.sql ├── business.sql ├── 管理员API.md └── 产品需求.md谁创建？你AI 能修改吗？不能为什么？因为：docs 是：原始真相AI：不能：修改真相五、business（最重要）这个：是：整个系统最重要的东西business 是什么？business：保存：业务规则例如：订单状态钱包规则分销规则提现规则为什么一定需要 business？因为：AI：根本不懂你的业务它只能猜。举例如果：你没有 business。AI 可能：允许：已完成订单退款但实际上：你业务：禁止完成后退款所以：业务规则：必须：你定义六、business 实际怎么写？文件business/order.md内容（真实版）# Order State Machine CREATED → PAID → DELIVERING → FINISHED 退款： PAID → REFUND_PENDING → REFUNDED 禁止： - FINISHED退款 - CREATED退款七、AGENTS.md（AI宪法）这个：超级重要。它是什么？它是：AI全局规则类似：公司制度文件AGENTS.md内容（初学者推荐版）# Global AI Rules ## SQL Rules - Never use SELECT * - Never update without WHERE - Always check indexes --- ## Scope Rules Never modify unrelated modules. --- ## Architecture Rules - Controller handles params only - Service handles business only - DAO handles SQL only --- ## Transaction Rules Critical operations must use transactions. --- ## Review Rules Always review: - transaction - sql - security - invariants八、为什么 AGENTS 很重要？因为：AI：非常容易乱来例如：你开发：用户分页AI：突然：改 auth改 wallet改 distributor很常见。所以：必须：提前立规矩九、tasks（最关键）本质本次任务 + AI边界为什么 task 最重要？因为：它决定：AI 允许做什么task 文件ai/tasks/admin-user-page.mdtask 内容（真实版）# Goal 开发： GET /admin/user/page --- # Features 支持： - phone - status - create_time 分页： - page - pageSize --- # Allowed Modules - user --- # Forbidden Modules - auth - wallet - distributor --- # Requirements 1. JdbcTemplate 2. Explain SQL 3. Prevent SQL Injection 4. Soft Delete Only --- # Deliverables Output: - modified files - sql - risks十、workflow（SOP）很多新人：最容易误解这个。workflow 不是任务workflow：不是：“开发用户分页”而是：“新增后台接口应该怎么做”workflow 本质AI工作流程模板文件ai/workflows/add-admin-api.md内容# Add Admin API Workflow ## Analysis 1. Analyze database 2. Check indexes 3. Check transactions 4. Check permissions --- ## Design Output: - api path - params - response - sql - risks --- ## Development 1. DAO 2. Service 3. Controller --- ## Review Must review: - explain - sql injection - RBAC - invariants十一、workflow 什么时候用？开始任务前例如：开发：退款接口AI：先读取：workflow知道：正确开发步骤十二、真正完整开发流程（重点）现在。真正开发开始。Step 1：你先看 docs理解：SQLAPI产品Step 2：你写 business定义：业务真相例如：退款规则。Step 3：你写 task限制：AI工作边界Step 4：AI读取 workflow学习：标准开发流程Step 5：第一次 AI 对话（分析）Prompt（你直接背）请读取： - AGENTS.md - business/order.md - ai/workflows/add-admin-api.md - ai/tasks/refund-api.md 现在： 只进行分析。 禁止开发。 输出： 1. API设计 2. SQL设计 3. 索引检查 4. 风险点 5. 事务分析 6. review建议为什么先分析？因为：AI：直接开发很危险Step 6：你审核分析结果检查：SQL风险事务是否符合业务Step 7：第二次 AI 对话（开发）Prompt（必背）请读取： - AGENTS.md - business/order.md - ai/workflows/add-admin-api.md - ai/tasks/refund-api.md 根据已确认方案。 现在开始开发。 要求： 1. 先DAO 2. 再Service 3. 最后Controller 输出： 1. 修改文件 2. explain sql 3. 风险 4. review建议Step 8：第三次 AI 对话（Review）Prompt（必背）review 当前模块。 检查： - explain - sql injection - indexes - null pointer - transaction - RBAC - idempotent - redis consistency - deep paginationreviews 保存什么？文件ai/reviews/refund-review.md内容# Refund Review 1. Missing Redis Lock 2. Callback not idempotent 3. Wallet rollback risk十三、engineering-knowledge（长期经验）review 后。你发现：支付回调容易重复这是：长期经验于是：保存：文件ai/engineering-knowledge/payment.md内容# Payment Callback 支付回调必须幂等。 原因： 支付平台会重复回调。十四、development-logs（开发记忆）文件ai/development-logs/refund-api.md内容# Refund API Development Log Completed: - refund api - sql review Risks: - no retry queue Next: - add retry mechanism十五、为什么必须有 DevelopmentLog？因为：AI：会失忆第二天：你继续开发。AI 读取：development-log快速恢复上下文。十六、真正成熟后的核心思想（最重要）你以后：不要认为：AI工程 = Prompt真正成熟后：AI工程：本质是：规则 + 边界 + SOP + Review + Knowledge + 记忆十七、最后一个真正重要的建议（一定记住）你现在：不要追求：“最完整治理”真正长期能跑的：一定是：最小必要治理先：能稳定开发再：慢慢增加规则这是：真正企业级 AI Native 开发