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 微服务架构的核心挑战
构建微服务系统面临以下挑战:
- 服务拆分:如何合理划分服务边界
- 服务通信:同步 vs 异步通信选择
- 数据一致性:分布式事务处理
- 服务发现:动态服务注册与发现
- 配置管理:集中化配置管理
- 链路追踪:分布式系统问题定位
本文将逐一解决这些问题。
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
解决方案:
- 使用
@Lazy注解 - 重构代码,提取公共逻辑到第三个服务
- 使用事件驱动架构解耦
java
@Service
public class ServiceA {
private final ServiceB serviceB;
public ServiceA(@Lazy ServiceB serviceB) {
this.serviceB = serviceB;
}
}
13.2 数据库连接泄漏
问题:连接池耗尽,应用无法获取新连接
解决方案:
- 配置合理的连接池参数
- 启用连接泄漏检测
- 确保所有数据库操作在 try-with-resources 中执行
yaml
spring:
datasource:
hikari:
leak-detection-threshold: 60000 # 60 秒检测泄漏
13.3 内存泄漏
问题:应用运行一段时间后 OOM
解决方案:
- 使用 JVM 参数限制堆内存
- 分析 Heap Dump 找出泄漏点
- 避免静态集合无限增长
bash
java -Xms512m -Xmx1g -jar app.jar
十、总结
- Spring Boot 3 新特性:JDK 17、Jakarta EE、AOT 编译
- 服务拆分:基于 DDD 原则合理划分服务边界
- 服务通信:RestTemplate 和 Feign 客户端
- 服务发现:Eureka 注册中心配置
- API 网关:Spring Cloud Gateway 统一入口
- 配置管理:Config Server 集中化配置
- 链路追踪:Micrometer Tracing 分布式追踪
- 数据一致性:Saga 模式处理分布式事务
- 容器化部署:Docker + Docker Compose
所有代码示例均基于 Spring Boot 3.2+ 和 JDK 17+,可直接运行。
版权声明:本文内容基于真实技术热点原创撰写,参考多个来源后独立重新表述。文中示例代码可自由使用于学习和个人项目。转载或引用请注明出处。
作者:超人不会飞