Spring Boot 3 微服务架构实战:从零构建企业级分布式系统

Spring Boot 3 微服务架构实战:从零构建企业级分布式系统

摘要

本文深入讲解如何使用 Spring Boot 3 构建企业级微服务架构。内容涵盖 Spring Boot 3 新特性、微服务设计原则、服务拆分策略、API 网关配置、服务发现与注册、分布式配置管理、链路追踪等核心主题。文章提供完整的 Java 代码示例,所有代码基于 Spring Boot 3.2+ 和 JDK 17+,可直接运行。通过本文,读者将掌握从零构建微服务系统的完整流程,包括项目搭建、服务拆分、通信机制、数据一致性处理等实战技巧。文章包含系统架构图、部署架构图和序列图,帮助读者独立完成企业级微服务架构的设计与实施。

关键词:Spring Boot 3、微服务架构、Java 17、分布式系统、Spring Cloud


一、引言:为什么选择 Spring Boot 3

1.1 Spring Boot 3 的重大升级

Spring Boot 3 于 2022 年 11 月发布,带来了多项重大升级:

  • JDK 17+ 要求:最低支持 JDK 17,充分利用 Java 17 新特性
  • Jakarta EE 9+:从 javax.* 迁移到 jakarta.* 命名空间
  • AOT 编译支持:原生镜像编译,启动速度提升 10 倍
  • 改进的 Observability:内置 Micrometer Tracing,替代 Spring Cloud Sleuth
  • Kotlin 1.7+ 支持:更好的 Kotlin 协程支持

1.2 微服务架构的核心挑战

构建微服务系统面临以下挑战:

  1. 服务拆分:如何合理划分服务边界
  2. 服务通信:同步 vs 异步通信选择
  3. 数据一致性:分布式事务处理
  4. 服务发现:动态服务注册与发现
  5. 配置管理:集中化配置管理
  6. 链路追踪:分布式系统问题定位

本文将逐一解决这些问题。

1.3 本文目标

通过本文,你将:

  • 掌握 Spring Boot 3 核心新特性
  • 学会微服务拆分策略
  • 构建完整的服务通信机制
  • 实现分布式配置和链路追踪
  • 获得可直接运行的代码示例

二、Spring Boot 3 核心新特性

2.1 JDK 17 特性利用

Spring Boot 3 充分利用 Java 17 的新特性:

Record 类简化 DTO

java 复制代码
// Java 17 Record - 不可变数据载体
public record UserDTO(
    Long id,
    String username,
    String email,
    LocalDateTime createdAt
) {}

Switch 表达式简化逻辑

java 复制代码
public String getOrderStatus(Order order) {
    return switch (order.status()) {
        case PENDING -> "待处理";
        case PROCESSING -> "处理中";
        case SHIPPED -> "已发货";
        case DELIVERED -> "已送达";
        case CANCELLED -> "已取消";
    };
}

Pattern Matching 简化类型检查

java 复制代码
public String process(Object obj) {
    if (obj instanceof String str) {
        return "String: " + str.toUpperCase();
    } else if (obj instanceof Integer num) {
        return "Number: " + (num * 2);
    }
    return "Unknown";
}

2.2 Jakarta EE 9+ 迁移

所有 javax.* 包迁移到 jakarta.*:

java 复制代码
// Spring Boot 2.x (旧)
import javax.persistence.Entity;
import javax.validation.constraints.NotNull;

// Spring Boot 3.x (新)
import jakarta.persistence.Entity;
import jakarta.validation.constraints.NotNull;

2.3 新的依赖配置

xml 复制代码
<!-- pom.xml -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.2.4</version>
    <relativePath/>
</parent>

<dependencies>
    <!-- Web 服务 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <!-- 数据访问 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    
    <!-- 服务发现 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    
    <!-- 配置中心 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-config</artifactId>
    </dependency>
    
    <!-- 链路追踪 -->
    <dependency>
        <groupId>io.micrometer</groupId>
        <artifactId>micrometer-tracing-bridge-brave</artifactId>
    </dependency>
</dependencies>

三、微服务拆分策略

3.1 领域驱动设计 (DDD) 原则

合理的微服务拆分基于领域驱动设计:

