本章目标
- 深入理解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 | 组件复用 | 标准化组件 | 版本兼容性 |
缓存优化 | 高并发场景 | 提升性能 | 缓存一致性 |
异步处理 | 耗时操作 | 提高响应速度 | 异常处理 |
监控指标 | 生产环境 | 运维可观测性 | 性能开销 |
性能优化要点
- JVM调优:合理设置堆内存、选择合适的垃圾收集器
- 数据库优化:连接池配置、查询优化、索引设计
- 缓存策略:多级缓存、缓存预热、缓存穿透防护
- 异步处理:合理使用线程池、避免阻塞操作
- 监控告警:关键指标监控、异常告警机制
安全考虑
- 配置安全:敏感信息加密、环境变量管理
- 接口安全:认证授权、输入验证、速率限制
- 数据安全:SQL注入防护、XSS防护、CSRF防护
- 审计日志:操作记录、访问日志、异常追踪
开发建议
- 遵循约定:使用Spring Boot约定优于配置的理念
- 模块化设计:合理划分模块、降低耦合度
- 测试驱动:编写充分的单元测试和集成测试
- 文档完善:API文档、部署文档、运维文档
- 持续集成:自动化构建、测试、部署流程
下一章预告
下一章我们将学习分布式系统设计,包括:
- 分布式系统理论基础
- 分布式事务处理
- 分布式锁实现
- 分布式缓存设计
- 服务治理和监控
练习题
- 自定义Starter开发:开发一个短信发送的Starter,支持多个服务提供商
- 性能优化实践:对现有项目进行性能分析和优化
- 监控系统搭建:集成Prometheus和Grafana进行应用监控
- 自动化测试:编写完整的测试用例,包括单元测试和集成测试
- 生产部署:使用Docker和Kubernetes部署Spring Boot应用 “`