Skip to content

Spring Boot 内嵌 Web 服务器完全指南 🚀

什么是内嵌 Web 服务器?

Spring Boot 最革命性的特性之一就是内嵌 Web 服务器。想象一下,在传统的 Java Web 开发中,你需要:

  1. 安装 Tomcat 服务器
  2. 配置服务器
  3. 打包成 WAR 文件
  4. 部署到服务器

而 Spring Boot 说:"不,这太复杂了!" 它直接把 Web 服务器打包到你的应用程序中,让你的应用变成一个可执行的 JAR 文件。

TIP

内嵌 Web 服务器的核心理念:应用即服务器。你的应用程序本身就包含了运行所需的一切,真正实现了"一键启动"。

支持的 Web 服务器类型

Spring Boot 支持多种内嵌 Web 服务器,每种都有其特色:

服务器适用场景特点
Tomcat传统 Servlet 应用默认选择,成熟稳定
Jetty高并发场景轻量级,内存占用少
Undertow高性能需求性能优异,支持 NIO
Reactor Netty响应式应用专为 WebFlux 设计

切换 Web 服务器

为什么要切换服务器?

不同的 Web 服务器有不同的优势:

  • Tomcat:最成熟,文档丰富,生态完善
  • Jetty:启动速度快,适合开发环境
  • Undertow:性能最佳,内存效率高
  • Reactor Netty:响应式编程的最佳选择

实际操作示例

kotlin
// build.gradle.kts
dependencies {
    implementation("org.springframework.boot:spring-boot-starter-web") {
        // 排除默认的 Tomcat
        exclude(group = "org.springframework.boot", module = "spring-boot-starter-tomcat") 
    }
    // 使用 Jetty 替代
    implementation("org.springframework.boot:spring-boot-starter-jetty") 
}
kotlin
// build.gradle.kts
dependencies {
    implementation("org.springframework.boot:spring-boot-starter-web") {
        exclude(group = "org.springframework.boot", module = "spring-boot-starter-tomcat") 
    }
    implementation("org.springframework.boot:spring-boot-starter-undertow") 
}

性能对比示例

kotlin
@RestController
class PerformanceTestController {
    
    @GetMapping("/hello")
    fun hello(): String {
        return "Hello from ${getServerType()}!"
    }
    
    private fun getServerType(): String {
        return when {
            isClassPresent("org.apache.catalina.startup.Tomcat") -> "Tomcat"
            isClassPresent("org.eclipse.jetty.server.Server") -> "Jetty"
            isClassPresent("io.undertow.Undertow") -> "Undertow"
            else -> "Unknown"
        }
    }
    
    private fun isClassPresent(className: String): Boolean {
        return try {
            Class.forName(className)
            true
        } catch (e: ClassNotFoundException) {
            false
        }
    }
}

端口配置与管理

基础端口配置

properties
# application.properties
server.port=9090  # 自定义端口

动态端口分配

在微服务架构中,经常需要动态分配端口:

properties
server.port=0  # 使用随机端口

运行时获取端口

kotlin
@Component
class PortDiscoveryService : ApplicationListener<WebServerInitializedEvent> {
    
    private var actualPort: Int = 0
    
    override fun onApplicationEvent(event: WebServerInitializedEvent) {
        actualPort = event.webServer.port 
        println("服务器启动在端口: $actualPort")
    }
    
    fun getActualPort(): Int = actualPort
}

测试中的端口注入

kotlin
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class WebIntegrationTest {
    
    @LocalServerPort
    private var port: Int = 0
    
    @Test
    fun `测试服务器端口`() {
        println("测试服务器运行在端口: $port")
        // 使用 port 进行测试
    }
}

SSL/HTTPS 配置

为什么需要 HTTPS?

在现代 Web 开发中,HTTPS 已经成为标准:

  • 保护数据传输安全
  • 提升 SEO 排名
  • 现代浏览器要求
  • 符合安全规范

使用 Java KeyStore

yaml
# application.yml
server:
  port: 8443
  ssl:
    key-store: "classpath:keystore.jks"
    key-store-password: "secret"
    key-password: "another-secret"

使用 PEM 证书文件

yaml
server:
  port: 8443
  ssl:
    certificate: "classpath:my-cert.crt"
    certificate-private-key: "classpath:my-cert.key"
    trust-certificate: "classpath:ca-cert.crt"