复制代码
┌─────────────────────────────────────────────────────────┐
│                    电商平台微服务架构                     │
├─────────────┬─────────────┬─────────────┬──────────────┤
│  用户服务    │  商品服务    │  订单服务    │  支付服务    │
│  (User)     │  (Product)  │  (Order)    │  (Payment)   │
│  - 注册登录  │  - 商品管理  │  - 订单创建  │  - 支付处理  │
│  - 用户信息  │  - 库存管理  │  - 订单查询  │  - 退款处理  │
│  - 权限管理  │  - 分类管理  │  - 订单状态  │  - 对账管理  │
└─────────────┴─────────────┴─────────────┴──────────────┘

3.2 服务拆分示例

用户服务 (user-service)

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

import jakarta.persistence.*;
import java.time.LocalDateTime;

@Entity
@Table(name = "users")
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false, unique = true)
    private String username;
    
    @Column(nullable = false)
    private String password;
    
    @Column(nullable = false, unique = true)
    private String email;
    
    @Column(name = "created_at")
    private LocalDateTime createdAt;
    
    // 构造函数
    public User() {
        this.createdAt = LocalDateTime.now();
    }
    
    // Getters and Setters
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    
    public String getUsername() { return username; }
    public void setUsername(String username) { this.username = username; }
    
    public String getPassword() { return password; }
    public void setPassword(String password) { this.password = password; }
    
    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
    
    public LocalDateTime getCreatedAt() { return createdAt; }
    public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
}

用户服务 Repository

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

import com.example.user.domain.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.Optional;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    
    Optional<User> findByUsername(String username);
    
    Optional<User> findByEmail(String email);
    
    boolean existsByUsername(String username);
    
    boolean existsByEmail(String email);
}

用户服务 Controller

java 复制代码
package com.example.user.controller;

import com.example.user.domain.User;
import com.example.user.dto.UserDTO;
import com.example.user.service.UserService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api/users")
public class UserController {
    
    private final UserService userService;
    
    public UserController(UserService userService) {
        this.userService = userService;
    }
    
    @PostMapping
    public ResponseEntity<UserDTO> createUser(@RequestBody CreateUserRequest request) {
        UserDTO user = userService.createUser(request);
        return ResponseEntity.ok(user);
    }
    
    @GetMapping("/{id}")
    public ResponseEntity<UserDTO> getUser(@PathVariable Long id) {
        return userService.getUserById(id)
                .map(ResponseEntity::ok)
                .orElse(ResponseEntity.notFound().build());
    }
    
    @GetMapping
    public ResponseEntity<List<UserDTO>> getAllUsers() {
        return ResponseEntity.ok(userService.getAllUsers());
    }
    
    @PutMapping("/{id}")
    public ResponseEntity<UserDTO> updateUser(
            @PathVariable Long id,
            @RequestBody UpdateUserRequest request) {
        return ResponseEntity.ok(userService.updateUser(id, request));
    }
    
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
        userService.deleteUser(id);
        return ResponseEntity.noContent().build();
    }
}

3.3 服务间通信

使用 RestTemplate 进行同步调用

java 复制代码
package com.example.order.client;

import com.example.order.dto.UserDTO;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

@Component
public class UserClient {
    
    private final RestTemplate restTemplate;
    private static final String USER_SERVICE_URL = "http://user-service/api/users";
    
    public UserClient(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }
    
    public UserDTO getUserById(Long userId) {
        return restTemplate.getForObject(
            USER_SERVICE_URL + "/" + userId,
            UserDTO.class
        );
    }
}

使用 Feign 进行声明式调用(推荐)

java 复制代码
package com.example.order.client;

import com.example.order.dto.UserDTO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@FeignClient(name = "user-service")
public interface UserClient {
    
    @GetMapping("/api/users/{id}")
    UserDTO getUserById(@PathVariable("id") Long userId);
}

配置 Feign Client

java 复制代码
package com.example.order.config;

import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableFeignClients(basePackages = "com.example.order.client")
public class FeignConfig {
    // Feign 客户端配置
}

四、服务发现与注册

4.1 Eureka Server 配置

启动类

java 复制代码
package com.example.eureka;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
    
    public static void main(String[] args) {
        SpringApplication.run(EurekaServerApplication.class, args);
    }
}

application.yml

yaml 复制代码
server:
  port: 8761

eureka:
  instance:
    hostname: localhost
  client:
    register-with-eureka: false
    fetch-registry: false
    service-url:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

