Spring Boot 3 微服务实践指南

1. 引言:为什么是 Spring Boot 3?

1.1 背景

微服务架构在过去十年中经历了从概念验证到工业级落地的完整演进。早期的 Spring Boot 2.x 时代,开发者主要关注的是如何快速构建独立的 HTTP 服务,并在 Spring Cloud 生态的支持下完成服务发现、配置管理和熔断降级等基础能力。然而,随着云原生时代的到来,传统的 JVM 运行模式开始暴露出明显的短板:启动慢、内存占用高、无法高效适配容器化编排环境。

Spring Boot 3 的推出正是对这些痛点的系统性回应。该版本基于 Jakarta EE 9+ 规范重构了命名空间,将核心依赖迁移至 Jakarta 命名体系,同时原生支持 GraalVM Native Image 编译技术。这意味着开发者可以将 Spring Boot 应用编译为本地可执行文件,启动时间从秒级降至毫秒级,内存 footprint 缩减 60% 以上。对于 Kubernetes 等容器平台而言,这种冷启动速度直接影响 Pod 弹性伸缩的响应延迟。

更关键的变化来自 Java 21 的虚拟线程(Virtual Threads)。传统线程模型在微服务高并发场景下面临严重瓶颈:每个请求对应一个线程,线程切换开销随并发量线性增长。虚拟线程通过用户态调度实现了轻量级线程,单次 JVM 进程可支撑数百万并发连接而不触发系统调用。Spring Boot 3 与 Spring Framework 6 对虚拟线程的原生支持,使得微服务在处理海量短时请求时具备真正的非阻塞能力。

云原生环境下的微服务需要具备快速启动、低内存占用和高并发处理能力,Spring Boot 3 的技术栈组合恰好满足了这一需求。

1.2 文章结构

本文将从工程实践角度系统阐述 Spring Boot 3 微服务的核心设计模式。首先探讨模块化单仓架构的依赖边界管理,其次分析同步与异步双模式的数据访问策略,随后深入安全认证的 OIDC/JWT 落地方案,接着演示基于 Spring Cloud Stream 的事件总线集成,最后覆盖可观测性体系的构建方法。全文贯穿可运行的代码示例,确保读者能够直接复现关键技术点。

2. 模块化单仓设计与依赖配置

2.1 多模块项目结构

单体仓库(Monorepo)模式在微服务团队中逐渐获得认可。与多仓模式相比,单仓能够保证所有服务共享同一套构建流水线、依赖版本和发布节奏,同时避免跨仓库版本对齐的协调成本。Spring Boot 3 推荐采用 Gradle 多模块项目组织代码,通过明确的模块划分实现业务边界的可视化。

典型的多模块结构包含四个层级:根构建模块 负责统一依赖版本声明,基础设施模块 提供通用组件如工具类和配置基类,领域模块 封装各业务域的实体和接口,应用模块作为独立部署单元组合领域逻辑并暴露 HTTP 端点。这种分层确保领域逻辑不与传输层耦合,同时也让测试可以在不同粒度上隔离执行。

groovy 复制代码
// settings.gradle.kts
rootProject.name = "microservice-platform"

include("infrastructure")
include("domain-user")
include("domain-order")
include("api-gateway")
include("service-user")
include("service-order")
groovy 复制代码
// build.gradle.kts(根构建)
plugins {
    java
    id("org.springframework.boot") version "3.3.0" apply false
    id("io.spring.dependency-management") version "1.1.5" apply false
}

subprojects {
    apply(plugin = "java")
    apply(plugin = "org.springframework.boot")
    apply(plugin = "io.spring.dependency-management")

    java {
        toolchain {
            languageVersion.set(JavaLanguageVersion.of(21))
        }
    }

    dependencies {
        implementation(platform("org.springframework.boot:spring-boot-dependencies:3.3.0"))
    }
}
groovy 复制代码
// service-user/build.gradle.kts
dependencies {
    implementation(project(":infrastructure"))
    implementation(project(":domain-user"))
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("org.springframework.boot:spring-boot-starter-data-jpa")
    implementation("org.springframework.boot:spring-boot-starter-oauth2-resource-server")
    runtimeOnly("org.postgresql:postgresql")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
}

2.2 Java 模块系统集成

Java Platform Module System(JPMS)为多模块项目提供编译期的依赖约束。通过在 module-info.java 中声明 requires 和 exports,可以将原本在运行时才能发现的循环依赖提前到编译阶段暴露。对于 Spring Boot 3 应用,建议在领域模块中引入模块描述文件,而 API 模块则通过 requires transitive 传递传递依赖。

