本章目标

  • 深入理解Spring Boot自动配置原理
  • 学会开发自定义Starter
  • 掌握Spring Boot测试最佳实践
  • 了解生产环境部署策略
  • 学习性能调优技巧

1. Spring Boot自动配置原理

1.1 自动配置机制

// 自动配置示例
@Configuration
@ConditionalOnClass(DataSource.class)
@ConditionalOnMissingBean(DataSource.class)
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceAutoConfiguration {
    
    @Bean
    @Primary
    public DataSource dataSource(DataSourceProperties properties) {
        return DataSourceBuilder.create()
                .driverClassName(properties.getDriverClassName())
                .url(properties.getUrl())
                .username(properties.getUsername())
                .password(properties.getPassword())
                .build();
    }
}

// 条件注解示例
@Configuration
public class ConditionalConfiguration {
    
    @Bean
    @ConditionalOnProperty(name = "app.feature.enabled", havingValue = "true")
    public FeatureService featureService() {
        return new FeatureServiceImpl();
    }
    
    @Bean
    @ConditionalOnMissingBean(CacheManager.class)
    public CacheManager cacheManager() {
        return new ConcurrentMapCacheManager();
    }
    
    @Bean
    @ConditionalOnClass(name = "redis.clients.jedis.Jedis")
    public RedisTemplate<String, Object> redisTemplate() {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        // Redis配置
        return template;
    }
    
    @Bean
    @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
    public WebMvcConfigurer webMvcConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**")
                        .allowedOrigins("*")
                        .allowedMethods("*");
            }
        };
    }
}

// 自定义条件注解
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnDatabaseCondition.class)
public @interface ConditionalOnDatabase {
    DatabaseType value();
}

public enum DatabaseType {
    MYSQL, POSTGRESQL, ORACLE, H2
}

public class OnDatabaseCondition implements Condition {
    
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionalOnDatabase.class.getName());
        DatabaseType requiredType = (DatabaseType) attributes.get("value");
        
        String url = context.getEnvironment().getProperty("spring.datasource.url");
        if (url == null) {
            return false;
        }
        
        switch (requiredType) {
            case MYSQL:
                return url.contains("mysql");
            case POSTGRESQL:
                return url.contains("postgresql");
            case ORACLE:
                return url.contains("oracle");
            case H2:
                return url.contains("h2");
            default:
                return false;
        }
    }
}

// 使用自定义条件
@Configuration
public class DatabaseSpecificConfiguration {
    
    @Bean
    @ConditionalOnDatabase(DatabaseType.MYSQL)
    public MySQLDialect mysqlDialect() {
        return new MySQLDialect();
    }
    
    @Bean
    @ConditionalOnDatabase(DatabaseType.POSTGRESQL)
    public PostgreSQLDialect postgresqlDialect() {
        return new PostgreSQLDialect();
    }
}

1.2 配置属性绑定

// 配置属性类
@ConfigurationProperties(prefix = "app")
@Data
@Validated
public class AppProperties {
    
    @NotBlank
    private String name;
    
    @NotBlank
    private String version;
    
    private Security security = new Security();
    
    private Database database = new Database();
    
    private Cache cache = new Cache();
    
    @Data
    public static class Security {
        
        @NotBlank
        private String secretKey;
        
        @Min(300)
        private int tokenExpiration = 3600;
        
        private boolean enableCsrf = true;
        
        private List<String> allowedOrigins = new ArrayList<>();
    }
    
    @Data
    public static class Database {
        
        @Min(1)
        private int maxConnections = 10;
        
        @Min(1000)
        private int connectionTimeout = 30000;
        
        private boolean enableMetrics = true;
        
        private Map<String, String> properties = new HashMap<>();
    }
    
    @Data
    public static class Cache {
        
        private CacheType type = CacheType.MEMORY;
        
        @Min(60)
        private int ttl = 3600;
        
        private String redisHost = "localhost";
        
        @Range(min = 1, max = 65535)
        private int redisPort = 6379;
    }
    
    public enum CacheType {
        MEMORY, REDIS, EHCACHE
    }
}

// 配置属性验证器
@Component
public class AppPropertiesValidator implements Validator {
    
    @Override
    public boolean supports(Class<?> clazz) {
        return AppProperties.class.isAssignableFrom(clazz);
    }
    
    @Override
    public void validate(Object target, Errors errors) {
        AppProperties properties = (AppProperties) target;
        
        // 自定义验证逻辑
        if (properties.getCache().getType() == AppProperties.CacheType.REDIS) {
            if (StringUtils.isEmpty(properties.getCache().getRedisHost())) {
                errors.rejectValue("cache.redisHost", "redis.host.required", "Redis主机不能为空");
            }
        }
        
        if (properties.getSecurity().getAllowedOrigins().isEmpty()) {
            errors.rejectValue("security.allowedOrigins", "origins.required", "至少需要配置一个允许的来源");
        }
    }
}

// 配置属性使用
@Service
public class ConfigurationService {
    
    private final AppProperties appProperties;
    
    public ConfigurationService(AppProperties appProperties) {
        this.appProperties = appProperties;
    }
    
    public void printConfiguration() {
        System.out.println("应用名称: " + appProperties.getName());
        System.out.println("应用版本: " + appProperties.getVersion());
        System.out.println("数据库最大连接数: " + appProperties.getDatabase().getMaxConnections());
        System.out.println("缓存类型: " + appProperties.getCache().getType());
        System.out.println("安全密钥: " + appProperties.getSecurity().getSecretKey());
    }
    
    public boolean isCacheEnabled() {
        return appProperties.getCache().getType() != null;
    }
    
    public Duration getTokenExpiration() {
        return Duration.ofSeconds(appProperties.getSecurity().getTokenExpiration());
    }
}

2. 自定义Starter开发

2.1 Starter项目结构

// 自动配置类
@Configuration
@ConditionalOnClass(SmsService.class)
@EnableConfigurationProperties(SmsProperties.class)
public class SmsAutoConfiguration {
    
    private final SmsProperties properties;
    
    public SmsAutoConfiguration(SmsProperties properties) {
        this.properties = properties;
    }
    
    @Bean
    @ConditionalOnMissingBean
    public SmsService smsService() {
        switch (properties.getProvider()) {
            case ALIYUN:
                return new AliyunSmsService(properties);
            case TENCENT:
                return new TencentSmsService(properties);
            case HUAWEI:
                return new HuaweiSmsService(properties);
            default:
                throw new IllegalArgumentException("不支持的短信服务提供商: " + properties.getProvider());
        }
    }
    
    @Bean
    @ConditionalOnProperty(name = "sms.template.enabled", havingValue = "true")
    public SmsTemplateService smsTemplateService(SmsService smsService) {
        return new SmsTemplateService(smsService, properties);
    }
    
    @Bean
    @ConditionalOnProperty(name = "sms.metrics.enabled", havingValue = "true")
    public SmsMetricsCollector smsMetricsCollector() {
        return new SmsMetricsCollector();
    }
}

// 配置属性
@ConfigurationProperties(prefix = "sms")
@Data
public class SmsProperties {
    
    /**
     * 短信服务提供商
     */
    private SmsProvider provider = SmsProvider.ALIYUN;
    
    /**
     * 访问密钥ID
     */
    private String accessKeyId;
    
    /**
     * 访问密钥Secret
     */
    private String accessKeySecret;
    
    /**
     * 短信签名
     */
    private String signName;
    
    /**
     * 连接超时时间(毫秒)
     */
    private int connectTimeout = 5000;
    
    /**
     * 读取超时时间(毫秒)
     */
    private int readTimeout = 10000;
    
    /**
     * 重试次数
     */
    private int retryCount = 3;
    
    /**
     * 是否启用模板功能
     */
    private Template template = new Template();
    
    /**
     * 是否启用指标收集
     */
    private Metrics metrics = new Metrics();
    
    @Data
    public static class Template {
        private boolean enabled = false;
        private String configLocation = "classpath:sms-templates.yml";
    }
    
    @Data
    public static class Metrics {
        private boolean enabled = false;
        private String prefix = "sms";
    }
    
    public enum SmsProvider {
        ALIYUN, TENCENT, HUAWEI
    }
}

// 短信服务接口
public interface SmsService {
    
    /**
     * 发送短信
     */
    SmsResult sendSms(SmsRequest request);
    
    /**
     * 批量发送短信
     */
    List<SmsResult> batchSendSms(List<SmsRequest> requests);
    
    /**
     * 查询短信发送状态
     */
    SmsStatus querySmsStatus(String messageId);
}

// 短信请求
@Data
@Builder
public class SmsRequest {
    
    /**
     * 手机号码
     */
    @NotBlank
    private String phoneNumber;
    
    /**
     * 短信模板代码
     */
    @NotBlank
    private String templateCode;
    
    /**
     * 模板参数
     */
    private Map<String, Object> templateParams;
    
    /**
     * 短信签名
     */
    private String signName;
    
    /**
     * 外部流水号
     */
    private String outId;
}

// 短信结果
@Data
public class SmsResult {
    
    /**
     * 是否成功
     */
    private boolean success;
    
    /**
     * 消息ID
     */
    private String messageId;
    
    /**
     * 错误代码
     */
    private String errorCode;
    
    /**
     * 错误消息
     */
    private String errorMessage;
    
    /**
     * 发送时间
     */
    private LocalDateTime sendTime;
    
