Springboot Patterns

affaan-m/ECC/docs/zh-CN/skills/springboot-patterns

by affaan-mef648e01899ba3e8dc6371642deaaf64b4477775No license275K starsListed Oct 9, 2026Updated Oct 9, 2026Repository updated 4 days ago

Spring Boot架构模式、REST API设计、分层服务、数据访问、缓存、异步处理和日志记录。用于Java Spring Boot后端工作。

Instructions onlySoftware Development
AI-generated overview

Reference patterns for building Spring Boot REST APIs, layered services, caching, async work and logging.

What it does
Provides Java code patterns for Spring Boot backends: REST controllers, Spring Data JPA repositories, transactional service layers, DTOs with validation, global exception handling, caching, async processing, SLF4J logging, request filters, pagination, retry logic and rate limiting. It also lists production defaults such as constructor injection, RFC 7807 error responses and connection pool tuning. It is an instruction-only reference with no scripts or generated artifacts.
When to use it
Use when building or reviewing Java Spring Boot services, especially REST APIs with controller-service-repository layering, Spring Data JPA, caching, async handling, validation, pagination or event-driven patterns. Also useful when setting up environment profiles or production defaults for such services.
Requirements
No scripts or bundled assets; the agent only needs the document. Applying the patterns assumes a Java Spring Boot project with Spring MVC or WebFlux, Spring Data JPA, and optional dependencies such as Bucket4j for rate limiting, Micrometer or OpenTelemetry for observability, and a message queue for background jobs.

Spring Boot 开发模式

用于可扩展、生产级服务的 Spring Boot 架构和 API 模式。

何时激活

  • 使用 Spring MVC 或 WebFlux 构建 REST API
  • 构建控制器 → 服务 → 仓库层结构
  • 配置 Spring Data JPA、缓存或异步处理
  • 添加验证、异常处理或分页
  • 为开发/预发布/生产环境设置配置文件
  • 使用 Spring Events 或 Kafka 实现事件驱动模式

REST API 结构

java
@RestController@RequestMapping("/api/markets")@Validatedclass 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));  }}

仓库模式 (Spring Data JPA)

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

带事务的服务层

java
@Servicepublic 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);  }}

DTO 和验证

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

异常处理

java
@ControllerAdviceclass 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"));  }}

缓存

需要在配置类上使用 @EnableCaching。

java
@Servicepublic 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) {}}

异步处理

需要在配置类上使用 @EnableAsync。

java
@Servicepublic class NotificationService {  @Async  public CompletableFuture<Void> sendAsync(Notification notification) {    // send email/SMS    return CompletableFuture.completedFuture(null);  }}

日志记录 (SLF4J)

java
@Servicepublic 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();  }}

中间件 / 过滤器

java
@Componentpublic 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);    }  }}

分页和排序

java
PageRequest page = PageRequest.of(pageNumber, pageSize, Sort.by("createdAt").descending());Page<Market> results = marketService.list(page);

容错的外部调用

java
public <T> T withRetry(Supplier<T> supplier, int maxRetries) {  int attempts = 0;  while (true) {    try {      return supplier.get();    } catch (Exception ex) {      attempts++;      if (attempts >= maxRetries) {        throw ex;      }      try {        Thread.sleep((long) Math.pow(2, attempts) * 100L);      } catch (InterruptedException ie) {        Thread.currentThread().interrupt();        throw ex;      }    }  }}

速率限制 (过滤器 + Bucket4j)

安全须知:默认情况下 X-Forwarded-For 头是不可信的,因为客户端可以伪造它。 仅在以下情况下使用转发头:

  1. 您的应用程序位于可信的反向代理(nginx、AWS ALB 等)之后
  2. 您已将 ForwardedHeaderFilter 注册为 bean
  3. 您已在应用属性中配置了 server.forward-headers-strategy=NATIVE 或 FRAMEWORK
  4. 您的代理配置为覆盖(而非追加)X-Forwarded-For 头

当 ForwardedHeaderFilter 被正确配置时,request.getRemoteAddr() 将自动从转发的头中返回正确的客户端 IP。 没有此配置时,请直接使用 request.getRemoteAddr()——它返回的是直接连接的 IP,这是唯一可信的值。

java
@Componentpublic class RateLimitFilter extends OncePerRequestFilter {  private final Map<String, Bucket> buckets = new ConcurrentHashMap<>();
  /*   * SECURITY: This filter uses request.getRemoteAddr() to identify clients for rate limiting.   *   * If your application is behind a reverse proxy (nginx, AWS ALB, etc.), you MUST configure   * Spring to handle forwarded headers properly for accurate client IP detection:   *   * 1. Set server.forward-headers-strategy=NATIVE (for cloud platforms) or FRAMEWORK in   *    application.properties/yaml   * 2. If using FRAMEWORK strategy, register ForwardedHeaderFilter:   *   *    @Bean   *    ForwardedHeaderFilter forwardedHeaderFilter() {   *        return new ForwardedHeaderFilter();   *    }   *   * 3. Ensure your proxy overwrites (not appends) the X-Forwarded-For header to prevent spoofing   * 4. Configure server.tomcat.remoteip.trusted-proxies or equivalent for your container   *   * Without this configuration, request.getRemoteAddr() returns the proxy IP, not the client IP.   * Do NOT read X-Forwarded-For directly—it is trivially spoofable without trusted proxy handling.   */  @Override  protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,      FilterChain filterChain) throws ServletException, IOException {    // Use getRemoteAddr() which returns the correct client IP when ForwardedHeaderFilter    // is configured, or the direct connection IP otherwise. Never trust X-Forwarded-For    // headers directly without proper proxy configuration.    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());    }  }}

后台作业

使用 Spring 的 @Scheduled 或与队列(如 Kafka、SQS、RabbitMQ)集成。保持处理程序是幂等的和可观察的。

可观测性

  • 通过 Logback 编码器进行结构化日志记录 (JSON)
  • 指标:Micrometer + Prometheus/OTel
  • 追踪:带有 OpenTelemetry 或 Brave 后端的 Micrometer Tracing

生产环境默认设置

  • 优先使用构造函数注入,避免字段注入
  • 启用 spring.mvc.problemdetails.enabled=true 以获得 RFC 7807 错误 (Spring Boot 3+)
  • 根据工作负载配置 HikariCP 连接池大小,设置超时
  • 对查询使用 @Transactional(readOnly = true)
  • 在适当的地方通过 @NonNull 和 Optional 强制执行空值安全

记住:保持控制器精简、服务专注、仓库简单,并集中处理错误。为可维护性和可测试性进行优化。

Source and attribution

Source:affaan-m/ECCindocs/zh-CN/skills/springboot-patternsat commitef648e0

License: No license

Content belongs to its original authors. SourceWeft indexes it from a public repository.

Report or request removal