SSL 配置服务

kotlin
@Configuration
class SSLConfiguration {
    
    @Bean
    fun sslCustomizer(): WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
        return WebServerFactoryCustomizer { factory ->
            factory.addConnectorCustomizers { connector ->
                // 自定义 SSL 配置
                connector.scheme = "https"
                connector.secure = true
                connector.port = 8443
            }
        }
    }
}

WARNING

配置 HTTPS 后,应用将不再支持 HTTP 连接。如果需要同时支持 HTTP 和 HTTPS,需要通过编程方式配置额外的连接器。

HTTP/2 支持

HTTP/2 的优势

启用 HTTP/2

yaml
server:
  http2:
    enabled: true  # 启用 HTTP/2
  ssl:
    enabled: true  # HTTP/2 需要 SSL

HTTP/2 测试控制器

kotlin
@RestController
class Http2TestController {
    
    @GetMapping("/api/data")
    fun getData(): ResponseEntity<Map<String, Any>> {
        val data = mapOf(
            "message" to "HTTP/2 响应",
            "timestamp" to System.currentTimeMillis(),
            "protocol" to "HTTP/2.0"
        )
        return ResponseEntity.ok(data)
    }
    
    @GetMapping("/api/stream")
    fun getStreamData(): Flux<String> {
        return Flux.interval(Duration.ofSeconds(1))
            .map { "数据流 #$it" }
            .take(10) 
    }
}

响应压缩优化

压缩配置

yaml
server:
  compression:
    enabled: true
    min-response-size: 1024  # 最小压缩大小
    mime-types: 
      - text/html
      - text/css
      - application/json
      - application/javascript

压缩效果测试

kotlin
@RestController
class CompressionTestController {
    
    @GetMapping("/large-json")
    fun getLargeJson(): Map<String, Any> {
        // 生成大量数据用于测试压缩效果
        val largeData = (1..1000).map { index ->
            "item$index" to mapOf(
                "id" to index,
                "name" to "测试数据项 $index",
                "description" to "这是一个用于测试压缩功能的长描述文本".repeat(5)
            )
        }.toMap()
        
        return mapOf(
            "data" to largeData,
            "size" to largeData.size,
            "compressed" to true
        )
    }
}

自定义 Web 服务器配置

高级配置场景

有时候,简单的属性配置无法满足复杂需求,这时就需要编程式配置:

kotlin
@Configuration
class CustomWebServerConfiguration {
    
    @Bean
    fun webServerCustomizer(): WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
        return WebServerFactoryCustomizer { factory ->
            // 自定义连接池配置
            factory.addConnectorCustomizers { connector ->
                val protocol = connector.protocolHandler as Http11NioProtocol
                protocol.maxConnections = 200
                protocol.maxThreads = 100
                protocol.minSpareThreads = 10
                protocol.connectionTimeout = 20000
            }
            
            // 添加自定义 Valve
            factory.addContextCustomizers { context ->
                val accessLogValve = AccessLogValve()
                accessLogValve.directory = "logs"
                accessLogValve.pattern = "%h %l %u %t \"%r\" %s %b %D"
                context.parent.pipeline.addValve(accessLogValve)
            }
        }
    }
}

多端口支持

kotlin
@Configuration
class MultiPortConfiguration {
    
    @Bean
    fun httpConnectorCustomizer(): WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
        return WebServerFactoryCustomizer { factory ->
            // 添加额外的 HTTP 连接器
            val httpConnector = Connector("org.apache.coyote.http11.Http11NioProtocol")
            httpConnector.port = 8080
            httpConnector.scheme = "http"
            httpConnector.secure = false
            
            factory.addAdditionalTomcatConnectors(httpConnector)
        }
    }
}

代理服务器配置

反向代理场景

代理头处理配置

yaml
server:
  forward-headers-strategy: NATIVE  # 或 FRAMEWORK
  tomcat:
    remoteip:
      remote-ip-header: x-forwarded-for
      protocol-header: x-forwarded-proto
      internal-proxies: 192\.168\.\d{1,3}\.\d{1,3}

代理信息获取

kotlin
@RestController
class ProxyInfoController {
    