    public static SmsResult success(String messageId) {
        SmsResult result = new SmsResult();
        result.setSuccess(true);
        result.setMessageId(messageId);
        result.setSendTime(LocalDateTime.now());
        return result;
    }
    
    public static SmsResult failure(String errorCode, String errorMessage) {
        SmsResult result = new SmsResult();
        result.setSuccess(false);
        result.setErrorCode(errorCode);
        result.setErrorMessage(errorMessage);
        result.setSendTime(LocalDateTime.now());
        return result;
    }
}

// 阿里云短信服务实现
public class AliyunSmsService implements SmsService {
    
    private final SmsProperties properties;
    private final IAcsClient client;
    
    public AliyunSmsService(SmsProperties properties) {
        this.properties = properties;
        this.client = createClient();
    }
    
    private IAcsClient createClient() {
        IClientProfile profile = DefaultProfile.getProfile(
                "cn-hangzhou",
                properties.getAccessKeyId(),
                properties.getAccessKeySecret()
        );
        
        DefaultProfile.addEndpoint(
                "cn-hangzhou",
                "Dysmsapi",
                "dysmsapi.aliyuncs.com"
        );
        
        return new DefaultAcsClient(profile);
    }
    
    @Override
    public SmsResult sendSms(SmsRequest request) {
        try {
            SendSmsRequest sendRequest = new SendSmsRequest();
            sendRequest.setPhoneNumbers(request.getPhoneNumber());
            sendRequest.setSignName(request.getSignName() != null ? request.getSignName() : properties.getSignName());
            sendRequest.setTemplateCode(request.getTemplateCode());
            
            if (request.getTemplateParams() != null && !request.getTemplateParams().isEmpty()) {
                sendRequest.setTemplateParam(JSON.toJSONString(request.getTemplateParams()));
            }
            
            if (request.getOutId() != null) {
                sendRequest.setOutId(request.getOutId());
            }
            
            SendSmsResponse response = client.getAcsResponse(sendRequest);
            
            if ("OK".equals(response.getCode())) {
                return SmsResult.success(response.getBizId());
            } else {
                return SmsResult.failure(response.getCode(), response.getMessage());
            }
            
        } catch (Exception e) {
            return SmsResult.failure("SYSTEM_ERROR", e.getMessage());
        }
    }
    
    @Override
    public List<SmsResult> batchSendSms(List<SmsRequest> requests) {
        return requests.stream()
                .map(this::sendSms)
                .collect(Collectors.toList());
    }
    
    @Override
    public SmsStatus querySmsStatus(String messageId) {
        // 实现查询逻辑
        return new SmsStatus();
    }
}

// 短信模板服务
public class SmsTemplateService {
    
    private final SmsService smsService;
    private final Map<String, SmsTemplate> templates;
    
    public SmsTemplateService(SmsService smsService, SmsProperties properties) {
        this.smsService = smsService;
        this.templates = loadTemplates(properties.getTemplate().getConfigLocation());
    }
    
    public SmsResult sendByTemplate(String templateName, String phoneNumber, Map<String, Object> params) {
        SmsTemplate template = templates.get(templateName);
        if (template == null) {
            throw new IllegalArgumentException("短信模板不存在: " + templateName);
        }
        
        SmsRequest request = SmsRequest.builder()
                .phoneNumber(phoneNumber)
                .templateCode(template.getCode())
                .templateParams(params)
                .signName(template.getSignName())
                .build();
        
        return smsService.sendSms(request);
    }
    
    private Map<String, SmsTemplate> loadTemplates(String configLocation) {
        // 从配置文件加载模板
        Map<String, SmsTemplate> templateMap = new HashMap<>();
        
        // 示例模板
        templateMap.put("login", new SmsTemplate("SMS_123456", "登录验证", "您的登录验证码是${code},5分钟内有效。"));
        templateMap.put("register", new SmsTemplate("SMS_789012", "注册验证", "欢迎注册,您的验证码是${code}。"));
        
        return templateMap;
    }
}

// 短信模板
@Data
public class SmsTemplate {
    
    private String code;
    private String name;
    private String content;
    private String signName;
    
    public SmsTemplate(String code, String name, String content) {
        this.code = code;
        this.name = name;
        this.content = content;
    }
}

// 指标收集器
@Component
public class SmsMetricsCollector {
    
    private final Counter smsCounter;
    private final Timer smsTimer;
    private final Gauge smsGauge;
    
    public SmsMetricsCollector() {
        this.smsCounter = Metrics.counter("sms.sent.total");
        this.smsTimer = Metrics.timer("sms.send.duration");
        this.smsGauge = Metrics.gauge("sms.queue.size", this, SmsMetricsCollector::getQueueSize);
    }
    
    public void recordSmsSent(String provider, boolean success) {
        smsCounter.increment(
                Tags.of(
                        "provider", provider,
                        "status", success ? "success" : "failure"
                )
        );
    }
    
    public void recordSendDuration(Duration duration) {
        smsTimer.record(duration);
    }
    
    private double getQueueSize() {
        // 返回队列大小
        return 0;
    }
}

2.2 Starter配置文件

# spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.sms.autoconfigure.SmsAutoConfiguration

# additional-spring-configuration-metadata.json
{
  "properties": [
    {
      "name": "sms.provider",
      "type": "com.example.sms.SmsProperties$SmsProvider",
      "description": "短信服务提供商",
      "defaultValue": "aliyun"
    },
    {
      "name": "sms.access-key-id",
      "type": "java.lang.String",
      "description": "访问密钥ID"
    },
    {
      "name": "sms.access-key-secret",
      "type": "java.lang.String",
      "description": "访问密钥Secret"
    },
    {
      "name": "sms.sign-name",
      "type": "java.lang.String",
      "description": "短信签名"
    },
    {
      "name": "sms.connect-timeout",
      "type": "java.lang.Integer",
      "description": "连接超时时间(毫秒)",
      "defaultValue": 5000
    },
    {
      "name": "sms.template.enabled",
      "type": "java.lang.Boolean",
      "description": "是否启用模板功能",
      "defaultValue": false
    },
    {
      "name": "sms.metrics.enabled",
      "type": "java.lang.Boolean",
      "description": "是否启用指标收集",
      "defaultValue": false
    }
  ]
}

3. Spring Boot测试

3.1 单元测试

// 服务层测试
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
    
    @Mock
    private UserRepository userRepository;
    
    @Mock
    private PasswordEncoder passwordEncoder;
    
    @InjectMocks
    private UserService userService;
    
    @Test
    @DisplayName("创建用户 - 成功")
    void createUser_Success() {
        // Given
        CreateUserRequest request = CreateUserRequest.builder()
                .username("testuser")
                .email("test@example.com")
                .password("password123")
                .build();
        
        User savedUser = new User();
        savedUser.setId("user-123");
        savedUser.setUsername(request.getUsername());
        savedUser.setEmail(request.getEmail());
        
        when(userRepository.existsByUsername(request.getUsername())).thenReturn(false);
        when(userRepository.existsByEmail(request.getEmail())).thenReturn(false);
        when(passwordEncoder.encode(request.getPassword())).thenReturn("encoded-password");
        when(userRepository.save(any(User.class))).thenReturn(savedUser);
        
        // When
        User result = userService.createUser(request);
        
        // Then
        assertThat(result).isNotNull();
        assertThat(result.getId()).isEqualTo("user-123");
        assertThat(result.getUsername()).isEqualTo("testuser");
        assertThat(result.getEmail()).isEqualTo("test@example.com");
        
        verify(userRepository).existsByUsername("testuser");
        verify(userRepository).existsByEmail("test@example.com");
        verify(passwordEncoder).encode("password123");
        verify(userRepository).save(any(User.class));
    }
    
    @Test
    @DisplayName("创建用户 - 用户名已存在")
    void createUser_UsernameExists() {
        // Given
        CreateUserRequest request = CreateUserRequest.builder()
                .username("existinguser")
                .email("test@example.com")
                .password("password123")
                .build();
        
        when(userRepository.existsByUsername(request.getUsername())).thenReturn(true);
        
        // When & Then
        assertThatThrownBy(() -> userService.createUser(request))
                .isInstanceOf(UserAlreadyExistsException.class)
                .hasMessage("用户名已存在: existinguser");
        
        verify(userRepository).existsByUsername("existinguser");
        verify(userRepository, never()).save(any(User.class));
    }
    
    @ParameterizedTest
    @ValueSource(strings = {"", " ", "ab", "verylongusernamethatexceedslimit"})
    @DisplayName("创建用户 - 无效用户名")
    void createUser_InvalidUsername(String username) {
        // Given
        CreateUserRequest request = CreateUserRequest.builder()
                .username(username)
                .email("test@example.com")
                .password("password123")
                .build();
        
        // When & Then
        assertThatThrownBy(() -> userService.createUser(request))
                .isInstanceOf(IllegalArgumentException.class);
    }
    
    @Test
    @DisplayName("获取用户 - 成功")
    void getUserById_Success() {
        // Given
        String userId = "user-123";
        User user = new User();
        user.setId(userId);
        user.setUsername("testuser");
        
        when(userRepository.findById(userId)).thenReturn(Optional.of(user));
        
        // When
        User result = userService.getUserById(userId);
        
        // Then
        assertThat(result).isNotNull();
        assertThat(result.getId()).isEqualTo(userId);
        assertThat(result.getUsername()).isEqualTo("testuser");
    }
    
    @Test
    @DisplayName("获取用户 - 用户不存在")
    void getUserById_UserNotFound() {
        // Given
        String userId = "nonexistent";
        when(userRepository.findById(userId)).thenReturn(Optional.empty());
        
        // When & Then
        assertThatThrownBy(() -> userService.getUserById(userId))
                .isInstanceOf(UserNotFoundException.class)
                .hasMessage("用户不存在: nonexistent");
    }
}