java 复制代码
// domain-user/src/main/java/module-info.java
module com.example.domain.user {
    requires transitive com.example.infrastructure;
    
    exports com.example.domain.user.model;
    exports com.example.domain.user.repository;
    exports com.example.domain.user.service;
    
    provides com.example.domain.user.repository.UserRepository 
        with com.example.domain.user.repository.impl.PostgresUserRepository;
}
java 复制代码
// infrastructure/src/main/java/module-info.java
module com.example.infrastructure {
    requires jakarta.persistence;
    requires io.micrometer.core;
    
    exports com.example.infrastructure.config;
    exports com.example.infrastructure.security;
    exports com.example.infrastructure.aop;
}
java 复制代码
// service-user/src/main/java/module-info.java
open module com.example.service.user {
    requires static org.springframework.boot;
    requires com.example.domain.user;
    requires com.example.infrastructure;
    
    requires org.springframework.core;
    requires org.springframework.beans;
    requires org.springframework.security.config;
    requires org.springframework.web;
    
    opens com.example.service.user to org.springframework.core, org.springframework.beans;
}

模块系统的 open module 语法允许 Spring 框架在运行时通过反射访问包内私有成员,这是 Spring Boot 与 JPMS 兼容的必要配置。未加 open 关键字的模块会导致 InaccessibleObjectException 运行时异常。

3. 数据库访问与 Spring Data

3.1 Spring Data JPA 同步访问模式

对于大多数读多写少且事务边界清晰的服务,Spring Data JPA 仍是首选方案。Spring Boot 3 对 JPA 3.1+ 规范的完整支持确保了与 Hibernate 6.x 的无缝集成。核心优势在于开发者只需定义接口,框架自动提供基于代理的实现,同时 @Transactional 注解与声明式事务管理构成可靠的事务边界控制机制。

java 复制代码
package com.example.domain.user.repository.impl;

import com.example.domain.user.model.User;
import com.example.domain.user.repository.UserRepository;
import jakarta.persistence.EntityNotFoundException;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import java.util.UUID;

@Repository
public class PostgresUserRepository implements UserRepository {

    private final JpaRepository<User, UUID> delegate;

    public PostgresUserRepository(JpaRepository<User, UUID> delegate) {
        this.delegate = delegate;
    }

    @Override
    @Transactional(readOnly = true)
    public Optional<User> findById(UUID id) {
        return delegate.findById(id)
                .orElseThrow(() -> new EntityNotFoundException("User not found: " + id));
    }

    @Override
    @Transactional(readOnly = true)
    public Optional<User> findByEmail(String email) {
        return delegate.findByEmail(email);
    }

    @Override
    @Transactional
    public User save(User user) {
        return delegate.save(user);
    }

    @Override
    @Transactional
    public void deleteById(UUID id) {
        delegate.deleteById(id);
    }

    @Override
    @Transactional(readOnly = true)
    @Query("SELECT u FROM User u WHERE u.createdAt > :since ORDER BY u.createdAt DESC")
    public List<User> findRecentUsers(LocalDateTime since) {
        return delegate.findAll();
    }
}
java 复制代码
package com.example.domain.user.model;

import jakarta.persistence.*;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import java.time.LocalDateTime;
import java.util.UUID;

@Entity
@Table(name = "users")
@EntityListeners(AuditingEntityListener.class)
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    private UUID id;

    @Column(nullable = false, unique = true)
    private String email;

    @Column(nullable = false)
    private String passwordHash;

    @CreatedDate
    @Column(updatable = false)
    private LocalDateTime createdAt;

    // Getters and constructors omitted for brevity
}

3.2 R2DBC 异步访问模式

对于计算密集型或 I/O 等待占比高的场景,Spring Data R2DBC 提供基于 Reactor 的响应式数据访问能力。R2DBC 通过非阻塞 I/O 模型将线程释放延迟至数据真正就绪,配合 Java 21 虚拟线程可进一步放大并发吞吐量。需要注意的是,响应式编程要求整个调用链保持非阻塞,任何同步数据库操作都会破坏这一契约。

java 复制代码
package com.example.domain.order.repository.impl;

import com.example.domain.order.model.Order;
import com.example.domain.order.repository.OrderRepository;
import io.r2dbc.spi.ConnectionFactory;
import org.springframework.data.r2dbc.core.R2dbcEntityTemplate;
import org.springframework.r2dbc.core.DatabaseClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.time.Duration;
import java.util.UUID;

public class ReactiveOrderRepository implements OrderRepository {

    private final R2dbcEntityTemplate template;
    private final DatabaseClient databaseClient;

