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+,可直接运行。


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

作者:超人不会飞

相关推荐
鬼先生_sir2 小时前
SpringCloud-openFeign(服务调用)
后端·spring·spring cloud
Devin~Y2 小时前
大厂 Java 面试实战:从电商微服务到 AI 智能客服(含 Spring 全家桶、Redis、Kafka、RAG/Agent 解析)
java·spring boot·redis·elasticsearch·spring cloud·docker·kafka
大数据新鸟3 小时前
微服务之Spring Cloud OpenFeign
spring cloud·微服务·架构
大数据新鸟3 小时前
微服务之Spring Cloud LoadBalancer
java·spring cloud·微服务
星辰_mya3 小时前
深度全面学习负载均衡Ribbon/Spring Cloud LoadBalancer
后端·spring cloud·面试·负载均衡·架构师
杰克尼15 小时前
springCloud_day07(MQ高级)
java·spring·spring cloud
杰克尼17 小时前
SpringCloud_day05
后端·spring·spring cloud
鬼先生_sir19 小时前
SpringCloud-GateWay网关
java·spring cloud·gateway
杰克尼1 天前
springCloud(day09-Elasticsearch02)
java·后端·spring·spring cloud