// 测试配置
@TestConfiguration
public class TestConfig {
    
    @Bean
    @Primary
    public Clock testClock() {
        return Clock.fixed(Instant.parse("2023-01-01T00:00:00Z"), ZoneOffset.UTC);
    }
    
    @Bean
    @Primary
    public PasswordEncoder testPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

// 自定义测试注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@SpringBootTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@Transactional
@Rollback
public @interface IntegrationTest {
}

3.2 集成测试

// Web层集成测试
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.ANY)
@Testcontainers
class UserControllerIntegrationTest {
    
    @Container
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:13")
            .withDatabaseName("testdb")
            .withUsername("test")
            .withPassword("test");
    
    @Autowired
    private TestRestTemplate restTemplate;
    
    @Autowired
    private UserRepository userRepository;
    
    @Autowired
    private TestEntityManager entityManager;
    
    @DynamicPropertySource
    static void configureProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", postgres::getJdbcUrl);
        registry.add("spring.datasource.username", postgres::getUsername);
        registry.add("spring.datasource.password", postgres::getPassword);
    }
    
    @BeforeEach
    void setUp() {
        userRepository.deleteAll();
    }
    
    @Test
    @DisplayName("创建用户 - 成功")
    void createUser_Success() {
        // Given
        CreateUserRequest request = CreateUserRequest.builder()
                .username("testuser")
                .email("test@example.com")
                .password("password123")
                .build();
        
        // When
        ResponseEntity<User> response = restTemplate.postForEntity(
                "/api/users",
                request,
                User.class
        );
        
        // Then
        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED);
        assertThat(response.getBody()).isNotNull();
        assertThat(response.getBody().getUsername()).isEqualTo("testuser");
        assertThat(response.getBody().getEmail()).isEqualTo("test@example.com");
        
        // 验证数据库
        Optional<User> savedUser = userRepository.findByUsername("testuser");
        assertThat(savedUser).isPresent();
        assertThat(savedUser.get().getEmail()).isEqualTo("test@example.com");
    }
    
    @Test
    @DisplayName("获取用户列表 - 分页")
    void getUsers_Pagination() {
        // Given
        createTestUsers(15);
        
        // When
        ResponseEntity<PagedResponse<User>> response = restTemplate.exchange(
                "/api/users?page=0&size=10",
                HttpMethod.GET,
                null,
                new ParameterizedTypeReference<PagedResponse<User>>() {}
        );
        
        // Then
        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
        assertThat(response.getBody()).isNotNull();
        assertThat(response.getBody().getContent()).hasSize(10);
        assertThat(response.getBody().getTotalElements()).isEqualTo(15);
        assertThat(response.getBody().getTotalPages()).isEqualTo(2);
    }
    
    @Test
    @DisplayName("更新用户 - 成功")
    void updateUser_Success() {
        // Given
        User existingUser = createTestUser("testuser", "test@example.com");
        
        UpdateUserRequest request = UpdateUserRequest.builder()
                .email("newemail@example.com")
                .build();
        
        // When
        ResponseEntity<User> response = restTemplate.exchange(
                "/api/users/" + existingUser.getId(),
                HttpMethod.PUT,
                new HttpEntity<>(request),
                User.class
        );
        
        // Then
        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
        assertThat(response.getBody()).isNotNull();
        assertThat(response.getBody().getEmail()).isEqualTo("newemail@example.com");
    }
    
    @Test
    @DisplayName("删除用户 - 成功")
    void deleteUser_Success() {
        // Given
        User existingUser = createTestUser("testuser", "test@example.com");
        
        // When
        ResponseEntity<Void> response = restTemplate.exchange(
                "/api/users/" + existingUser.getId(),
                HttpMethod.DELETE,
                null,
                Void.class
        );
        
        // Then
        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NO_CONTENT);
        
        // 验证用户已删除
        Optional<User> deletedUser = userRepository.findById(existingUser.getId());
        assertThat(deletedUser).isEmpty();
    }
    
    private User createTestUser(String username, String email) {
        User user = new User();
        user.setUsername(username);
        user.setEmail(email);
        user.setPassword("encoded-password");
        user.setCreatedAt(LocalDateTime.now());
        return userRepository.save(user);
    }
    
    private void createTestUsers(int count) {
        for (int i = 0; i < count; i++) {
            createTestUser("user" + i, "user" + i + "@example.com");
        }
    }
}

// 分页响应DTO
@Data
public class PagedResponse<T> {
    private List<T> content;
    private int page;
    private int size;
    private long totalElements;
    private int totalPages;
    private boolean first;
    private boolean last;
}

3.3 测试切片

// JPA测试
@DataJpaTest
class UserRepositoryTest {
    
    @Autowired
    private TestEntityManager entityManager;
    
    @Autowired
    private UserRepository userRepository;
    
    @Test
    @DisplayName("根据用户名查找用户")
    void findByUsername() {
        // Given
        User user = new User();
        user.setUsername("testuser");
        user.setEmail("test@example.com");
        user.setPassword("password");
        entityManager.persistAndFlush(user);
        
        // When
        Optional<User> found = userRepository.findByUsername("testuser");
        
        // Then
        assertThat(found).isPresent();
        assertThat(found.get().getUsername()).isEqualTo("testuser");
        assertThat(found.get().getEmail()).isEqualTo("test@example.com");
    }
    
    @Test
    @DisplayName("根据邮箱查找用户")
    void findByEmail() {
        // Given
        User user = new User();
        user.setUsername("testuser");
        user.setEmail("test@example.com");
        user.setPassword("password");
        entityManager.persistAndFlush(user);
        
        // When
        Optional<User> found = userRepository.findByEmail("test@example.com");
        
        // Then
        assertThat(found).isPresent();
        assertThat(found.get().getEmail()).isEqualTo("test@example.com");
    }
    
    @Test
    @DisplayName("检查用户名是否存在")
    void existsByUsername() {
        // Given
        User user = new User();
        user.setUsername("testuser");
        user.setEmail("test@example.com");
        user.setPassword("password");
        entityManager.persistAndFlush(user);
        
        // When & Then
        assertThat(userRepository.existsByUsername("testuser")).isTrue();
        assertThat(userRepository.existsByUsername("nonexistent")).isFalse();
    }
    
    @Test
    @DisplayName("分页查询活跃用户")
    void findActiveUsers() {
        // Given
        createTestUsers(15, true);
        createTestUsers(5, false);
        
        Pageable pageable = PageRequest.of(0, 10);
        
        // When
        Page<User> result = userRepository.findByActiveTrue(pageable);
        
        // Then
        assertThat(result.getContent()).hasSize(10);
        assertThat(result.getTotalElements()).isEqualTo(15);
        assertThat(result.getTotalPages()).isEqualTo(2);
    }
    
    private void createTestUsers(int count, boolean active) {
        for (int i = 0; i < count; i++) {
            User user = new User();
            user.setUsername("user" + i + (active ? "_active" : "_inactive"));
            user.setEmail("user" + i + "@example.com");
            user.setPassword("password");
            user.setActive(active);
            entityManager.persistAndFlush(user);
        }
    }
}

// Web MVC测试
@WebMvcTest(UserController.class)
class UserControllerTest {
    
    @Autowired
    private MockMvc mockMvc;
    
    @MockBean
    private UserService userService;
    
    @Autowired
    private ObjectMapper objectMapper;
    
    @Test
    @DisplayName("创建用户 - 成功")
    void createUser_Success() throws Exception {
        // Given
        CreateUserRequest request = CreateUserRequest.builder()
                .username("testuser")
                .email("test@example.com")
                .password("password123")
                .build();
        
        User createdUser = new User();
        createdUser.setId("user-123");
        createdUser.setUsername(request.getUsername());
        createdUser.setEmail(request.getEmail());
        
        when(userService.createUser(any(CreateUserRequest.class))).thenReturn(createdUser);
        
        // When & Then
        mockMvc.perform(post("/api/users")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(request)))
                .andExpect(status().isCreated())
                .andExpect(jsonPath("$.id").value("user-123"))
                .andExpect(jsonPath("$.username").value("testuser"))
                .andExpect(jsonPath("$.email").value("test@example.com"));
        
        verify(userService).createUser(any(CreateUserRequest.class));
    }
    
    @Test
    @DisplayName("创建用户 - 验证失败")
    void createUser_ValidationFailure() throws Exception {
        // Given
        CreateUserRequest request = CreateUserRequest.builder()
                .username("") // 空用户名
                .email("invalid-email") // 无效邮箱
                .password("123") // 密码太短
                .build();
        
        // When & Then
        mockMvc.perform(post("/api/users")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(request)))
                .andExpect(status().isBadRequest())
                .andExpect(jsonPath("$.errors").isArray())
                .andExpect(jsonPath("$.errors", hasSize(greaterThan(0))));
        
        verify(userService, never()).createUser(any(CreateUserRequest.class));
    }
    
    @Test
    @DisplayName("获取用户 - 成功")
    void getUser_Success() throws Exception {
        // Given
        String userId = "user-123";
        User user = new User();
        user.setId(userId);
        user.setUsername("testuser");
        user.setEmail("test@example.com");
        
        when(userService.getUserById(userId)).thenReturn(user);
        
        // When & Then
        mockMvc.perform(get("/api/users/{id}", userId))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.id").value(userId))
                .andExpect(jsonPath("$.username").value("testuser"))
                .andExpect(jsonPath("$.email").value("test@example.com"));
    }
    
    @Test
    @DisplayName("获取用户 - 用户不存在")
    void getUser_NotFound() throws Exception {
        // Given
        String userId = "nonexistent";
        when(userService.getUserById(userId)).thenThrow(new UserNotFoundException("用户不存在: " + userId));
        
        // When & Then
        mockMvc.perform(get("/api/users/{id}", userId))
                .andExpect(status().isNotFound())
                .andExpect(jsonPath("$.message").value("用户不存在: nonexistent"));
    }
}