    public ReactiveOrderRepository(ConnectionFactory connectionFactory) {
        this.template = new R2dbcEntityTemplate(connectionFactory);
        this.databaseClient = DatabaseClient.create(connectionFactory);
    }

    @Override
    public Mono<Order> findById(UUID id) {
        return template.findById(id, Order.class)
                .switchIfEmpty(Mono.error(new IllegalArgumentException("Order not found: " + id)));
    }

    @Override
    public Flux<Order> findByUserId(UUID userId) {
        return template.select(Order.class)
                .matching(org.springframework.data.relational.core.query.Query.query(
                        org.springframework.data.relational.core.query.Criteria.where("user_id").is(userId)))
                .all();
    }

    @Override
    public Mono<Order> save(Order order) {
        return template.insert(order).then(Mono.just(order));
    }

    @Override
    public Mono<Void> deleteById(UUID id) {
        return template.deleteById(id, Order.class);
    }

    @Override
    public Mono<Long> countOrdersByStatus(String status) {
        return databaseClient.sql("SELECT COUNT(*) FROM orders WHERE status = :status")
                .bind("status", status)
                .map((row, meta) -> row.get(0, Long.class))
                .one()
                .defaultIfEmpty(0L);
    }

    @Override
    public Flux<Order> streamOrdersWithTimeout(Duration timeout) {
        return template.select(Order.class)
                .all()
                .timeout(timeout)
                .onErrorResume(TimeoutException.class, _ -> Flux.empty());
    }
}
java 复制代码
// application.yml
spring:
  r2dbc:
    url: r2dbc:postgresql://localhost:5432/orders_db
    username: app_user
    password: secure_password
  data:
    r2dbc:
      repositories:
        enabled: true

同步与异步数据访问并非互斥选择。Spring Boot 3 允许在同一应用中混合使用 JPA 和 R2DBC,通过细粒度的服务边界划分决定每个服务的访问模式。关键原则是:事务一致性要求高的场景选择 JPA,高并发低延迟场景选择 R2DBC。

4. 认证与授权安全策略

4.1 OAuth 2.0 / OIDC 安全配置

微服务间的身份传递必须遵循标准化协议。OAuth 2.0 授权框架与 OIDC 扩展为 Spring Boot 3 提供了开箱即用的安全能力。通过 spring-boot-starter-oauth2-resource-server,应用可以将自身配置为资源服务器,验证由授权服务器签发的 JWT 令牌,而无需自行实现密码存储和会话管理。

4.2 JWT 过滤器链实现

java 复制代码
package com.example.infrastructure.security;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf.disable())
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/actuator/health", "/actuator/info").permitAll()
                .requestMatchers("/api/public/**").permitAll()
                .requestMatchers("/api/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated())
            .oauth2ResourceServer(oauth2 -> oauth2
                .jwt(jwt -> jwt
                    .jwtAuthenticationConverter(jwtAuthenticationConverter()))
                .opaqueToken(opaque -> opaque
                    .introspectionUri("https://auth.example.com/oauth2/introspect")
                    .introspectionClientCredentials("client-id", "client-secret")));
        return http.build();
    }

    @Bean
    public JwtAuthenticationConverter jwtAuthenticationConverter() {
        JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
        converter.setJwtGrantedAuthoritiesConverter(new JwtGrantedAuthoritiesConverter());
        converter.setPrincipalClaimName("sub");
        return converter;
    }
}
java 复制代码
package com.example.infrastructure.security;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.lang.NonNull;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.Collection;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private final JwtDecoder jwtDecoder;

    public JwtAuthenticationFilter(JwtDecoder jwtDecoder) {
        this.jwtDecoder = jwtDecoder;
    }

    @Override
    protected void doFilterInternal(
            @NonNull HttpServletRequest request,
            @NonNull HttpServletResponse response,
            @NonNull FilterChain filterChain) throws ServletException, IOException {
        
        String header = request.getHeader("Authorization");
        if (header != null && header.startsWith("Bearer ")) {
            String token = header.substring(7);
            try {
                Jwt jwt = jwtDecoder.decode(token);
                Collection<SimpleGrantedAuthority> authorities = extractAuthorities(jwt);
                UsernamePasswordAuthenticationToken authentication =
                    new UsernamePasswordAuthenticationToken(jwt.getSubject(), null, authorities);
                SecurityContextHolder.getContext().setAuthentication(authentication);
            } catch (Exception e) {
                response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid token");
                return;
            }
        }
        filterChain.doFilter(request, response);
    }

    @SuppressWarnings("unchecked")
    private Collection<SimpleGrantedAuthority> extractAuthorities(Jwt jwt) {
        Object realmAccess = jwt.getClaim("realm_access");
        if (realmAccess instanceof Map) {
            Collection<String> roles = ((Map<String, Object>) realmAccess).get("roles");
            if (roles instanceof Collection) {
                return ((Collection<String>) roles).stream()
                    .map(role -> new SimpleGrantedAuthority("ROLE_" + role))
                    .collect(Collectors.toList());
            }
        }
        return Stream.empty().collect(Collectors.toList());
    }
}

