Skip to content

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

以下是生产打包的核心最佳实践:

  1. 安全配置

    • 移除开发工具依赖
    • 使用环境变量管理敏感信息
    • 启用生产级别的日志配置
  2. 性能优化

    • 合理配置连接池大小
    • 启用缓存机制
    • 优化 JVM 参数
  3. 可观测性

    • 集成 Actuator 健康检查
    • 配置自定义 Metrics
    • 设置适当的日志级别
  4. 部署策略

    • 使用分层 JAR 优化 Docker 构建
    • 配置健康检查端点
    • 实现优雅关闭
  5. 监控集成

    • 集成 Prometheus 指标收集
    • 配置应用性能监控
    • 设置告警机制

WARNING

在生产环境中,确保:

  • 不要包含开发工具依赖
  • 敏感信息不要硬编码在配置文件中
  • 定期更新依赖版本以修复安全漏洞

通过遵循这些指南,你可以创建一个高效、安全、可监控的 Spring Boot 生产应用程序包。记住,生产打包不是一次性任务,而是需要根据实际运行情况持续优化的过程。