4. 生产环境部署

4.1 配置管理

# application-prod.yml
spring:
  profiles:
    active: prod
  
  datasource:
    url: ${DB_URL:jdbc:mysql://localhost:3306/myapp}
    username: ${DB_USERNAME:myapp}
    password: ${DB_PASSWORD}
    hikari:
      maximum-pool-size: ${DB_POOL_SIZE:20}
      minimum-idle: ${DB_MIN_IDLE:5}
      connection-timeout: ${DB_CONNECTION_TIMEOUT:30000}
      idle-timeout: ${DB_IDLE_TIMEOUT:600000}
      max-lifetime: ${DB_MAX_LIFETIME:1800000}
  
  jpa:
    hibernate:
      ddl-auto: validate
    show-sql: false
    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQL8Dialect
        format_sql: false
        use_sql_comments: false
  
  redis:
    host: ${REDIS_HOST:localhost}
    port: ${REDIS_PORT:6379}
    password: ${REDIS_PASSWORD:}
    timeout: ${REDIS_TIMEOUT:2000}
    lettuce:
      pool:
        max-active: ${REDIS_POOL_MAX_ACTIVE:8}
        max-idle: ${REDIS_POOL_MAX_IDLE:8}
        min-idle: ${REDIS_POOL_MIN_IDLE:0}
  
  cache:
    type: redis
    redis:
      time-to-live: ${CACHE_TTL:3600000}
  
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: ${JWT_ISSUER_URI}

server:
  port: ${SERVER_PORT:8080}
  servlet:
    context-path: ${CONTEXT_PATH:/api}
  compression:
    enabled: true
    mime-types: text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json
  http2:
    enabled: true

logging:
  level:
    com.example: ${LOG_LEVEL:INFO}
    org.springframework.security: WARN
    org.hibernate.SQL: WARN
  pattern:
    console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
    file: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
  file:
    name: ${LOG_FILE:/var/log/myapp/application.log}
    max-size: ${LOG_MAX_SIZE:100MB}
    max-history: ${LOG_MAX_HISTORY:30}

management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus
      base-path: /actuator
  endpoint:
    health:
      show-details: when-authorized
      show-components: always
  metrics:
    export:
      prometheus:
        enabled: true
  health:
    redis:
      enabled: true
    db:
      enabled: true

app:
  name: ${APP_NAME:MyApplication}
  version: ${APP_VERSION:1.0.0}
  security:
    jwt:
      secret: ${JWT_SECRET}
      expiration: ${JWT_EXPIRATION:86400}
    cors:
      allowed-origins: ${CORS_ALLOWED_ORIGINS:http://localhost:3000}
      allowed-methods: ${CORS_ALLOWED_METHODS:GET,POST,PUT,DELETE,OPTIONS}
  
  async:
    core-pool-size: ${ASYNC_CORE_POOL_SIZE:10}
    max-pool-size: ${ASYNC_MAX_POOL_SIZE:50}
    queue-capacity: ${ASYNC_QUEUE_CAPACITY:100}
  
  rate-limit:
    enabled: ${RATE_LIMIT_ENABLED:true}
    requests-per-minute: ${RATE_LIMIT_RPM:100}

4.2 Docker部署

# 多阶段构建Dockerfile
FROM maven:3.8.4-openjdk-11 AS builder

WORKDIR /app

# 复制pom.xml和下载依赖
COPY pom.xml .
RUN mvn dependency:go-offline -B

# 复制源代码并构建
COPY src ./src
RUN mvn clean package -DskipTests

# 运行时镜像
FROM openjdk:11-jre-slim

# 安装必要的工具
RUN apt-get update && apt-get install -y \
    curl \
    && rm -rf /var/lib/apt/lists/*

# 创建应用用户
RUN groupadd -r appuser && useradd -r -g appuser appuser

# 设置工作目录
WORKDIR /app

# 复制jar文件
COPY --from=builder /app/target/*.jar app.jar

# 创建日志目录
RUN mkdir -p /var/log/myapp && chown -R appuser:appuser /var/log/myapp

# 设置文件权限
RUN chown -R appuser:appuser /app

# 切换到应用用户
USER appuser

# 暴露端口
EXPOSE 8080

# 健康检查
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
    CMD curl -f http://localhost:8080/actuator/health || exit 1

# JVM参数
ENV JAVA_OPTS="-Xms512m -Xmx1024m -XX:+UseG1GC -XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0"

# 启动应用
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar app.jar"]
# docker-compose-prod.yml
version: '3.8'

services:
  app:
    build: .
    ports:
      - "8080:8080"
    environment:
      - SPRING_PROFILES_ACTIVE=prod
      - DB_URL=jdbc:mysql://mysql:3306/myapp
      - DB_USERNAME=myapp
      - DB_PASSWORD=${DB_PASSWORD}
      - REDIS_HOST=redis
      - JWT_SECRET=${JWT_SECRET}
      - LOG_LEVEL=INFO
    depends_on:
      - mysql
      - redis
    volumes:
      - app_logs:/var/log/myapp
    networks:
      - app_network
    restart: unless-stopped
    deploy:
      resources:
        limits:
          memory: 1G
          cpus: '0.5'
        reservations:
          memory: 512M
          cpus: '0.25'

  mysql:
    image: mysql:8.0
    environment:
      - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
      - MYSQL_DATABASE=myapp
      - MYSQL_USER=myapp
      - MYSQL_PASSWORD=${DB_PASSWORD}
    volumes:
      - mysql_data:/var/lib/mysql
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql
    networks:
      - app_network
    restart: unless-stopped
    command: --default-authentication-plugin=mysql_native_password

  redis:
    image: redis:6-alpine
    command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD}
    volumes:
      - redis_data:/data
    networks:
      - app_network
    restart: unless-stopped

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
      - ./ssl:/etc/nginx/ssl
      - nginx_logs:/var/log/nginx
    depends_on:
      - app
    networks:
      - app_network
    restart: unless-stopped

volumes:
  mysql_data:
  redis_data:
  app_logs:
  nginx_logs:

networks:
  app_network:
    driver: bridge

4.3 Kubernetes部署

# k8s-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
  labels:
    app: myapp
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
      - name: myapp
        image: myapp:latest
        ports:
        - containerPort: 8080
        env:
        - name: SPRING_PROFILES_ACTIVE
          value: "prod"
        - name: DB_URL
          valueFrom:
            secretKeyRef:
              name: app-secrets
              key: db-url
        - name: DB_USERNAME
          valueFrom:
            secretKeyRef:
              name: app-secrets
              key: db-username
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: app-secrets
              key: db-password
        - name: REDIS_HOST
          value: "redis-service"
        - name: JWT_SECRET
          valueFrom:
            secretKeyRef:
              name: app-secrets
              key: jwt-secret
        resources:
          requests:
            memory: "512Mi"
            cpu: "250m"
          limits:
            memory: "1Gi"
            cpu: "500m"
        livenessProbe:
          httpGet:
            path: /actuator/health
            port: 8080
          initialDelaySeconds: 60
          periodSeconds: 30
          timeoutSeconds: 10
          failureThreshold: 3
        readinessProbe:
          httpGet:
            path: /actuator/health
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 10
          timeoutSeconds: 5
          failureThreshold: 3
        volumeMounts:
        - name: app-logs
          mountPath: /var/log/myapp
      volumes:
      - name: app-logs
        emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
  name: myapp-service
spec:
  selector:
    app: myapp
  ports:
  - protocol: TCP
    port: 80
    targetPort: 8080
  type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: myapp-ingress
  annotations:
    kubernetes.io/ingress.class: nginx
    cert-manager.io/cluster-issuer: letsencrypt-prod
    nginx.ingress.kubernetes.io/rate-limit: "100"
spec:
  tls:
  - hosts:
    - myapp.example.com
    secretName: myapp-tls
  rules:
  - host: myapp.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: myapp-service
            port:
              number: 80
---
apiVersion: v1
kind: Secret
metadata:
  name: app-secrets
type: Opaque
data:
  db-url: # base64 encoded database URL
  db-username: # base64 encoded database username
  db-password: # base64 encoded database password
  jwt-secret: # base64 encoded JWT secret
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: myapp-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: myapp
  minReplicas: 3
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70
  - type: Resource
    resource:
      name: memory
      target:
        type: Utilization
        averageUtilization: 80

5. 性能调优

5.1 JVM调优

# JVM启动参数
JAVA_OPTS="
  -Xms1g -Xmx2g
  -XX:+UseG1GC
  -XX:MaxGCPauseMillis=200
  -XX:+UseContainerSupport
  -XX:MaxRAMPercentage=75.0
  -XX:+HeapDumpOnOutOfMemoryError
  -XX:HeapDumpPath=/var/log/myapp/heapdump.hprof
  -XX:+PrintGCDetails
  -XX:+PrintGCTimeStamps
  -Xloggc:/var/log/myapp/gc.log
  -XX:+UseGCLogFileRotation
  -XX:NumberOfGCLogFiles=5
  -XX:GCLogFileSize=10M
  -Djava.security.egd=file:/dev/./urandom
  -Dspring.profiles.active=prod
"

5.2 数据库优化

// 数据库连接池配置
@Configuration
public class DatabaseConfig {
    
    @Bean
    @ConfigurationProperties("spring.datasource.hikari")
    public HikariConfig hikariConfig() {
        HikariConfig config = new HikariConfig();
        
        // 连接池大小
        config.setMaximumPoolSize(20);
        config.setMinimumIdle(5);
        
        // 超时设置
        config.setConnectionTimeout(30000);
        config.setIdleTimeout(600000);
        config.setMaxLifetime(1800000);
        config.setLeakDetectionThreshold(60000);
        
        // 连接测试
        config.setConnectionTestQuery("SELECT 1");
        config.setValidationTimeout(5000);
        
        // 性能优化
        config.addDataSourceProperty("cachePrepStmts", "true");
        config.addDataSourceProperty("prepStmtCacheSize", "250");
        config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
        config.addDataSourceProperty("useServerPrepStmts", "true");
        config.addDataSourceProperty("useLocalSessionState", "true");
        config.addDataSourceProperty("rewriteBatchedStatements", "true");
        config.addDataSourceProperty("cacheResultSetMetadata", "true");
        config.addDataSourceProperty("cacheServerConfiguration", "true");
        config.addDataSourceProperty("elideSetAutoCommits", "true");
        config.addDataSourceProperty("maintainTimeStats", "false");
        
        return config;
    }
    
    @Bean
    public DataSource dataSource(HikariConfig hikariConfig) {
        return new HikariDataSource(hikariConfig);
    }
}

// JPA性能优化
@Configuration
public class JpaConfig {
    
    @Bean
    public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(entityManagerFactory);
        return transactionManager;
    }
    
    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(
            DataSource dataSource, JpaProperties jpaProperties) {
        
        LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(dataSource);
        em.setPackagesToScan("com.example.entity");
        
        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        em.setJpaVendorAdapter(vendorAdapter);
        
        Properties properties = new Properties();
        properties.putAll(jpaProperties.getProperties());
        
        // 性能优化配置
        properties.put("hibernate.jdbc.batch_size", "25");
        properties.put("hibernate.order_inserts", "true");
        properties.put("hibernate.order_updates", "true");
        properties.put("hibernate.jdbc.batch_versioned_data", "true");
        properties.put("hibernate.connection.provider_disables_autocommit", "true");
        properties.put("hibernate.query.plan_cache_max_size", "2048");
        properties.put("hibernate.query.plan_parameter_metadata_max_size", "128");
        
        em.setJpaProperties(properties);
        
        return em;
    }
}

// 查询优化
@Repository
public class OptimizedUserRepository {
    
    @PersistenceContext
    private EntityManager entityManager;
    
    // 使用JPQL进行批量查询
    @Query("SELECT u FROM User u WHERE u.id IN :ids")
    List<User> findByIds(@Param("ids") List<String> ids);
    
    // 使用原生SQL进行复杂查询
    @Query(value = "SELECT * FROM users u WHERE u.created_at >= :startDate AND u.status = :status", 
           nativeQuery = true)
    List<User> findActiveUsersSince(@Param("startDate") LocalDateTime startDate, 
                                   @Param("status") String status);
    
    // 分页查询优化
    @Query("SELECT u FROM User u WHERE u.active = true ORDER BY u.createdAt DESC")
    Page<User> findActiveUsersOptimized(Pageable pageable);
    
    // 投影查询减少数据传输
    @Query("SELECT new com.example.dto.UserSummaryDto(u.id, u.username, u.email) FROM User u")
    List<UserSummaryDto> findUserSummaries();
    
    // 批量更新
    @Modifying
    @Query("UPDATE User u SET u.lastLoginAt = :loginTime WHERE u.id IN :ids")
    int updateLastLoginTime(@Param("ids") List<String> ids, @Param("loginTime") LocalDateTime loginTime);
    
    // 使用EntityGraph避免N+1问题
    @EntityGraph(attributePaths = {"roles", "profile"})
    @Query("SELECT u FROM User u WHERE u.active = true")
    List<User> findActiveUsersWithRolesAndProfile();
    
    // 自定义查询方法
    public List<User> findUsersWithCustomCriteria(UserSearchCriteria criteria) {
        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        CriteriaQuery<User> query = cb.createQuery(User.class);
        Root<User> root = query.from(User.class);
        
        List<Predicate> predicates = new ArrayList<>();
        
        if (criteria.getUsername() != null) {
            predicates.add(cb.like(root.get("username"), "%" + criteria.getUsername() + "%"));
        }
        
        if (criteria.getEmail() != null) {
            predicates.add(cb.like(root.get("email"), "%" + criteria.getEmail() + "%"));
        }
        
        if (criteria.getActive() != null) {
            predicates.add(cb.equal(root.get("active"), criteria.getActive()));
        }
        
        if (criteria.getCreatedAfter() != null) {
            predicates.add(cb.greaterThanOrEqualTo(root.get("createdAt"), criteria.getCreatedAfter()));
        }
        
        query.where(predicates.toArray(new Predicate[0]));
        query.orderBy(cb.desc(root.get("createdAt")));
        
        return entityManager.createQuery(query)
                .setMaxResults(criteria.getLimit())
                .getResultList();
    }
}

// 用户搜索条件
@Data
@Builder
public class UserSearchCriteria {
    private String username;
    private String email;
    private Boolean active;
    private LocalDateTime createdAfter;
    private int limit = 100;
}

// 用户摘要DTO
@Data
@AllArgsConstructor
public class UserSummaryDto {
    private String id;
    private String username;
    private String email;
}

5.3 缓存优化

// 缓存配置
@Configuration
@EnableCaching
public class CacheConfig {
    
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofHours(1))
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()))
                .disableCachingNullValues();
        
        Map<String, RedisCacheConfiguration> cacheConfigurations = new HashMap<>();
        
        // 用户缓存 - 1小时
        cacheConfigurations.put("users", config.entryTtl(Duration.ofHours(1)));
        
        // 配置缓存 - 24小时
        cacheConfigurations.put("config", config.entryTtl(Duration.ofHours(24)));
        
        // 短期缓存 - 5分钟
        cacheConfigurations.put("shortTerm", config.entryTtl(Duration.ofMinutes(5)));
        
        return RedisCacheManager.builder(redisConnectionFactory)
                .cacheDefaults(config)
                .withInitialCacheConfigurations(cacheConfigurations)
                .build();
    }
    
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
        
        // 设置序列化器
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
        
        template.afterPropertiesSet();
        return template;
    }
    
    @Bean
    public CacheErrorHandler cacheErrorHandler() {
        return new SimpleCacheErrorHandler();
    }
}

// 缓存服务
@Service
public class CachedUserService {
    
    private final UserRepository userRepository;
    private final RedisTemplate<String, Object> redisTemplate;
    
    public CachedUserService(UserRepository userRepository, RedisTemplate<String, Object> redisTemplate) {
        this.userRepository = userRepository;
        this.redisTemplate = redisTemplate;
    }
    
    @Cacheable(value = "users", key = "#id")
    public User getUserById(String id) {
        return userRepository.findById(id)
                .orElseThrow(() -> new UserNotFoundException("用户不存在: " + id));
    }
    
    @Cacheable(value = "users", key = "'username:' + #username")
    public User getUserByUsername(String username) {
        return userRepository.findByUsername(username)
                .orElseThrow(() -> new UserNotFoundException("用户不存在: " + username));
    }
    
    @CacheEvict(value = "users", key = "#user.id")
    public User updateUser(User user) {
        return userRepository.save(user);
    }
    
    @CacheEvict(value = "users", key = "#id")
    public void deleteUser(String id) {
        userRepository.deleteById(id);
    }
    
    @Caching(evict = {
            @CacheEvict(value = "users", key = "#user.id"),
            @CacheEvict(value = "users", key = "'username:' + #user.username")
    })
    public User updateUserWithUsernameChange(User user) {
        return userRepository.save(user);
    }
    
    // 批量缓存预热
    public void warmUpCache() {
        List<User> activeUsers = userRepository.findByActiveTrue(PageRequest.of(0, 100)).getContent();
        
        activeUsers.forEach(user -> {
            redisTemplate.opsForValue().set(
                    "users::" + user.getId(),
                    user,
                    Duration.ofHours(1)
            );
        });
    }
    
    // 自定义缓存逻辑
    public List<User> getUsersByIds(List<String> ids) {
        List<String> cacheKeys = ids.stream()
                .map(id -> "users::" + id)
                .collect(Collectors.toList());
        
        List<Object> cachedUsers = redisTemplate.opsForValue().multiGet(cacheKeys);
        
        List<User> result = new ArrayList<>();
        List<String> missedIds = new ArrayList<>();
        
        for (int i = 0; i < ids.size(); i++) {
            if (cachedUsers.get(i) != null) {
                result.add((User) cachedUsers.get(i));
            } else {
                missedIds.add(ids.get(i));
            }
        }
        
        // 查询缓存未命中的数据
        if (!missedIds.isEmpty()) {
            List<User> dbUsers = userRepository.findByIds(missedIds);
            result.addAll(dbUsers);
            
            // 将查询结果放入缓存
            dbUsers.forEach(user -> {
                redisTemplate.opsForValue().set(
                        "users::" + user.getId(),
                        user,
                        Duration.ofHours(1)
                );
            });
        }
        
        return result;
    }
}

// 缓存监控
@Component
public class CacheMonitor {
    
    private final MeterRegistry meterRegistry;
    private final CacheManager cacheManager;
    
    public CacheMonitor(MeterRegistry meterRegistry, CacheManager cacheManager) {
        this.meterRegistry = meterRegistry;
        this.cacheManager = cacheManager;
    }
    
    @EventListener
    public void handleCacheGetEvent(CacheGetEvent event) {
        Timer.Sample sample = Timer.start(meterRegistry);
        sample.stop(Timer.builder("cache.get")
                .tag("cache", event.getCacheName())
                .tag("result", event.getResult() != null ? "hit" : "miss")
                .register(meterRegistry));
    }
    
    @Scheduled(fixedRate = 60000) // 每分钟执行一次
    public void reportCacheStatistics() {
        cacheManager.getCacheNames().forEach(cacheName -> {
            Cache cache = cacheManager.getCache(cacheName);
            if (cache instanceof RedisCache) {
                // 记录缓存统计信息
                Gauge.builder("cache.size")
                        .tag("cache", cacheName)
                        .register(meterRegistry, cache, this::getCacheSize);
            }
        });
    }
    
    private double getCacheSize(Cache cache) {
        // 实现获取缓存大小的逻辑
        return 0;
    }
}

5.4 异步处理优化

// 异步配置
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
    
    @Override
    @Bean(name = "taskExecutor")
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(50);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("Async-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.setWaitForTasksToCompleteOnShutdown(true);
        executor.setAwaitTerminationSeconds(60);
        executor.initialize();
        return executor;
    }
    
    @Bean(name = "emailExecutor")
    public Executor emailExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(20);
        executor.setQueueCapacity(50);
        executor.setThreadNamePrefix("Email-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
        executor.initialize();
        return executor;
    }
    
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new CustomAsyncExceptionHandler();
    }
}

// 自定义异步异常处理器
public class CustomAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
    
    private static final Logger logger = LoggerFactory.getLogger(CustomAsyncExceptionHandler.class);
    
    @Override
    public void handleUncaughtException(Throwable throwable, Method method, Object... objects) {
        logger.error("异步方法执行异常 - 方法: {}, 参数: {}", method.getName(), Arrays.toString(objects), throwable);
        
        // 可以在这里添加告警逻辑
        // alertService.sendAlert("异步任务执行失败", throwable.getMessage());
    }
}

// 异步服务
@Service
public class AsyncUserService {
    
    private final UserRepository userRepository;
    private final EmailService emailService;
    private final AuditService auditService;
    
    public AsyncUserService(UserRepository userRepository, EmailService emailService, AuditService auditService) {
        this.userRepository = userRepository;
        this.emailService = emailService;
        this.auditService = auditService;
    }
    
    @Async("taskExecutor")
    public CompletableFuture<User> createUserAsync(CreateUserRequest request) {
        try {
            User user = new User();
            user.setUsername(request.getUsername());
            user.setEmail(request.getEmail());
            user.setPassword(request.getPassword());
            user.setCreatedAt(LocalDateTime.now());
            
            User savedUser = userRepository.save(user);
            
            // 异步发送欢迎邮件
            sendWelcomeEmailAsync(savedUser);
            
            // 异步记录审计日志
            auditUserCreationAsync(savedUser);
            
            return CompletableFuture.completedFuture(savedUser);
        } catch (Exception e) {
            return CompletableFuture.failedFuture(e);
        }
    }
    
    @Async("emailExecutor")
    public void sendWelcomeEmailAsync(User user) {
        try {
            emailService.sendWelcomeEmail(user.getEmail(), user.getUsername());
        } catch (Exception e) {
            // 邮件发送失败不应该影响用户创建
            logger.error("发送欢迎邮件失败: {}", user.getEmail(), e);
        }
    }
    
    @Async("taskExecutor")
    public void auditUserCreationAsync(User user) {
        auditService.logUserCreation(user.getId(), user.getUsername());
    }
    
    @Async("taskExecutor")
    public CompletableFuture<List<User>> batchProcessUsersAsync(List<String> userIds) {
        List<CompletableFuture<User>> futures = userIds.stream()
                .map(this::processUserAsync)
                .collect(Collectors.toList());
        
        return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
                .thenApply(v -> futures.stream()
                        .map(CompletableFuture::join)
                        .collect(Collectors.toList()));
    }
    
    @Async("taskExecutor")
    public CompletableFuture<User> processUserAsync(String userId) {
        return CompletableFuture.supplyAsync(() -> {
            User user = userRepository.findById(userId)
                    .orElseThrow(() -> new UserNotFoundException("用户不存在: " + userId));
            
            // 执行一些处理逻辑
            user.setLastProcessedAt(LocalDateTime.now());
            
            return userRepository.save(user);
        });
    }
    
    // 异步方法组合
    public CompletableFuture<UserProcessResult> processUserWithNotification(String userId) {
        return processUserAsync(userId)
                .thenCompose(user -> {
                    CompletableFuture<Void> emailFuture = CompletableFuture.runAsync(
                            () -> emailService.sendProcessingNotification(user.getEmail()),
                            emailExecutor
                    );
                    
                    CompletableFuture<Void> auditFuture = CompletableFuture.runAsync(
                            () -> auditService.logUserProcessing(user.getId()),
                            taskExecutor
                    );
                    
                    return CompletableFuture.allOf(emailFuture, auditFuture)
                            .thenApply(v -> new UserProcessResult(user, true));
                })
                .exceptionally(throwable -> {
                    logger.error("用户处理失败: {}", userId, throwable);
                    return new UserProcessResult(null, false);
                });
    }
}

// 用户处理结果
@Data
@AllArgsConstructor
public class UserProcessResult {
    private User user;
    private boolean success;
}

6. 监控和健康检查

6.1 Actuator监控

// 自定义健康检查
@Component
public class CustomHealthIndicator implements HealthIndicator {
    
    private final UserRepository userRepository;
    private final RedisTemplate<String, Object> redisTemplate;
    
    public CustomHealthIndicator(UserRepository userRepository, RedisTemplate<String, Object> redisTemplate) {
        this.userRepository = userRepository;
        this.redisTemplate = redisTemplate;
    }
    
    @Override
    public Health health() {
        try {
            // 检查数据库连接
            long userCount = userRepository.count();
            
            // 检查Redis连接
            redisTemplate.opsForValue().set("health:check", "ok", Duration.ofSeconds(10));
            String redisCheck = (String) redisTemplate.opsForValue().get("health:check");
            
            if ("ok".equals(redisCheck)) {
                return Health.up()
                        .withDetail("database", "可用")
                        .withDetail("redis", "可用")
                        .withDetail("userCount", userCount)
                        .withDetail("timestamp", LocalDateTime.now())
                        .build();
            } else {
                return Health.down()
                        .withDetail("database", "可用")
                        .withDetail("redis", "不可用")
                        .withDetail("reason", "Redis连接测试失败")
                        .build();
            }
            
        } catch (Exception e) {
            return Health.down()
                    .withDetail("error", e.getMessage())
                    .withDetail("timestamp", LocalDateTime.now())
                    .build();
        }
    }
}

// 自定义指标
@Component
public class CustomMetrics {
    
    private final MeterRegistry meterRegistry;
    private final Counter userCreationCounter;
    private final Timer userProcessingTimer;
    private final Gauge activeUsersGauge;
    
    public CustomMetrics(MeterRegistry meterRegistry, UserRepository userRepository) {
        this.meterRegistry = meterRegistry;
        
        this.userCreationCounter = Counter.builder("user.creation.total")
                .description("用户创建总数")
                .register(meterRegistry);
        
        this.userProcessingTimer = Timer.builder("user.processing.duration")
                .description("用户处理耗时")
                .register(meterRegistry);
        
        this.activeUsersGauge = Gauge.builder("user.active.count")
                .description("活跃用户数量")
                .register(meterRegistry, userRepository, this::getActiveUserCount);
    }
    
    public void incrementUserCreation(String userType) {
        userCreationCounter.increment(Tags.of("type", userType));
    }
    
    public Timer.Sample startUserProcessingTimer() {
        return Timer.start(meterRegistry);
    }
    
    public void recordUserProcessingTime(Timer.Sample sample, String operation) {
        sample.stop(Timer.builder("user.processing.duration")
                .tag("operation", operation)
                .register(meterRegistry));
    }
    
    private double getActiveUserCount(UserRepository userRepository) {
        return userRepository.countByActiveTrue();
    }
    
    @EventListener
    public void handleUserCreatedEvent(UserCreatedEvent event) {
        incrementUserCreation(event.getUserType());
    }
}

// 自定义端点
@Component
@Endpoint(id = "custom")
public class CustomEndpoint {
    
    private final UserRepository userRepository;
    private final CacheManager cacheManager;
    
    public CustomEndpoint(UserRepository userRepository, CacheManager cacheManager) {
        this.userRepository = userRepository;
        this.cacheManager = cacheManager;
    }
    
    @ReadOperation
    public Map<String, Object> customInfo() {
        Map<String, Object> info = new HashMap<>();
        
        // 用户统计
        Map<String, Object> userStats = new HashMap<>();
        userStats.put("total", userRepository.count());
        userStats.put("active", userRepository.countByActiveTrue());
        userStats.put("inactive", userRepository.countByActiveFalse());
        info.put("users", userStats);
        
        // 缓存统计
        Map<String, Object> cacheStats = new HashMap<>();
        cacheManager.getCacheNames().forEach(cacheName -> {
            Cache cache = cacheManager.getCache(cacheName);
            if (cache != null) {
                cacheStats.put(cacheName, "available");
            }
        });
        info.put("caches", cacheStats);
        
        // 系统信息
        Map<String, Object> systemInfo = new HashMap<>();
        systemInfo.put("timestamp", LocalDateTime.now());
        systemInfo.put("jvm.memory.used", Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory());
        systemInfo.put("jvm.memory.total", Runtime.getRuntime().totalMemory());
        systemInfo.put("jvm.memory.max", Runtime.getRuntime().maxMemory());
        info.put("system", systemInfo);
        
        return info;
    }
    
    @WriteOperation
    public Map<String, String> clearCache(@Selector String cacheName) {
        Cache cache = cacheManager.getCache(cacheName);
        if (cache != null) {
            cache.clear();
            return Map.of("status", "success", "message", "缓存 " + cacheName + " 已清空");
        } else {
            return Map.of("status", "error", "message", "缓存 " + cacheName + " 不存在");
        }
    }
}

// 应用信息贡献者
@Component
public class CustomInfoContributor implements InfoContributor {
    
    @Override
    public void contribute(Info.Builder builder) {
        Map<String, Object> appInfo = new HashMap<>();
        appInfo.put("name", "Spring Boot进阶示例");
        appInfo.put("version", "1.0.0");
        appInfo.put("description", "Spring Boot进阶特性演示应用");
        appInfo.put("build-time", LocalDateTime.now());
        
        builder.withDetail("app", appInfo);
    }
}

6.2 日志和审计

// 审计配置
@Configuration
@EnableJpaAuditing
public class AuditConfig {
    
    @Bean
    public AuditorAware<String> auditorProvider() {
        return new SpringSecurityAuditorAware();
    }
}

// Spring Security审计提供者
public class SpringSecurityAuditorAware implements AuditorAware<String> {
    
    @Override
    public Optional<String> getCurrentAuditor() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        
        if (authentication == null || !authentication.isAuthenticated() || 
            authentication instanceof AnonymousAuthenticationToken) {
            return Optional.of("system");
        }
        
        return Optional.of(authentication.getName());
    }
}

// 审计实体基类
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class AuditableEntity {
    
    @CreatedDate
    @Column(name = "created_at", nullable = false, updatable = false)
    private LocalDateTime createdAt;
    
    @LastModifiedDate
    @Column(name = "updated_at")
    private LocalDateTime updatedAt;
    
    @CreatedBy
    @Column(name = "created_by", updatable = false)
    private String createdBy;
    
    @LastModifiedBy
    @Column(name = "updated_by")
    private String updatedBy;
    
    // getters and setters
    public LocalDateTime getCreatedAt() { return createdAt; }
    public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
    
    public LocalDateTime getUpdatedAt() { return updatedAt; }
    public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; }
    
    public String getCreatedBy() { return createdBy; }
    public void setCreatedBy(String createdBy) { this.createdBy = createdBy; }
    
    public String getUpdatedBy() { return updatedBy; }
    public void setUpdatedBy(String updatedBy) { this.updatedBy = updatedBy; }
}

// 审计日志实体
@Entity
@Table(name = "audit_logs")
public class AuditLog {
    
    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    private String id;
    
    @Column(name = "entity_type", nullable = false)
    private String entityType;
    
    @Column(name = "entity_id", nullable = false)
    private String entityId;
    
    @Column(name = "operation", nullable = false)
    @Enumerated(EnumType.STRING)
    private AuditOperation operation;
    
    @Column(name = "old_values", columnDefinition = "TEXT")
    private String oldValues;
    
    @Column(name = "new_values", columnDefinition = "TEXT")
    private String newValues;
    
    @Column(name = "user_id")
    private String userId;
    
    @Column(name = "ip_address")
    private String ipAddress;
    
    @Column(name = "user_agent")
    private String userAgent;
    
    @Column(name = "timestamp", nullable = false)
    private LocalDateTime timestamp;
    
    // constructors, getters and setters
    public AuditLog() {}
    
    public AuditLog(String entityType, String entityId, AuditOperation operation, 
                   String oldValues, String newValues, String userId, 
                   String ipAddress, String userAgent) {
        this.entityType = entityType;
        this.entityId = entityId;
        this.operation = operation;
        this.oldValues = oldValues;
        this.newValues = newValues;
        this.userId = userId;
        this.ipAddress = ipAddress;
        this.userAgent = userAgent;
        this.timestamp = LocalDateTime.now();
    }
    
    // getters and setters...
}

// 审计操作枚举
public enum AuditOperation {
    CREATE, UPDATE, DELETE, LOGIN, LOGOUT, ACCESS
}

// 审计服务
@Service
@Transactional
public class AuditService {
    
    private final AuditLogRepository auditLogRepository;
    private final ObjectMapper objectMapper;
    
    public AuditService(AuditLogRepository auditLogRepository, ObjectMapper objectMapper) {
        this.auditLogRepository = auditLogRepository;
        this.objectMapper = objectMapper;
    }
    
    public void logUserCreation(String userId, String username) {
        try {
            Map<String, Object> newValues = Map.of(
                    "id", userId,
                    "username", username,
                    "action", "created"
            );
            
            AuditLog auditLog = new AuditLog(
                    "User",
                    userId,
                    AuditOperation.CREATE,
                    null,
                    objectMapper.writeValueAsString(newValues),
                    getCurrentUserId(),
                    getCurrentIpAddress(),
                    getCurrentUserAgent()
            );
            
            auditLogRepository.save(auditLog);
        } catch (Exception e) {
            // 审计日志失败不应该影响业务操作
            logger.error("记录审计日志失败", e);
        }
    }
    
    public void logUserUpdate(String userId, Object oldUser, Object newUser) {
        try {
            String oldValues = objectMapper.writeValueAsString(oldUser);
            String newValues = objectMapper.writeValueAsString(newUser);
            
            AuditLog auditLog = new AuditLog(
                    "User",
                    userId,
                    AuditOperation.UPDATE,
                    oldValues,
                    newValues,
                    getCurrentUserId(),
                    getCurrentIpAddress(),
                    getCurrentUserAgent()
            );
            
            auditLogRepository.save(auditLog);
        } catch (Exception e) {
            logger.error("记录审计日志失败", e);
        }
    }
    
    public void logUserAccess(String userId, String resource) {
        try {
            Map<String, Object> accessInfo = Map.of(
                    "userId", userId,
                    "resource", resource,
                    "timestamp", LocalDateTime.now()
            );
            
            AuditLog auditLog = new AuditLog(
                    "Access",
                    userId,
                    AuditOperation.ACCESS,
                    null,
                    objectMapper.writeValueAsString(accessInfo),
                    userId,
                    getCurrentIpAddress(),
                    getCurrentUserAgent()
            );
            
            auditLogRepository.save(auditLog);
        } catch (Exception e) {
            logger.error("记录访问日志失败", e);
        }
    }
    
    private String getCurrentUserId() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        return authentication != null ? authentication.getName() : "anonymous";
    }
    
    private String getCurrentIpAddress() {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        if (requestAttributes instanceof ServletRequestAttributes) {
            HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
            return getClientIpAddress(request);
        }
        return "unknown";
    }
    
    private String getCurrentUserAgent() {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        if (requestAttributes instanceof ServletRequestAttributes) {
            HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
            return request.getHeader("User-Agent");
        }
        return "unknown";
    }
    
    private String getClientIpAddress(HttpServletRequest request) {
        String[] headers = {
                "X-Forwarded-For",
                "X-Real-IP",
                "Proxy-Client-IP",
                "WL-Proxy-Client-IP",
                "HTTP_X_FORWARDED_FOR",
                "HTTP_X_FORWARDED",
                "HTTP_X_CLUSTER_CLIENT_IP",
                "HTTP_CLIENT_IP",
                "HTTP_FORWARDED_FOR",
                "HTTP_FORWARDED",
                "HTTP_VIA",
                "REMOTE_ADDR"
        };
        
        for (String header : headers) {
            String ip = request.getHeader(header);
            if (ip != null && !ip.isEmpty() && !"unknown".equalsIgnoreCase(ip)) {
                return ip.split(",")[0].trim();
            }
        }
        
        return request.getRemoteAddr();
    }
}

7. Spring Boot最佳实践

7.1 项目结构最佳实践

src/
├── main/
│   ├── java/
│   │   └── com/example/myapp/
│   │       ├── MyAppApplication.java          # 主启动类
│   │       ├── config/                        # 配置类
│   │       │   ├── DatabaseConfig.java
│   │       │   ├── SecurityConfig.java
│   │       │   ├── CacheConfig.java
│   │       │   └── AsyncConfig.java
│   │       ├── controller/                    # 控制器
│   │       │   ├── UserController.java
│   │       │   └── AuthController.java
│   │       ├── service/                       # 服务层
│   │       │   ├── UserService.java
│   │       │   ├── impl/
│   │       │   │   └── UserServiceImpl.java
│   │       │   └── EmailService.java
│   │       ├── repository/                    # 数据访问层
│   │       │   ├── UserRepository.java
│   │       │   └── AuditLogRepository.java
│   │       ├── entity/                        # 实体类
│   │       │   ├── User.java
│   │       │   ├── AuditLog.java
│   │       │   └── base/
│   │       │       └── AuditableEntity.java
│   │       ├── dto/                          # 数据传输对象
│   │       │   ├── request/
│   │       │   │   ├── CreateUserRequest.java
│   │       │   │   └── UpdateUserRequest.java
│   │       │   ├── response/
│   │       │   │   ├── UserResponse.java
│   │       │   │   └── ApiResponse.java
│   │       │   └── UserDto.java
│   │       ├── exception/                     # 异常处理
│   │       │   ├── GlobalExceptionHandler.java
│   │       │   ├── UserNotFoundException.java
│   │       │   └── BusinessException.java
│   │       ├── security/                      # 安全相关
│   │       │   ├── JwtTokenUtil.java
│   │       │   ├── JwtAuthenticationFilter.java
│   │       │   └── CustomUserDetailsService.java
│   │       ├── util/                         # 工具类
│   │       │   ├── DateUtil.java
│   │       │   ├── ValidationUtil.java
│   │       │   └── JsonUtil.java
│   │       ├── constant/                     # 常量
│   │       │   ├── ApiConstants.java
│   │       │   └── ErrorCodes.java
│   │       └── event/                        # 事件
│   │           ├── UserCreatedEvent.java
│   │           └── UserEventListener.java
│   └── resources/
│       ├── application.yml                   # 主配置文件
│       ├── application-dev.yml               # 开发环境配置
│       ├── application-prod.yml              # 生产环境配置
│       ├── application-test.yml              # 测试环境配置
│       ├── db/migration/                     # 数据库迁移脚本
│       │   ├── V1__Create_users_table.sql
│       │   └── V2__Create_audit_logs_table.sql
│       ├── static/                           # 静态资源
│       └── templates/                        # 模板文件
└── test/
    ├── java/
    │   └── com/example/myapp/
    │       ├── controller/                   # 控制器测试
    │       │   └── UserControllerTest.java
    │       ├── service/                      # 服务测试
    │       │   └── UserServiceTest.java
    │       ├── repository/                   # 仓库测试
    │       │   └── UserRepositoryTest.java
    │       ├── integration/                  # 集成测试
    │       │   └── UserIntegrationTest.java
    │       └── config/                       # 测试配置
    │           └── TestConfig.java
    └── resources/
        ├── application-test.yml              # 测试配置
        └── test-data.sql                     # 测试数据

7.2 编码最佳实践

// 1. 使用构造器注入而不是字段注入
@Service
public class UserService {
    
    private final UserRepository userRepository;
    private final PasswordEncoder passwordEncoder;
    private final EmailService emailService;
    
    // 推荐:构造器注入
    public UserService(UserRepository userRepository, 
                      PasswordEncoder passwordEncoder,
                      EmailService emailService) {
        this.userRepository = userRepository;
        this.passwordEncoder = passwordEncoder;
        this.emailService = emailService;
    }
}

// 2. 使用@ConfigurationProperties而不是@Value
@ConfigurationProperties(prefix = "app.security")
@Data
public class SecurityProperties {
    private String jwtSecret;
    private int jwtExpiration;
    private List<String> allowedOrigins;
}

// 3. 使用专门的DTO类进行数据传输
@Data
@Builder
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class UserResponse {
    private String id;
    private String username;
    private String email;
    private LocalDateTime createdAt;
    private boolean active;
    
    public static UserResponse from(User user) {
        return UserResponse.builder()
                .id(user.getId())
                .username(user.getUsername())
                .email(user.getEmail())
                .createdAt(user.getCreatedAt())
                .active(user.isActive())
                .build();
    }
}

// 4. 使用统一的API响应格式
@Data
@Builder
public class ApiResponse<T> {
    private boolean success;
    private String message;
    private T data;
    private String timestamp;
    private String path;
    
    public static <T> ApiResponse<T> success(T data) {
        return ApiResponse.<T>builder()
                .success(true)
                .data(data)
                .timestamp(LocalDateTime.now().toString())
                .build();
    }
    
    public static <T> ApiResponse<T> error(String message) {
        return ApiResponse.<T>builder()
                .success(false)
                .message(message)
                .timestamp(LocalDateTime.now().toString())
                .build();
    }
}

// 5. 使用自定义异常和全局异常处理
@RestControllerAdvice
public class GlobalExceptionHandler {
    
    @ExceptionHandler(UserNotFoundException.class)
    public ResponseEntity<ApiResponse<Void>> handleUserNotFound(UserNotFoundException e) {
        return ResponseEntity.status(HttpStatus.NOT_FOUND)
                .body(ApiResponse.error(e.getMessage()));
    }
    
    @ExceptionHandler(ValidationException.class)
    public ResponseEntity<ApiResponse<Map<String, String>>> handleValidation(ValidationException e) {
        return ResponseEntity.status(HttpStatus.BAD_REQUEST)
                .body(ApiResponse.error(e.getMessage()));
    }
    
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ApiResponse<Void>> handleGeneral(Exception e) {
        logger.error("未处理的异常", e);
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(ApiResponse.error("系统内部错误"));
    }
}

// 6. 使用事务注解的最佳实践
@Service
@Transactional(readOnly = true) // 类级别设置只读事务
public class UserService {
    
    @Transactional // 写操作覆盖为读写事务
    public User createUser(CreateUserRequest request) {
        // 创建用户逻辑
    }
    
    @Transactional(rollbackFor = Exception.class) // 指定回滚异常
    public User updateUser(String id, UpdateUserRequest request) {
        // 更新用户逻辑
    }
    
    // 查询方法继承类级别的只读事务
    public User getUserById(String id) {
        // 查询用户逻辑
    }
}

// 7. 使用验证注解
@Data
@Builder
public class CreateUserRequest {
    
    @NotBlank(message = "用户名不能为空")
    @Size(min = 3, max = 20, message = "用户名长度必须在3-20之间")
    @Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "用户名只能包含字母、数字和下划线")
    private String username;
    
    @NotBlank(message = "邮箱不能为空")
    @Email(message = "邮箱格式不正确")
    private String email;
    
    @NotBlank(message = "密码不能为空")
    @Size(min = 8, message = "密码长度至少8位")
    @Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).*$", 
             message = "密码必须包含大小写字母和数字")
    private String password;
}

// 8. 使用分页和排序
@RestController
@RequestMapping("/api/users")
public class UserController {
    
    @GetMapping
    public ResponseEntity<ApiResponse<Page<UserResponse>>> getUsers(
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "20") int size,
            @RequestParam(defaultValue = "createdAt") String sort,
            @RequestParam(defaultValue = "desc") String direction) {
        
        Sort.Direction sortDirection = "desc".equalsIgnoreCase(direction) 
                ? Sort.Direction.DESC : Sort.Direction.ASC;
        
        Pageable pageable = PageRequest.of(page, size, Sort.by(sortDirection, sort));
        
        Page<User> users = userService.getUsers(pageable);
        Page<UserResponse> response = users.map(UserResponse::from);
        
        return ResponseEntity.ok(ApiResponse.success(response));
    }
}

本章小结

Spring Boot进阶技术对比

技术特性 适用场景 优势 注意事项
自动配置 快速开发 减少配置代码 理解配置原理
自定义Starter 组件复用 标准化组件 版本兼容性
缓存优化 高并发场景 提升性能 缓存一致性
异步处理 耗时操作 提高响应速度 异常处理
监控指标 生产环境 运维可观测性 性能开销

性能优化要点

  1. JVM调优:合理设置堆内存、选择合适的垃圾收集器
  2. 数据库优化:连接池配置、查询优化、索引设计
  3. 缓存策略:多级缓存、缓存预热、缓存穿透防护
  4. 异步处理:合理使用线程池、避免阻塞操作
  5. 监控告警:关键指标监控、异常告警机制

安全考虑

  1. 配置安全:敏感信息加密、环境变量管理
  2. 接口安全:认证授权、输入验证、速率限制
  3. 数据安全:SQL注入防护、XSS防护、CSRF防护
  4. 审计日志:操作记录、访问日志、异常追踪

开发建议

  1. 遵循约定:使用Spring Boot约定优于配置的理念
  2. 模块化设计:合理划分模块、降低耦合度
  3. 测试驱动:编写充分的单元测试和集成测试
  4. 文档完善:API文档、部署文档、运维文档
  5. 持续集成:自动化构建、测试、部署流程

下一章预告

下一章我们将学习分布式系统设计,包括:

  • 分布式系统理论基础
  • 分布式事务处理
  • 分布式锁实现
  • 分布式缓存设计
  • 服务治理和监控

练习题

  1. 自定义Starter开发:开发一个短信发送的Starter,支持多个服务提供商
  2. 性能优化实践:对现有项目进行性能分析和优化
  3. 监控系统搭建:集成Prometheus和Grafana进行应用监控
  4. 自动化测试:编写完整的测试用例,包括单元测试和集成测试
  5. 生产部署:使用Docker和Kubernetes部署Spring Boot应用 “`