Spring Security 6.x 全面采用函数式 DSL 替代了基于 XML 的配置方式。@EnableMethodSecurity 启用基于注解的方法级权限控制,使 @PreAuthorize 和 @Secured 注解能够正常工作。资源服务器配置同时支持 JWT 自包含模式和 Opaque Token 外省模式,后者适用于需要服务端验证令牌状态的场景。

5. 事件驱动与消息集成

5.1 Spring Cloud Stream Binder 配置

事件驱动架构将服务间耦合从同步调用转化为异步消息传递。Spring Cloud Stream 抽象了底层消息中间件的差异,通过 Binder 模式提供统一的编程模型。Kafka 和 RabbitMQ 均被官方 Binder 支持,开发者只需声明功能接口和绑定配置,无需感知具体的传输协议细节。

java 复制代码
package com.example.infrastructure.messaging;

import org.springframework.cloud.stream.function.StreamBridge;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.support.MessageBuilder;

import java.util.function.Consumer;
import java.util.function.Supplier;

@Configuration
public class EventStreamConfig {

    @Bean
    public Consumer<OrderCreatedEvent> orderCreatedProcessor() {
        return event -> {
            // 处理订单创建事件的领域逻辑
        };
    }

    @Bean
    public Supplier<InventoryUpdatedEvent> inventoryUpdateSupplier() {
        return () -> MessageBuilder
            .withPayload(new InventoryUpdatedEvent(generateEvent()))
            .setHeader("x-correlation-id", java.util.UUID.randomUUID().toString())
            .build();
    }

    @Bean
    public Consumer<UserRegisteredEvent> userRegisteredListener() {
        return event -> {
            // 处理用户注册事件
        };
    }
}
java 复制代码
package com.example.infrastructure.messaging;

import org.springframework.cloud.stream.function.StreamBridge;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;

@Service
public class EventPublisher {

    private final StreamBridge streamBridge;

    public EventPublisher(StreamBridge streamBridge) {
        this.streamBridge = streamBridge;
    }

    public void publishOrderEvent(OrderEvent event) {
        boolean sent = streamBridge.send("orders-out-0", 
            MessageBuilder
                .withPayload(event)
                .setHeader("spring.cloud.stream.partitionKey", event.getOrderId().toString())
                .build());
        if (!sent) {
            throw new RuntimeException("Failed to publish order event");
        }
    }

    public void publishWithRetry(OrderEvent event, int maxAttempts) {
        for (int attempt = 1; attempt <= maxAttempts; attempt++) {
            try {
                publishOrderEvent(event);
                return;
            } catch (Exception e) {
                if (attempt == maxAttempts) {
                    throw e;
                }
            }
        }
    }
}
yaml 复制代码
# application.yml
spring:
  cloud:
    stream:
      function:
        definition: orderCreatedProcessor;inventoryUpdateSupplier;userRegisteredListener
      bindings:
        orderCreatedProcessor-in-0:
          destination: orders-topic
          group: order-processors
        inventoryUpdateSupplier-out-0:
          destination: inventory-topic
        userRegisteredListener-in-0:
          destination: users-topic
          group: user-listeners
      binders:
        kafka:
          type: kafka
          default-auto-register: true
          environment:
            spring:
              cloud:
                stream:
                  kafka:
                    binder:
                      brokers: kafka:9092
                      configuration:
                        auto.offset.reset: earliest
                    bindings:
                      orderCreatedProcessor-in-0:
                        consumer:
                          maxPollRecords: 50
                          enable.auto.commit: false

Spring Cloud Stream 的 Binder 抽象层屏蔽了 Kafka 分区策略、RabbitMQ 交换机类型等细节。通过 destination 和 group 配置分别控制发布目标和消费组语义,消费者组内的实例自动负载均衡,不同组则独立消费全量消息,这一机制天然支持事件溯源和审计日志的并行消费场景。

6. 链路追踪与可观测性

6.1 指标收集体系

Micrometer 作为 Spring Boot 3 默认的指标门面,提供了与 Prometheus、Datadog、InfluxDB 等监控后端兼容的度量收集能力。通过自动配置的 MeterRegistry,框架拦截 HTTP 请求、JVM 内存、线程池等关键指标并暴露为 OpenMetrics 格式端点。生产环境通常需要叠加业务自定义指标以覆盖特定领域的关键成功指标。