4.2 服务注册配置

user-service application.yml

yaml 复制代码
server:
  port: 8081

spring:
  application:
    name: user-service

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
    register-with-eureka: true
    fetch-registry: true
  instance:
    prefer-ip-address: true

五、API 网关配置

5.1 Spring Cloud Gateway

启动类

java 复制代码
package com.example.gateway;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class GatewayApplication {
    
    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class, args);
    }
}

application.yml

yaml 复制代码
server:
  port: 8080

spring:
  application:
    name: api-gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
          lower-case-service-id: true
      routes:
        - id: user-service
          uri: lb://user-service
          predicates:
            - Path=/api/users/**
        - id: order-service
          uri: lb://order-service
          predicates:
            - Path=/api/orders/**
        - id: product-service
          uri: lb://product-service
          predicates:
            - Path=/api/products/**

六、分布式配置管理

6.1 Config Server 配置

启动类

java 复制代码
package com.example.config;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;

@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
    
    public static void main(String[] args) {
        SpringApplication.run(ConfigServerApplication.class, args);
    }
}

application.yml

yaml 复制代码
server:
  port: 8888

spring:
  application:
    name: config-server
  cloud:
    config:
      server:
        git:
          uri: https://github.com/your-org/config-repo
          default-label: main
          clone-on-start: true

6.2 客户端配置

bootstrap.yml

yaml 复制代码
spring:
  application:
    name: user-service
  cloud:
    config:
      uri: http://localhost:8888
      fail-fast: true
      retry:
        initial-interval: 1000
        max-interval: 2000
        max-attempts: 3

七、链路追踪与监控

7.1 Micrometer Tracing 配置

添加依赖

xml 复制代码
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>
<dependency>
    <groupId>io.zipkin.reporter2</groupId>
    <artifactId>zipkin-reporter-brave</artifactId>
</dependency>

application.yml

yaml 复制代码
management:
  tracing:
    sampling:
      probability: 1.0
  zipkin:
    tracing:
      endpoint: http://localhost:9411/api/v2/spans

7.2 自定义链路信息

java 复制代码
package com.example.user.config;

import io.micrometer.tracing.Span;
import io.micrometer.tracing.Tracer;
import org.springframework.stereotype.Component;

@Component
public class TracingConfig {
    
    private final Tracer tracer;
    
    public TracingConfig(Tracer tracer) {
        this.tracer = tracer;
    }
    
    public void addCustomTag(String key, String value) {
        Span currentSpan = tracer.currentSpan();
        if (currentSpan != null) {
            currentSpan.tag(key, value);
        }
    }
}

八、数据一致性处理

8.1 Saga 模式实现分布式事务

java 复制代码
package com.example.order.service;

import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class OrderService {
    
    private final OrderRepository orderRepository;
    private final InventoryClient inventoryClient;
    private final PaymentClient paymentClient;
    
    @GlobalTransactional
    @Transactional
    public Order createOrder(CreateOrderRequest request) {
        // 1. 创建订单
        Order order = orderRepository.save(request.toOrder());
        
        // 2. 扣减库存
        inventoryClient.deductStock(request.getProductId(), request.getQuantity());
        
        // 3. 扣款
        paymentClient.deductBalance(request.getUserId(), request.getAmount());
        
        return order;
    }
}

九、部署架构

9.1 Docker 容器化部署

Dockerfile

dockerfile 复制代码
# 构建阶段
FROM maven:3.9-eclipse-temurin-17 AS build
WORKDIR /app
COPY pom.xml .
COPY src ./src
RUN mvn clean package -DskipTests

# 运行阶段
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
COPY --from=build /app/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]

docker-compose.yml

yaml 复制代码
version: '3.8'

services:
  eureka-server:
    build: ./eureka-server
    ports:
      - "8761:8761"
  
  config-server:
    build: ./config-server
    ports:
      - "8888:8888"
    depends_on:
      - eureka-server
  
  api-gateway:
    build: ./api-gateway
    ports:
      - "8080:8080"
    depends_on:
      - eureka-server
      - config-server
  
  user-service:
    build: ./user-service
    ports:
      - "8081:8081"
    depends_on:
      - eureka-server
      - config-server
    environment:
      - SPRING_PROFILES_ACTIVE=docker
  
  order-service:
    build: ./order-service
    ports:
      - "8082:8082"
    depends_on:
      - eureka-server
      - config-server
    environment:
      - SPRING_PROFILES_ACTIVE=docker

十、最佳实践与性能优化

10.1 连接池配置优化

HikariCP 配置

yaml 复制代码
spring:
  datasource:
    hikari:
      maximum-pool-size: 20
      minimum-idle: 5
      idle-timeout: 300000
      connection-timeout: 20000
      max-lifetime: 1200000
      pool-name: UserDBPool

10.2 缓存策略

Redis 缓存配置

java 复制代码
package com.example.user.config;

import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;

@Configuration
@EnableCaching
public class CacheConfig {
    
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        return template;
    }
    
    @Bean
    public RedisCacheConfiguration cacheConfiguration() {
        return RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofMinutes(30))
                .serializeKeysWith(RedisSerializationContext.SerializationPair
                        .fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair
                        .fromSerializer(new GenericJackson2JsonRedisSerializer()));
    }
}

使用缓存

java 复制代码
package com.example.user.service;

import com.example.user.domain.User;
import com.example.user.repository.UserRepository;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.stereotype.Service;

@Service
public class UserService {
    
    private final UserRepository userRepository;
    
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    
    @Cacheable(value = "users", key = "#id")
    public User getUserById(Long id) {
        return userRepository.findById(id).orElse(null);
    }
    
    @CacheEvict(value = "users", key = "#user.id")
    public User updateUser(User user) {
        return userRepository.save(user);
    }
    
    @CacheEvict(value = "users", allEntries = true)
    public void deleteUser(Long id) {
        userRepository.deleteById(id);
    }
}

10.3 异步处理

@Async 异步方法

java 复制代码
package com.example.order.service;

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

@Service
public class NotificationService {
    
    @Async
    public void sendOrderConfirmation(Long orderId) {
        // 异步发送订单确认邮件
        // 不会阻塞主线程
        try {
            Thread.sleep(1000); // 模拟邮件发送
            System.out.println("订单确认邮件已发送:" + orderId);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
    
    @Async
    public void sendSmsNotification(Long userId, String message) {
        // 异步发送短信通知
        System.out.println("短信已发送:" + message);
    }
}

配置异步线程池

java 复制代码
package com.example.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;

@Configuration
@EnableAsync
public class AsyncConfig {
    
    @Bean(name = "taskExecutor")
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(20);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("async-");
        executor.initialize();
        return executor;
    }
}

10.4 限流与熔断

Resilience4j 配置

xml 复制代码
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
yaml 复制代码
resilience4j:
  circuitbreaker:
    instances:
      userService:
        registerHealthIndicator: true
        slidingWindowSize: 10
        minimumNumberOfCalls: 5
        permittedNumberOfCallsInHalfOpenState: 3
        automaticTransitionFromOpenToHalfOpenEnabled: true
        waitDurationInOpenState: 10s
        failureRateThreshold: 50
        eventConsumerBufferSize: 10
  ratelimiter:
    instances:
      userService:
        limitForPeriod: 10
        limitRefreshPeriod: 1s
        timeoutDuration: 0s

使用熔断器

java 复制代码
package com.example.order.service;

import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import io.github.resilience4j.ratelimiter.annotation.RateLimiter;
import org.springframework.stereotype.Service;

@Service
public class OrderService {
    
    @CircuitBreaker(name = "userService", fallbackMethod = "getUserFallback")
    @RateLimiter(name = "userService")
    public UserDTO getUserById(Long userId) {
        return userClient.getUserById(userId);
    }
    
    public UserDTO getUserFallback(Long userId, Exception ex) {
        // 熔断降级处理
        return new UserDTO(userId, "未知用户", "unknown@example.com", null);
    }
}

十一、安全配置

11.1 Spring Security 6 配置

添加依赖

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>

安全配置类

java 复制代码
package com.example.user.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
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;

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf.disable())
            .sessionManagement(session -> 
                session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/users/register", "/api/users/login").permitAll()
                .requestMatchers("/api/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
            )
            .oauth2ResourceServer(oauth2 -> oauth2
                .jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter()))
            );
        
        return http.build();
    }
    
    @Bean
    public JwtAuthenticationConverter jwtAuthenticationConverter() {
        JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
        grantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");
        grantedAuthoritiesConverter.setAuthoritiesClaimName("roles");
        
        JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
        jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
        return jwtAuthenticationConverter;
    }
}

11.2 JWT 工具类

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

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

@Component
public class JwtUtil {
    
    @Value("${jwt.secret}")
    private String secret;
    
    @Value("${jwt.expiration}")
    private Long expiration;
    
    private Key getSigningKey() {
        byte[] keyBytes = secret.getBytes();
        return Keys.hmacShaKeyFor(keyBytes);
    }
    
    public String extractUsername(String token) {
        return extractClaim(token, Claims::getSubject);
    }
    
    public Date extractExpiration(String token) {
        return extractClaim(token, Claims::getExpiration);
    }
    
    public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
        final Claims claims = extractAllClaims(token);
        return claimsResolver.apply(claims);
    }
    
    private Claims extractAllClaims(String token) {
        return Jwts.parserBuilder()
                .setSigningKey(getSigningKey())
                .build()
                .parseClaimsJws(token)
                .getBody();
    }
    
    private Boolean isTokenExpired(String token) {
        return extractExpiration(token).before(new Date());
    }
    
    public String generateToken(String username, Map<String, Object> claims) {
        return createToken(claims, username);
    }
    
    private String createToken(Map<String, Object> claims, String subject) {
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(subject)
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .setExpiration(new Date(System.currentTimeMillis() + expiration))
                .signWith(getSigningKey(), SignatureAlgorithm.HS256)
                .compact();
    }
    
    public Boolean validateToken(String token, String username) {
        final String extractedUsername = extractUsername(token);
        return (extractedUsername.equals(username) && !isTokenExpired(token));
    }
}

十二、测试策略

12.1 单元测试

java 复制代码
package com.example.user.service;

import com.example.user.domain.User;
import com.example.user.repository.UserRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.util.Optional;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
class UserServiceTest {
    
    @Mock
    private UserRepository userRepository;
    
    @InjectMocks
    private UserService userService;
    
    private User testUser;
    
    @BeforeEach
    void setUp() {
        testUser = new User();
        testUser.setId(1L);
        testUser.setUsername("testuser");
        testUser.setEmail("test@example.com");
        testUser.setPassword("hashedPassword");
    }
    
    @Test
    void testCreateUser() {
        when(userRepository.save(any(User.class))).thenReturn(testUser);
        
        User result = userService.createUser(testUser);
        
        assertNotNull(result);
        assertEquals("testuser", result.getUsername());
        verify(userRepository, times(1)).save(any(User.class));
    }
    
    @Test
    void testGetUserById() {
        when(userRepository.findById(1L)).thenReturn(Optional.of(testUser));
        
        Optional<User> result = userService.getUserById(1L);
        
        assertTrue(result.isPresent());
        assertEquals("testuser", result.get().getUsername());
    }
    
    @Test
    void testGetUserByIdNotFound() {
        when(userRepository.findById(99L)).thenReturn(Optional.empty());
        
        Optional<User> result = userService.getUserById(99L);
        
        assertFalse(result.isPresent());
    }
}

12.2 集成测试

java 复制代码
package com.example.user;

import com.example.user.domain.User;
import com.example.user.repository.UserRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.context.ActiveProfiles;

import java.util.Optional;

import static org.assertj.core.api.Assertions.assertThat;

@DataJpaTest
@ActiveProfiles("test")
class UserRepositoryIntegrationTest {
    
    @Autowired
    private UserRepository userRepository;
    
    @Test
    void testFindByUsername() {
        User user = new User();
        user.setUsername("integration_test");
        user.setEmail("test@example.com");
        user.setPassword("password");
        
        userRepository.save(user);
        
        Optional<User> found = userRepository.findByUsername("integration_test");
        
        assertThat(found).isPresent();
        assertThat(found.get().getEmail()).isEqualTo("test@example.com");
    }
    
    @Test
    void testExistsByUsername() {
        User user = new User();
        user.setUsername("exists_test");
        user.setEmail("test@example.com");
        user.setPassword("password");
        
        userRepository.save(user);
        
        boolean exists = userRepository.existsByUsername("exists_test");
        
        assertThat(exists).isTrue();
    }
}

12.3 控制器测试

java 复制代码
package com.example.user.controller;

import com.example.user.dto.CreateUserRequest;
import com.example.user.dto.UserDTO;
import com.example.user.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@WebMvcTest(UserController.class)
class UserControllerTest {
    
    @Autowired
    private MockMvc mockMvc;
    
    @MockBean
    private UserService userService;
    
    @Test
    void testCreateUser() throws Exception {
        CreateUserRequest request = new CreateUserRequest("testuser", "password", "test@example.com");
        UserDTO response = new UserDTO(1L, "testuser", "test@example.com", null);
        
        when(userService.createUser(any(CreateUserRequest.class))).thenReturn(response);
        
        mockMvc.perform(post("/api/users")
                .contentType(MediaType.APPLICATION_JSON)
                .content("{\"username\":\"testuser\",\"password\":\"password\",\"email\":\"test@example.com\"}"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.id").value(1))
                .andExpect(jsonPath("$.username").value("testuser"));
    }
}

十三、常见问题与解决方案

13.1 循环依赖问题

问题:服务 A 依赖服务 B,服务 B 又依赖服务 A

解决方案

  1. 使用 @Lazy 注解
  2. 重构代码,提取公共逻辑到第三个服务
  3. 使用事件驱动架构解耦
java 复制代码
@Service
public class ServiceA {
    
    private final ServiceB serviceB;
    
    public ServiceA(@Lazy ServiceB serviceB) {
        this.serviceB = serviceB;
    }
}

13.2 数据库连接泄漏

问题:连接池耗尽,应用无法获取新连接

解决方案

  1. 配置合理的连接池参数
  2. 启用连接泄漏检测
  3. 确保所有数据库操作在 try-with-resources 中执行
yaml 复制代码
spring:
  datasource:
    hikari:
      leak-detection-threshold: 60000  # 60 秒检测泄漏

13.3 内存泄漏

问题:应用运行一段时间后 OOM

解决方案

  1. 使用 JVM 参数限制堆内存
  2. 分析 Heap Dump 找出泄漏点
  3. 避免静态集合无限增长
bash 复制代码
java -Xms512m -Xmx1g -jar app.jar

十、总结

  1. Spring Boot 3 新特性:JDK 17、Jakarta EE、AOT 编译
  2. 服务拆分:基于 DDD 原则合理划分服务边界
  3. 服务通信:RestTemplate 和 Feign 客户端
  4. 服务发现:Eureka 注册中心配置
  5. API 网关:Spring Cloud Gateway 统一入口
  6. 配置管理:Config Server 集中化配置
  7. 链路追踪:Micrometer Tracing 分布式追踪
  8. 数据一致性:Saga 模式处理分布式事务
  9. 容器化部署:Docker + Docker Compose

所有代码示例均基于 Spring Boot 3.2+ 和 JDK 17+,可直接运行。


版权声明:本文内容基于真实技术热点原创撰写,参考多个来源后独立重新表述。文中示例代码可自由使用于学习和个人项目。转载或引用请注明出处。

作者:超人不会飞

相关推荐
蓝眸少年CY1 天前
(第十五篇)spring cloud之Sentinel实现熔断与限流
数据库·spring cloud·sentinel
huipeng9261 天前
GateWay使用详解
java·spring boot·spring cloud·微服务·gateway
huipeng9261 天前
分布式服务部署详解
java·开发语言·spring cloud·微服务
.柒宇.2 天前
SpringCloud微服务入门教程
spring·spring cloud·微服务
.生产的驴2 天前
SpringBoot 大文件分片上传 文件切片、断点续传与性能优化 切片技术与优化方案 文件高效上传
java·服务器·spring boot·后端·spring·spring cloud·状态模式
xiaogg36783 天前
springcloud oauth2 自定义token实现
spring boot·后端·spring cloud
大龄码农-涵哥3 天前
Spring Cloud微服务架构详解:从服务注册到配置中心,阿里面试核心知识点
spring cloud·微服务·架构
下地种菜小叶3 天前
特征定义、特征计算、特征服务怎么配合?一次讲透
java·服务器·前端·数据库·spring cloud
Devin~Y3 天前
大厂Java面试实战:Spring Boot + Redis + Kafka + Kubernetes + RAG 的三轮追问(附答案解析)
java·spring boot·redis·spring cloud·kafka·kubernetes·resilience4j
腾飞开源3 天前
06_系统架构设计
微服务架构·智能决策·langgraph·deepseek·智能体开发·fastmcp·langsmith