ECC 技能解析:Spring Boot 生产级架构与 API 设计模式完整实践指南
本指南以开源仓库 ECC 内置技能文档 skills/springboot-patterns/SKILL.md(对应 .kiro 安装副本 .kiro/skills/springboot-patterns/SKILL.md)为骨架,系统讲解 Spring Boot 后端工程从 Controller → Service → Repository 分层、数据访问、缓存、异步、日志、限流到可观测性的全套生产级模式,并对照仓库中的自动装配映射与评审规则给出可执行的落地清单。读完本文将掌握一套"构建与评审"双视角下的 Spring Boot 最佳实践,可直接用于日常后端开发与代码评审。
ECC(agent harness performance optimization system)将本文所讲解的模式沉淀为一个名为 springboot-patterns 的技能:当 Agent 在 Java Spring Boot 后端上构建或评审代码(REST 层、服务层、数据访问、缓存或异步逻辑)时会被自动激活。技能本身是"模式知识库",模式代码示例中的市场(Market)领域模型只是用于演示的参考上下文,真正重要的是把每一层该做什么、边界划在哪里、防御点如何布置一次性讲清楚。
技能定位:何时应当启用 springboot-patterns
技能元数据(frontmatter)给出明确激活条件,凡命中以下场景,Agent 应加载该技能以约束实现风格:
- 使用 Spring MVC 或 WebFlux 构建 REST API;
- 组织 controller → service → repository 分层结构;
- 配置 Spring Data JPA、缓存(Caching)或异步(Async)处理;
- 引入 Bean Validation 校验、统一异常处理或分页;
- 为 dev / staging / production 环境设置 Profile;
- 使用 Spring Events 或 Kafka 实现事件驱动模式。
在 ECC 中该技能不是孤立存在的。查看 config/project-stack-mappings.json 可确认它隶属于 springboot 技术栈的默认技能组合:
| 技术栈项 | 内容 |
|---|---|
| 栈标识 | springboot,名称 "Spring Boot (Java/Kotlin)" |
| 项目识别信号 | pom.xml、build.gradle、build.gradle.kts 中包含 spring-boot 关键字 |
| 绑定规则 | common、java |
| 绑定技能 | springboot-patterns、springboot-tdd、springboot-verification、springboot-security、java-coding-standards、tdd-workflow、verification-loop |
| 常用命令 | 构建 ./mvnw compile / ./gradlew build;测试 ./mvnw test / ./gradlew test;lint ./mvnw checkstyle:check;格式化 ./mvnw spotless:apply;开发 ./mvnw spring-boot:run / ./gradlew bootRun |
| 权限约束 | 允许 Maven/Gradle/java 执行,禁止 deploy、publish 类操作 |
同时,manifests/install-modules.json 将 skills/springboot-patterns 归入 framework-language(框架与语言核心工程技能)模块随包分发。这意味着:当你在一个含 spring-boot 的 Maven/Gradle 项目根目录运行 ECC 的 /project-init 时,上面的技能组合与构建命令会被自动配置好,springboot-patterns 即为 Spring Boot 类工程写作风格与架构约束的基准参照。
REST API 分层结构:controller 只做编排,不做业务
技能强调的第一条纪律是"薄 Controller"。控制器只负责接收 HTTP 请求、做参数绑定与权限级校验,然后立即委托给服务层,禁止在其中写业务逻辑(agents/java-reviewer.md 也将"控制器内的业务逻辑必须立刻下沉到服务层"列为 HIGH 级架构问题)。
技能文档给出一个完整的列表 + 创建双端点示例:
@RestController
@RequestMapping("/api/markets")
@Validated
class MarketController {
private final MarketService marketService;
MarketController(MarketService marketService) {
this.marketService = marketService;
}
@GetMapping
ResponseEntity<Page<MarketResponse>> list(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {
Page<Market> markets = marketService.list(PageRequest.of(page, size));
return ResponseEntity.ok(markets.map(MarketResponse::from));
}
@PostMapping
ResponseEntity<MarketResponse> create(@Valid @RequestBody CreateMarketRequest request) {
Market market = marketService.create(request);
return ResponseEntity.status(HttpStatus.CREATED).body(MarketResponse.from(market));
}
}
关键点逐条拆解:
@Validated+@Valid双保险:类级@Validated激活方法级参数校验(如@RequestParam约束),@Valid @RequestBody触发请求体的 Bean Validation。- 构造函数注入而非字段注入:字段使用
private final,通过构造函数完成注入。技能在 "Production Defaults" 中明确"优先构造函数注入、避免字段注入",这保证 Bean 一旦构造即处于完整状态、便于单元测试,也消除了循环依赖的隐患。java-reviewer 规则将@Autowired字段注入直接标记为 code smell。 - 返回
ResponseEntity<T>精确控制状态码:创建成功返回201 Created,列表成功返回200 OK。评审规则中"创建接口返回 200 空 body 而不是 201、缺失资源返回 200 而不是 404"属于错误做法。 - 领域对象不直接出网:
markets.map(MarketResponse::from)把内部实体投影为响应 DTO。直接向客户端暴露 JPA 实体(懒加载代理、密码/内部字段泄漏、JSON 循环引用)是评审红线。 - 响应即分页:返回
Page<T>而非裸List<T>,未分页的列表端点同样被评审规则列为 HIGH。
Repository 模式(Spring Data JPA):接口即查询契约
数据访问层使用 Spring Data JPA 的派生查询与 @Query 命名查询,仓库保持"简单",复杂逻辑一律上移到服务层:
public interface MarketRepository extends JpaRepository<MarketEntity, Long> {
@Query("select m from MarketEntity m where m.status = :status order by m.volume desc")
List<MarketEntity> findActive(@Param("status") MarketStatus status, Pageable pageable);
}
要点与纵深:
@Query+ 参数绑定防注入:JPQL 中通过命名参数:status(@Param("status")标注)绑定,禁止字符串拼接查询。java-reviewer 将@Query、JdbcTemplate、NamedParameterJdbcTemplate中的拼接 SQL 列为 CRITICAL 级注入风险。Pageable作为方法参数:排序与分页由调用方注入,Sort/PageRequest与 SQL 解耦,天然避免"接口数量爆炸"。- N+1 防护意识:集合关联上禁用
FetchType.EAGER,改用JOIN FETCH或@EntityGraph;凡是返回集合的关联查询都要审视是否会逐条触发懒加载。此外,@Query若包含更新/删除语句,必须加@Modifying并配合事务,否则运行时失败。 - 实体只属于持久化层:
MarketEntity(带 JPA 注解的实体)与领域对象Market、响应MarketResponse各自独立,这正是评审中"实体直接返回 controller"反模式的规避方式。
Service 层与事务:@Transactional 只出现在这里
服务层承载全部业务编排,是事务的唯一合法宿主。技能示例:
@Service
public class MarketService {
private final MarketRepository repo;
public MarketService(MarketRepository repo) {
this.repo = repo;
}
@Transactional
public Market create(CreateMarketRequest request) {
MarketEntity entity = MarketEntity.from(request);
MarketEntity saved = repo.save(entity);
return Market.from(saved);
}
}
实践要点:
- 事务边界 = 服务方法:
@Transactional放在 controller 或 repository 上都属于错误分层(java-reviewer 明确列出该问题)。事务跨多个仓库操作时才能保证原子性。 - 读操作显式声明
readOnly = true:写操作默认读写事务,只读查询声明readOnly = true可让 Hibernate 关闭脏检查并允许底层驱动走读优化。这是技能 "Production Defaults" 的硬性要求。 repo.save()的语义:save对已存在主键执行 merge、对新实体执行 persist,属于仓储封装而非"必须调用才落库";理解@Transactional下 flush 时机(方法提交时)比盲目依赖 save 返回值更重要。- 空值策略:服务层返回
Optional<T>而不是 null;消费方用orElseThrow而不是裸.get()(java-reviewer 将无保护的.get()列为 CRITICAL 错误处理缺陷)。
DTO 与 Bean Validation:record + 声明式校验
从 Java 16 起,DTO 首选 record 承载不可变数据,并用 Bean Validation 注解在边界完成声明式校验:
public record CreateMarketRequest(
@NotBlank @Size(max = 200) String name,
@NotBlank @Size(max = 2000) String description,
@NotNull @FutureOrPresent Instant endDate,
@NotEmpty List<@NotBlank String> categories) {}
public record MarketResponse(Long id, String name, MarketStatus status) {
static MarketResponse from(Market market) {
return new MarketResponse(market.id(), market.name(), market.status());
}
}
参数语义说明:
| 注解 | 作用 | 典型取值/触发时机 |
|---|---|---|
@NotBlank |
字符串非空且去空白后长度 > 0 | name、description |
@Size(max = n) |
上限约束,配合 @NotBlank 限制长度 | 200(短文本)、2000(长文本) |
@NotNull |
引用不可为空 | endDate |
@FutureOrPresent |
时间必须为当前或未来(面向截止日期类业务) | Instant/日期字段 |
@NotEmpty |
容器非空 | categories |
List<@NotBlank String> |
容器元素级校验(type-use 约束) | 集合内每个元素均须合法 |
配套技巧:请求 DTO(入站)与响应 DTO(出站)分离,MarketResponse.from(market) 这类静态工厂统一做领域对象 → DTO 投影,禁止 controller 手写散落的字段拷贝;若存在高度复用的转换,可进一步使用 MapStruct 生成映射代码,但技能示例保持纯手写以最小化依赖。
集中式异常处理:@ControllerAdvice + 语义化状态码
业务异常绝不允许散落在 controller 的 try-catch 中。技能给出统一出口:
@ControllerAdvice
class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
ResponseEntity<ApiError> handleValidation(MethodArgumentNotValidException ex) {
String message = ex.getBindingResult().getFieldErrors().stream()
.map(e -> e.getField() + ": " + e.getDefaultMessage())
.collect(Collectors.joining(", "));
return ResponseEntity.badRequest().body(ApiError.validation(message));
}
@ExceptionHandler(AccessDeniedException.class)
ResponseEntity<ApiError> handleAccessDenied() {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(ApiError.of("Forbidden"));
}
@ExceptionHandler(Exception.class)
ResponseEntity<ApiError> handleGeneric(Exception ex) {
// Log unexpected errors with stack traces
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ApiError.of("Internal server error"));
}
}
要点说明:
- 按异常类型映射状态码:校验失败
400、权限不足403、兜底未知异常500。兜底 handler 不应把异常堆栈回写给客户端,只记录日志(示例注释明确"Log unexpected errors with stack traces")。 - 校验错误聚合:遍历
BindingResult的 field errors,拼成"field: message"形式返回,前端可直接展示。 - 统一错误信封
ApiError:让所有失败响应共享同一结构(技能示例中的ApiError.validation(...)、ApiError.of(...)为错误体构造入口),客户端可稳定解析。若需要字段级明细,可扩展为{"code","message","fieldErrors":[...]}结构。 - Spring Boot 3+ 进阶:开启
spring.mvc.problemdetails.enabled=true即可获得符合 RFC 7807 的application/problem+json标准错误体,与@ControllerAdvice方案互补。 - 一致性要求:若工程内某处异常处理绕过了该 Advice、在 controller 内各自为政,java-reviewer 将直接判为 "No
@RestControllerAdvice(异常处理散落)" 缺陷。
缓存:@Cacheable / @CacheEvict 与开启开关
技能文档强调:缓存注解本身并不生效,必须在某个配置类上声明 @EnableCaching,否则 @Cacheable 只是摆设。
@Service
public class MarketCacheService {
private final MarketRepository repo;
public MarketCacheService(MarketRepository repo) {
this.repo = repo;
}
@Cacheable(value = "market", key = "#id")
public Market getById(Long id) {
return repo.findById(id)
.map(Market::from)
.orElseThrow(() -> new EntityNotFoundException("Market not found"));
}
@CacheEvict(value = "market", key = "#id")
public void evict(Long id) {}
}
@Cacheable(value = "market", key = "#id"):缓存名market(对应底层 Cache 区域),键 SpEL#id使用方法入参。命中缓存时方法体不执行;未命中则执行并将结果入缓存。@CacheEvict:更新/删除场景删除对应键,防止脏读。缓存一致性采用"先写库、后逐出"的策略时注意事务提交顺序,避免提交前逐出导致并发窗口。EntityNotFoundException抛出语义:读不到数据时的统一业务异常,供前面的全局 Advice 映射 404。- 缓存本质上仍要处理并发击穿/穿透/雪崩;技能保持"最小可用 + 显式驱逐"的克制,更复杂的分布式缓存策略属于上层架构决策。
异步处理:@EnableAsync + @Async + 有界线程池
与缓存同理,异步调用需要配置类声明 @EnableAsync,随后在服务方法上加 @Async 即可异步执行:
@Service
public class NotificationService {
@Async
public CompletableFuture<Void> sendAsync(Notification notification) {
// send email/SMS
return CompletableFuture.completedFuture(null);
}
}
关键认知:
@Async的代理本质:Spring 通过 AOP 代理切入异步执行。调用必须经由 Spring 管理的 Bean 代理(从另一个 Bean 注入调用),同类内部this自调用会绕过代理导致异步失效。- 返回类型:返回
void(调用即弃)或CompletableFuture<T>(可聚合)。不要在@Async方法上返回裸Future之外的复杂类型。 - 线程池必须显式配置:java-reviewer 明确指出,Spring 默认的
SimpleAsyncTaskExecutor每任务新建线程、无界增长;@Async或CompletableFuture若不配置自定义Executor,将产生无界线程风险。生产环境应提供有界、带队列与拒绝策略的ThreadPoolTaskExecutorBean。 - 失败面:
void异步方法的异常对调用方不可见,需在 Executor 或方法内捕获上报指标/日志,保证失败可观测。
日志(SLF4J):参数化占位符与包含上下文的错误日志
技能示例使用 SLF4J API,强调"结构化字段 + 不拼接字符串":
@Service
public class ReportService {
private static final Logger log = LoggerFactory.getLogger(ReportService.class);
public Report generate(Long marketId) {
log.info("generate_report marketId={}", marketId);
try {
// logic
} catch (Exception ex) {
log.error("generate_report_failed marketId={}", marketId, ex);
throw ex;
}
return new Report();
}
}
- 参数化日志:
"marketId={}"占位符由日志框架延迟求值,避免字符串拼接开销,无谓的拼接在热路径上是真实成本。 - 事件名风格:
generate_report、generate_report_failed用下划线命名事件,后续可按事件名聚合统计成功率。 - 异常日志的完整调用栈:
log.error(msg, args, ex)把异常作为最后一个参数传入,保证堆栈完整落盘。 - 绝不记敏感信息:java-reviewer 在 CRITICAL 安全项中强调,认证附近代码若出现密码、令牌入日志,即为 PII/凭据泄漏事故(Spring 侧重点检查 SLF4J 的
log.info(...))。 - 生产进阶:通过 Logback JSON encoder 输出结构化日志,便于采集进日志系统做字段检索(与后文 Observability 呼应)。
过滤器(OncePerRequestFilter):请求日志与请求级切面
Servlet 请求日志使用继承 OncePerRequestFilter 的 @Component,保证同一请求在转发场景下只执行一次:
@Component
public class RequestLoggingFilter extends OncePerRequestFilter {
private static final Logger log = LoggerFactory.getLogger(RequestLoggingFilter.class);
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
long start = System.currentTimeMillis();
try {
filterChain.doFilter(request, response);
} finally {
long duration = System.currentTimeMillis() - start;
log.info("req method={} uri={} status={} durationMs={}",
request.getMethod(), request.getRequestURI(), response.getStatus(), duration);
}
}
}
try/finally保证必记:无论下游抛异常与否,响应状态与耗时都会被记录(response.getStatus()在异常被全局 Advice 处理前可能仍为默认 200,若需精确值可在 Advice 中另行补充——这是实现层面需留意的细节)。- 声明为
@Component即自动注册:无需手写 FilterRegistrationBean,Spring Boot 会自动将其加入过滤器链并作用于全部请求。 - 注意
OncePerRequestFilter的shouldNotFilter可对静态资源、监控端点做白名单,避免日志噪声。
分页与排序:PageRequest 组合 Sort
PageRequest page = PageRequest.of(pageNumber, pageSize, Sort.by("createdAt").descending());
Page<Market> results = marketService.list(page);
PageRequest.of(page, size, sort):从 0 起始的页码 + 页大小 +Sort排序。- 将
Page<Market>传给 controller 后,响应可包含totalElements、totalPages、hasNext等元数据;技能示例用Page.map(MarketResponse::from)投影,避免实体出网。 - 白名单排序字段:直接透传用户可控排序字段存在注入与滥用风险,生产实现应对可排序字段做白名单映射后再构造
Sort。
面向外部调用的弹性重试:带退避上限的手写实现与生产级替代
技能提供了"零依赖可用"的手写重试工具方法,并明确给出生产建议——若追求健壮性请使用 Resilience4j / Spring Retry(含熔断、指标与可配置策略):
public <T> T withRetry(Supplier<T> supplier, int maxRetries) {
final long maxBackoffMillis = 10_000L;
int attempts = 0;
while (true) {
try {
return supplier.get();
} catch (Exception ex) {
attempts++;
if (attempts >= maxRetries) {
throw ex;
}
try {
long backoff = Math.min((long) Math.pow(2, attempts) * 100L, maxBackoffMillis);
Thread.sleep(backoff);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw ex;
}
}
}
}
- 指数退避 + 上限封顶:每次重试间隔
2^attempts * 100ms,并Math.min收敛到maxBackoffMillis = 10s,防止对下游造成风暴。 - 中断即放弃并复位中断位:捕获
InterruptedException后Thread.currentThread().interrupt()恢复中断状态再抛出原异常,这是 Java 并发下处理中断的标准姿势。 - 只重试可恢复异常:示例捕获
Exception属教学简化,生产应只对超时、瞬时网络错误等重试,业务异常直接上抛;同时 java-reviewer 提醒"指数退避不加抖动会导致惊群效应(thundering herd)",大量客户端同时重试会在退避点齐射,生产应叠加随机 jitter。
限流:Bucket4j 过滤器 + 对 X-Forwarded-For 的安全处理
技能用 OncePerRequestFilter + Bucket4j 令牌桶实现按客户端 IP 的限流,核心代码如下:
@Component
public class RateLimitFilter extends OncePerRequestFilter {
private final Map<String, Bucket> buckets = new ConcurrentHashMap<>();
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String clientIp = request.getRemoteAddr();
Bucket bucket = buckets.computeIfAbsent(clientIp,
k -> Bucket.builder()
.addLimit(Bandwidth.classic(100, Refill.greedy(100, Duration.ofMinutes(1))))
.build());
if (bucket.tryConsume(1)) {
filterChain.doFilter(request, response);
} else {
response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
}
}
}
这是全文档安全警示最密集的一节,务必逐条落实:
X-Forwarded-For默认不可信:客户端可以任意伪造该头。技能明确列出仅在同时满足以下条件时才可依赖转发头:应用位于可信反向代理(nginx、AWS ALB 等)之后;已注册ForwardedHeaderFilterBean;已在配置中设置server.forward-headers-strategy=NATIVE或FRAMEWORK;代理配置为覆盖(overwrite)而非追加(append)X-Forwarded-For。- 客户端 IP 的取值策略:当
ForwardedHeaderFilter正确配置后,request.getRemoteAddr()会自动从转发头解析出真实客户端 IP;未配置时它返回直连 IP(即代理 IP),而这恰恰是唯一可信的值。技能代码注释反复强调:永远不要直接读取X-Forwarded-For头——在缺少可信代理处理时它可被轻松伪造,攻击者可伪造任意 IP 绕过限流或干扰他人额度。 - 限流参数:示例为每个 IP 构建一个容量 100、以每分钟 100 令牌速率补充(
Refill.greedy即补满式)的令牌桶,每次请求消耗 1 令牌;桶满则返回429 Too Many Requests。令牌桶相比固定窗口能容忍突发。 - 内存态 Map 的适用边界:
ConcurrentHashMap保存桶实例适合单实例场景;多实例部署需把计数状态外置(Redis 等),否则限流总额会被实例数放大。 - 可配置项对齐:与
server.tomcat.remoteip.trusted-proxies或容器对应的远程 IP 信任配置配合,才能让 NATIVE 策略准确识别可信代理列表。
后台任务:@Scheduled 与消息队列
技能给出的方向性约束:使用 Spring 的 @Scheduled 定时执行,或与消息队列(Kafka、SQS、RabbitMQ)集成做异步解耦;处理器必须幂等且可观测。
配套要点(与 java-reviewer 规则互相印证):
- 调度线程勿被长任务阻塞:长时间运行的
@Scheduled方法不应占用调度线程;同时,调度方法的异常若不捕获会导致后续触发被吞,需要异常处理与监控告警。 - 分布式锁:多实例部署下同一定时任务会在每个实例执行,需引入分布式锁(如基于数据库/ShedLock/Redis)保证单实例执行。
- 消费端幂等:事件可能重复投递,处理逻辑必须以业务键去重(如唯一约束 + 幂等表),保证重复消息无副作用。
- 失败兜底:消息处理失败要有重试队列与死信(DLQ)策略,避免消息无限重投或静默丢失。
可观测性三件套
技能列出生产系统必须具备的观测能力,方向清晰、可直接对标:
- 结构化日志(JSON):通过 Logback encoder(如
logstash-logback-encoder)输出 JSON 行,字段可被日志平台直接检索、聚合与告警。 - 指标(Metrics):Micrometer 作为门面,暴露给 Prometheus(
/actuator/prometheus)或 OpenTelemetry(OTel)采集,覆盖 HTTP 延迟、错误率、线程池、连接池等核心指标。 - 链路追踪(Tracing):Micrometer Tracing 接 OpenTelemetry 或 Brave 后端,为分布式请求生成 traceId/spanId,贯穿网关、服务与数据库,使"请求日志 → 指标 → 链路"可交叉关联。
落地最小集建议:引入 spring-boot-starter-actuator 暴露健康与指标端点,配合上述方案即可得到可监控的基座。
生产默认配置清单(Production Defaults)
技能在结尾给出了可直接当"验收清单"使用的生产默认项:
- 构造函数注入优先,杜绝字段注入(
@Autowired字段注入 = 反模式)。 - 开启 RFC 7807 标准错误体:Spring Boot 3+ 设置
spring.mvc.problemdetails.enabled=true。 - 显式配置 HikariCP 连接池:按工作负载设定最大/最小池大小并配置连接超时(如
spring.datasource.hikari.maximum-pool-size、connection-timeout),避免默认值不适配高并发或导致连接耗尽。 - 查询型服务方法声明
@Transactional(readOnly = true)。 - 空安全策略:恰当使用
@NonNull(或org.springframework.lang.NonNull)注解与Optional返回值,消灭隐式 null 传递。
总结口诀(技能原文):让 controller 保持薄、service 保持专注、repository 保持简单、异常集中处理,一切以可维护性与可测试性为优化目标。
在 ECC 中的联动机制:技能如何被"评审者"消费
springboot-patterns 不仅是写作指南,还是 ECC 代码评审管线的模式基准。Java 评审 Agent agents/java-reviewer.md 在 frontmatter 中声明"Expert Java code reviewer for Spring Boot and Quarkus projects",其评审规则与本技能一一呼应,形成"规范 → 评审"闭环:
| 技能中的约定 | java-reviewer 对应检查 |
|---|---|
| 构造函数注入优先 | @Autowired 字段注入 → code smell(HIGH) |
@Transactional 只放 Service |
出现在 controller/repository → HIGH 缺陷 |
| 实体不出网、DTO 投影 | 实体直接从 controller 返回 → HIGH 缺陷 |
| 集中式异常处理 | 无 @RestControllerAdvice → CRITICAL 错误处理缺陷 |
读取用 Optional |
无保护 .get() → CRITICAL |
| 参数化查询 | @Query/JdbcTemplate SQL 拼接 → CRITICAL 注入 |
@Query 更新语句 |
缺 @Modifying → HIGH(JPA 运行时失败) |
分页返回 Page<T> |
裸 List<T> 无分页 → HIGH |
| N+1 防护 | 集合 FetchType.EAGER → HIGH |
@Async 需要自定义 Executor |
默认无界线程 → MEDIUM |
| 重试需加 jitter | 纯指数退避 → MEDIUM(惊群效应) |
评审 Agent 还给出配套的测试切面建议:单元测试避免 @SpringBootTest 全量启动,controller 用 @WebMvcTest、repository 用 @DataJpaTest、服务层用 @ExtendWith(MockitoExtension.class) + Mockito;异步断言使用 Awaitility 而不是 Thread.sleep。这些与 skills/springboot-tdd/SKILL.md(Spring Boot TDD 技能)、skills/springboot-security/SKILL.md(安全加固技能)、skills/springboot-verification/SKILL.md(验证技能)共同构成完整的 Spring Boot 技能族,后者在 manifests/install-modules.json 的 framework-language 模块中与本文技能同批分发。
一套完整的"对照式"落地建议
把整篇技能落成一个可执行的行动序列:
- 开工前(Spring Boot 工程识别):确认
pom.xml/build.gradle(.kts)含spring-boot,进入本技能上下文;初始化时同步加载springboot-tdd、springboot-security、springboot-verification。 - 建控制器:只写 URL 映射、参数绑定与状态码;用
@Validated+@Valid;返回 DTO 或Page<DTO>。 - 建服务:承载事务与业务编排;写方法加
@Transactional,读方法加readOnly = true;返回Optional或用orElseThrow。 - 建仓库:继承
JpaRepository,用@Query参数绑定或派生查询;避免 EAGER 与 N+1。 - 定义 DTO/校验:record + Bean Validation,入站出站分离。
- 加全局异常处理:一个
@ControllerAdvice覆盖校验、鉴权、兜底三类;Spring Boot 3+ 开启 problemdetails。 - 按需开启横切能力:缓存/异步分别补齐
@EnableCaching、@EnableAsync与有界线程池;过滤器统一做请求日志与限流,并严格按代理头安全清单配置。 - 收尾加固:结构化日志、Micrometer 指标、链路追踪;按 Production Defaults 五项做最终复查。
整个模式体系的设计取向非常清晰:它不追求炫技,而是把每一层的职责、边界与失败处理收敛成可复用、可评审、可测试的最小完备集合——这正是"controllers thin, services focused, repositories simple, errors handled centrally"所代表的、面向长期维护与生产可观测性的工程审美。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00