6.2 分布式追踪集成

OpenTelemetry 已成为云原生可观测性的事实标准。Spring Boot 3 通过 micrometer-tracing-bridge-otel 实现零配置集成,自动为 HTTP 客户端、数据库查询和消息队列操作生成追踪跨度。每个请求携带 Trace ID 和 Span ID,这些标识符通过 MDC 传递至日志上下文,实现日志、指标、追踪三张数据曲线的关联分析。

groovy 复制代码
// build.gradle.kts(依赖声明)
dependencies {
    implementation("org.springframework.boot:spring-boot-starter-actuator")
    implementation("io.micrometer:micrometer-registry-prometheus")
    implementation("io.micrometer:micrometer-tracing-bridge-otel")
    implementation("io.opentelemetry:opentelemetry-exporter-zipkin")
    runtimeOnly("io.opentelemetry.javaagent:opentelemetry-javaagent:1.32.0")
}
yaml 复制代码
# application.yml
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus
  metrics:
    export:
      prometheus:
        enabled: true
      otlp:
        enabled: true
        endpoint: http://otel-collector:4318/v1/metrics
        headers:
          authorization: Basic ${OTEL_AUTH_TOKEN}
  tracing:
    sampling:
      probability: 0.1
  observations:
    key-values:
      environment: production
      service: user-service

opentelemetry:
  resource-attributes:
    service.name: user-service
    service.version: 1.0.0
    deployment.environment: production

采样率设置为 0.1 意味着仅 10% 的请求生成完整追踪数据,这一配置在保持可观测性的同时控制存储成本。对于关键路径或错误追踪,可通过 TraceSampler 自定义实现覆盖全局采样策略,例如对耗时超过阈值的请求强制记录完整跨度。

7. 总结:演进路线与建议

7.1 重构路径

从单体架构向微服务演进时,建议遵循"识别边界、剥离模块、独立部署"的三阶段策略。首先通过领域驱动设计的战略设计识别 bounded context,将业务概念映射为服务边界;其次在单仓内按模块拆分代码,通过接口隔离降低初始耦合度;最后在基础设施层面引入服务网格和 API 网关,实现跨服务的治理能力建设。

7.2 最佳实践回顾

本文涵盖的技术要点包括:使用 Gradle 多模块和 JPMS 建立清晰的编译期依赖边界;根据访问模式选择 JPA 或 R2DBC 的数据访问策略;基于 OAuth 2.0 / OIDC 的标准安全认证体系;借助 Spring Cloud Stream 实现解耦的事件驱动通信;以及通过 Micrometer 和 OpenTelemetry 构建统一的三支柱可观测性体系。

Spring Boot 3 的核心价值在于将云原生能力内建于框架层,开发者无需在多个独立组件间手动集成。虚拟线程、原生镜像、响应式编程三大特性共同构成了现代微服务的基础设施底座。工程实践中应当根据业务特征选择合适的技术组合,避免过度设计导致架构复杂度过载。

相关推荐
香蕉大香蕉(06敬畏市场)1 小时前
高频八股 · JVM(7):方法区、元空间与字符串常量池
java·jvm·ai·博客·八股·大厂实习
zzu123zsw1 小时前
从 SPI 契约到独立 ClassLoader 隔离:Spring Boot 插件热插拔架构设计与内存泄露防御
java·spring boot·后端·架构设计
文丑颜不良啊1 小时前
LeetCode1161~1180题目解析
java·leetcode
古法老登瞎折腾9 小时前
大数据智能运维实战(十一):运维 MCP 的工程化——权限、限流、审计与错误处理
java·大数据·运维·spring ai·mcp
智能运维指南10 小时前
研制过程“两张皮“怎么破:研制一体化支撑平台的定位、能力边界与落地前提
java·前端·人工智能·嘉为蓝鲸
yz_aiks11 小时前
Spring AI 2.1 ChatMemory 已过时用什么替代:迁移到 Session Memory 实战(JDBC 存储与会话压缩)
java·spring boot·大模型·ai agent·spring ai
金銀銅鐵11 小时前
[Java] 用 GUI 展示 EnumMap 增删键值对时的内部变化
java·后端·ai编程
学长毕设11 小时前
基于SpringBoot的上门洗车服务系统设计与实现
java·spring boot·后端
jason成都11 小时前
RTKLIB-Java 多基线联合RTK:FIX率从0%到91.5%的三层根因修复
java·gnss·rtklib·rtklib_java