    @GetMapping("/proxy-info")
    fun getProxyInfo(request: HttpServletRequest): Map<String, Any?> {
        return mapOf(
            "remoteAddr" to request.remoteAddr,
            "forwardedFor" to request.getHeader("X-Forwarded-For"), 
            "forwardedProto" to request.getHeader("X-Forwarded-Proto"),
            "forwardedHost" to request.getHeader("X-Forwarded-Host"),
            "realIp" to getRealClientIp(request)
        )
    }
    
    private fun getRealClientIp(request: HttpServletRequest): String {
        val xForwardedFor = request.getHeader("X-Forwarded-For")
        return if (!xForwardedFor.isNullOrBlank()) {
            xForwardedFor.split(",").first().trim() 
        } else {
            request.remoteAddr
        }
    }
}

访问日志配置

不同服务器的日志配置

yaml
server:
  tomcat:
    basedir: "logs"
    accesslog:
      enabled: true
      pattern: "%t %a %r %s (%D microseconds)"
      directory: "access-logs"
yaml
server:
  undertow:
    accesslog:
      enabled: true
      pattern: "%t %a %r %s (%D milliseconds)"
    options:
      server:
        record-request-start-time: true
yaml
server:
  jetty:
    accesslog:
      enabled: true
      filename: "/var/log/jetty-access.log"

自定义访问日志处理

kotlin
@Component
class AccessLogProcessor {
    
    private val logger = LoggerFactory.getLogger(AccessLogProcessor::class.java)
    
    @EventListener
    fun handleAccessLog(event: ServletRequestHandledEvent) {
        val logEntry = buildString {
            append("${event.requestUrl} ")
            append("${event.method} ")
            append("${event.statusCode} ")
            append("${event.processingTimeMillis}ms") 
        }
        
        logger.info("访问日志: $logEntry")
    }
}

实际应用场景

微服务环境配置

kotlin
@Configuration
@Profile("microservice")
class MicroserviceWebConfiguration {
    
    @Bean
    fun microserviceCustomizer(): WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
        return WebServerFactoryCustomizer { factory ->
            // 微服务优化配置
            factory.setPort(0) // 动态端口
            
            // 健康检查端点
            factory.addInitializers { servletContext ->
                servletContext.addServlet("health", HealthCheckServlet())
                    .addMapping("/health") 
            }
        }
    }
}

class HealthCheckServlet : HttpServlet() {
    override fun doGet(req: HttpServletRequest, resp: HttpServletResponse) {
        resp.contentType = "application/json"
        resp.writer.write("""{"status":"UP","timestamp":"${System.currentTimeMillis()}"}""")
    }
}

开发环境配置

kotlin
@Configuration
@Profile("dev")
class DevelopmentWebConfiguration {
    
    @Bean
    fun devCustomizer(): WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
        return WebServerFactoryCustomizer { factory ->
            // 开发环境快速重启配置
            factory.addConnectorCustomizers { connector ->
                connector.setProperty("connectionTimeout", "3000")
                connector.setProperty("keepAliveTimeout", "3000") 
            }
        }
    }
}

性能监控与调优

服务器指标收集

kotlin
@Component
class WebServerMetrics {
    
    private val meterRegistry: MeterRegistry by lazy {
        Metrics.globalRegistry
    }
    
    @EventListener
    fun recordServerMetrics(event: WebServerInitializedEvent) {
        val webServer = event.webServer
        
        // 记录服务器启动指标
        Timer.Sample.start(meterRegistry)
            .stop(Timer.builder("server.startup.time")
                .description("服务器启动时间")
                .register(meterRegistry)) 
        
        // 记录端口信息
        Gauge.builder("server.port")
            .description("服务器端口")
            .register(meterRegistry) { webServer.port.toDouble() }
    }
}

最佳实践总结

关键要点

  1. 选择合适的服务器:根据应用特性选择最适合的内嵌服务器
  2. 安全配置:生产环境必须启用 HTTPS
  3. 性能优化:合理配置连接池、压缩等参数
  4. 监控日志:完善的访问日志和监控体系
  5. 环境隔离:不同环境使用不同的配置策略

实用建议

  • 开发环境使用 Jetty(启动快)
  • 生产环境使用 Undertow(性能好)
  • 响应式应用使用 Reactor Netty
  • 传统应用继续使用 Tomcat(稳定可靠)

通过合理配置内嵌 Web 服务器,你可以构建出高性能、安全可靠的 Spring Boot 应用。记住,配置不是目的,解决实际问题才是关键! 🎯