Appearance
Spring Boot 生产环境应用打包指南
概述
当你的 Spring Boot 应用程序准备好进行生产部署时,有许多选项可以用来打包和优化应用程序。本指南将深入探讨这些打包策略,帮助你选择最适合的生产环境部署方案。
IMPORTANT
生产环境的打包不仅仅是简单的编译,还涉及性能优化、安全配置、监控集成等多个方面。
为什么需要专门的生产打包策略?
在开发环境中,我们通常关注的是快速启动和调试便利性。但在生产环境中,我们需要考虑:
- 性能优化:最小化启动时间和内存占用
- 安全性:移除开发工具和调试信息
- 可观测性:集成监控和健康检查
- 部署便利性:创建独立可执行的包
主要打包选项
1. Fat JAR(胖 JAR 包)
这是 Spring Boot 最常见的打包方式,将所有依赖都打包到一个可执行的 JAR 文件中。
kotlin
import org.springframework.boot.gradle.tasks.bundling.BootJar
plugins {
id("org.springframework.boot") version "3.2.0"
id("io.spring.dependency-management") version "1.1.4"
kotlin("jvm") version "1.9.20"
kotlin("plugin.spring") version "1.9.20"
}
// 配置 Fat JAR 打包
tasks.getByName<BootJar>("bootJar") {
// 设置主类
mainClass.set("com.example.MyApplicationKt")
// 自定义 JAR 文件名
archiveFileName.set("my-app-${version}.jar")
// 排除不需要的文件
exclude("**/*.properties.example")
}
// 禁用普通 JAR 任务,只保留 Fat JAR
tasks.getByName<Jar>("jar") {
enabled = false
archiveClassifier = "" // 避免冲突
}
java
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<!-- 指定主类 -->
<mainClass>com.example.MyApplication</mainClass>
<!-- 设置可执行 JAR -->
<executable>true</executable>
<!-- 排除不需要的依赖 -->
<excludes>
<exclude>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
2. 分层 JAR(Layered JAR)
分层 JAR 是为了优化 Docker 镜像构建而设计的,将不同类型的内容分层存储。
kotlin
// build.gradle.kts
tasks.getByName<BootJar>("bootJar") {
// 启用分层功能
layered {
// 自定义分层策略
application {
intoLayer("spring-boot-loader") {
include("org/springframework/boot/loader/**")
}
intoLayer("application")
}
dependencies {
intoLayer("dependencies")
}
layerOrder = listOf("dependencies", "spring-boot-loader", "application")
}
}
3. Docker 镜像打包
利用 Spring Boot 的 Docker 集成功能直接构建 Docker 镜像。
kotlin
// build.gradle.kts
tasks.bootBuildImage {
// 设置镜像名称
imageName = "mycompany/my-app:${version}"
// 设置基础镜像
builder = "paketobuildpacks/builder:base"
// 添加环境变量
environment = mapOf(
"BP_JVM_VERSION" to "17",
"BPL_JVM_THREAD_COUNT" to "50"
)
// 设置标签
tags = listOf("mycompany/my-app:latest")
}
实际业务应用示例
让我们通过一个电商订单服务的例子来演示生产打包:
场景:电商订单服务
kotlin
@SpringBootApplication
@EnableJpaRepositories
@EnableScheduling
class OrderServiceApplication
fun main(args: Array<String>) {
runApplication<OrderServiceApplication>(*args)
}
@RestController
@RequestMapping("/api/orders")
class OrderController(
private val orderService: OrderService
) {
@PostMapping
fun createOrder(@RequestBody orderRequest: OrderRequest): ResponseEntity<OrderResponse> {
// 创建订单逻辑
val order = orderService.createOrder(orderRequest)
return ResponseEntity.ok(OrderResponse.from(order))
}
@GetMapping("/{orderId}")
fun getOrder(@PathVariable orderId: Long): ResponseEntity<OrderResponse> {
// 查询订单逻辑
val order = orderService.findById(orderId)
return ResponseEntity.ok(OrderResponse.from(order))
}
}
@Service
@Transactional
class OrderService(
private val orderRepository: OrderRepository,
private val inventoryService: InventoryService
) {
fun createOrder(request: OrderRequest): Order {
// 检查库存
inventoryService.checkStock(request.productId, request.quantity)
// 创建订单
val order = Order(
productId = request.productId,
quantity = request.quantity,
customerId = request.customerId,
status = OrderStatus.PENDING
)
return orderRepository.save(order)
}
}
生产环境配置
kotlin
// application-prod.yml 配置
@ConfigurationProperties(prefix = "app.order")
@Component
data class OrderConfig(
var maxRetries: Int = 3,
var timeoutSeconds: Long = 30,
var batchSize: Int = 100
)
@Configuration
@Profile("prod")
class ProductionConfig {
@Bean
@Primary
fun productionDataSource(): DataSource {
return HikariDataSource().apply {
// 生产数据库连接池配置
maximumPoolSize = 20
minimumIdle = 5
connectionTimeout = 30000
idleTimeout = 600000
maxLifetime = 1800000
}
}
@Bean
fun cacheManager(): CacheManager {
// Redis 缓存配置
return RedisCacheManager.builder(jedisConnectionFactory())
.cacheDefaults(
RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(10))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(GenericJackson2JsonRedisSerializer()))
)
.build()
}
}
Gradle 生产打包配置
kotlin
// build.gradle.kts - 完整的生产打包配置
import org.springframework.boot.gradle.tasks.bundling.BootJar
plugins {
id("org.springframework.boot") version "3.2.0"
id("io.spring.dependency-management") version "1.1.4"
kotlin("jvm") version "1.9.20"
kotlin("plugin.spring") version "1.9.20"
kotlin("plugin.jpa") version "1.9.20"
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-data-redis")
implementation("org.springframework.boot:spring-boot-starter-cache")
// 生产监控依赖
implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("io.micrometer:micrometer-registry-prometheus")
// 数据库驱动
runtimeOnly("mysql:mysql-connector-java")
// 开发工具(生产环境会被排除)
developmentOnly("org.springframework.boot:spring-boot-devtools")
testImplementation("org.springframework.boot:spring-boot-starter-test")
}
// 配置不同环境的打包
tasks.register<BootJar>("bootJarProd") {
archiveClassifier = "prod"
mainClass.set("com.example.order.OrderServiceApplicationKt")
// 排除开发工具
exclude("**/spring-boot-devtools-*.jar")
exclude("**/*.properties.dev")
// 只包含生产配置
from(sourceSets.main.get().output)
from(sourceSets.main.get().resources) {
include("application.yml")
include("application-prod.yml")
exclude("application-dev.yml")
exclude("application-test.yml")
}
// 启用分层
layered {
enabled = true
}
}
// Docker 镜像构建配置
tasks.bootBuildImage {
imageName = "order-service:${version}"
environment = mapOf(
"BP_JVM_VERSION" to "17",
"SPRING_PROFILES_ACTIVE" to "prod",
"BPL_JVM_THREAD_COUNT" to "50",
"BPL_JVM_HEAD_ROOM" to "10"
)
buildpacks = listOf(
"gcr.io/paketo-buildpacks/java",
"gcr.io/paketo-buildpacks/health-checker"
)
}
Spring Boot Actuator 生产监控
为了实现生产环境的可观测性,我们需要集成 Spring Boot Actuator:
基础 Actuator 配置
kotlin
// ActuatorConfig.kt
@Configuration
@ConditionalOnProfile("prod")
class ActuatorConfig {
@Bean
fun healthContributor(): HealthContributor {
return CompositeHealthContributor.fromMap(mapOf(
"database" to DatabaseHealthIndicator(),
"redis" to RedisHealthIndicator(),
"orderService" to OrderServiceHealthIndicator()
))
}
}
@Component
class OrderServiceHealthIndicator(
private val orderService: OrderService
) : HealthIndicator {
override fun health(): Health {
return try {
// 检查服务健康状态
val pendingOrders = orderService.countPendingOrders()
if (pendingOrders > 1000) {
Health.down()
.withDetail("pendingOrders", pendingOrders)
.withDetail("reason", "Too many pending orders")
.build()
} else {
Health.up()
.withDetail("pendingOrders", pendingOrders)
.build()
}
} catch (e: Exception) {
Health.down(e).build()
}
}
}
自定义 Metrics
kotlin
@Component
class OrderMetrics(
private val meterRegistry: MeterRegistry
) {
private val orderCounter = Counter.builder("orders.created")
.description("Total number of orders created")
.register(meterRegistry)
private val orderProcessingTimer = Timer.builder("orders.processing.time")
.description("Order processing time")
.register(meterRegistry)
fun recordOrderCreated() {
orderCounter.increment()
}
fun recordOrderProcessingTime(duration: Duration) {
orderProcessingTimer.record(duration)
}
}
@Service
class OrderService(
private val orderRepository: OrderRepository,
private val orderMetrics: OrderMetrics
) {
@Timed(value = "orders.create", description = "Time taken to create order")
fun createOrder(request: OrderRequest): Order {
val startTime = System.currentTimeMillis()
try {
// 订单创建逻辑
val order = processOrderCreation(request)
// 记录指标
orderMetrics.recordOrderCreated()
return order
} finally {
// 记录处理时间
val processingTime = Duration.ofMillis(System.currentTimeMillis() - startTime)
orderMetrics.recordOrderProcessingTime(processingTime)
}
}
}
生产配置文件
yaml
# application-prod.yml
spring:
profiles:
active: prod
datasource:
url: jdbc:mysql://prod-db:3306/orderdb
username: ${DB_USERNAME}
password: ${DB_PASSWORD}
hikari:
maximum-pool-size: 20
minimum-idle: 5
redis:
host: ${REDIS_HOST}
port: 6379
password: ${REDIS_PASSWORD}
jpa:
hibernate:
ddl-auto: validate
show-sql: false
# Actuator 配置
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
base-path: /actuator
endpoint:
health:
show-details: when-authorized
health:
redis:
enabled: true
db:
enabled: true
metrics:
export:
prometheus:
enabled: true
# 应用配置
server:
port: 8080
tomcat:
max-threads: 200
min-spare-threads: 10
logging:
level:
com.example.order: INFO
org.springframework.web: WARN
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
部署和运行
1. 传统 JAR 部署
bash
# 构建生产 JAR
./gradlew bootJarProd
# 运行应用
java -jar -Dspring.profiles.active=prod \
-Xmx512m -Xms256m \
build/libs/order-service-1.0.0-prod.jar
2. Docker 部署
dockerfile
# Dockerfile
FROM openjdk:17-jre-slim
# 创建应用目录
RUN mkdir /app
WORKDIR /app
# 复制 JAR 文件
COPY build/libs/order-service-*.jar app.jar
# 创建非 root 用户
RUN addgroup --system spring && adduser --system spring --ingroup spring
USER spring:spring
# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=30s --retries=3 \
CMD curl -f http://localhost:8080/actuator/health || exit 1
# 启动应用
ENTRYPOINT ["java", "-jar", "app.jar"]
bash
# 构建 Docker 镜像
./gradlew bootBuildImage
# 运行容器
docker run -d \
--name order-service \
-p 8080:8080 \
-e SPRING_PROFILES_ACTIVE=prod \
-e DB_USERNAME=orderuser \
-e DB_PASSWORD=secret \
order-service:1.0.0
性能优化建议
1. JVM 调优
bash
# 生产环境 JVM 参数示例
java -jar \
-Xms512m -Xmx1024m \
-XX:+UseG1GC \
-XX:MaxGCPauseMillis=200 \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/app/logs/ \
-Dspring.profiles.active=prod \
order-service.jar
2. 应用级优化
kotlin
@Configuration
@EnableCaching
class PerformanceConfig {
@Bean
@ConditionalOnProfile("prod")
fun taskExecutor(): TaskExecutor {
return ThreadPoolTaskExecutor().apply {
corePoolSize = 10
maxPoolSize = 50
queueCapacity = 100
setThreadNamePrefix("order-")
setRejectedExecutionHandler(ThreadPoolExecutor.CallerRunsPolicy())
initialize()
}
}
}
最佳实践总结
TIP
以下是生产打包的核心最佳实践:
安全配置
- 移除开发工具依赖
- 使用环境变量管理敏感信息
- 启用生产级别的日志配置
性能优化
- 合理配置连接池大小
- 启用缓存机制
- 优化 JVM 参数
可观测性
- 集成 Actuator 健康检查
- 配置自定义 Metrics
- 设置适当的日志级别
部署策略
- 使用分层 JAR 优化 Docker 构建
- 配置健康检查端点
- 实现优雅关闭
监控集成
- 集成 Prometheus 指标收集
- 配置应用性能监控
- 设置告警机制
WARNING
在生产环境中,确保:
- 不要包含开发工具依赖
- 敏感信息不要硬编码在配置文件中
- 定期更新依赖版本以修复安全漏洞
通过遵循这些指南,你可以创建一个高效、安全、可监控的 Spring Boot 生产应用程序包。记住,生产打包不是一次性任务,而是需要根据实际运行情况持续优化的过程。