RestClient、RestTemplate、HTTP Interface 底层都用什么?默认有没有超时?连接池要不要自己配?为什么我设的 read-timeout 没生效?返回 404 是抛异常还是返回?这篇用一个会计数连接数的本地服务端,在 Spring Boot 4.0.8 上逐个测量。
ClientHttpRequestFactoryBuilder.detect() 建出的工厂 = JdkClientHttpRequestFactory
RestTemplateBuilder.build() 的工厂 = JdkClientHttpRequestFactory
new RestTemplate() 的工厂 = SimpleClientHttpRequestFactory
spring.http.clients.imperative.factory=simple 时:
ClientHttpRequestFactoryBuilder.detect() 建出的工厂 = JdkClientHttpRequestFactory
RestTemplateBuilder.build() 的工厂 = SimpleClientHttpsRequestFactory
new RestTemplate() 的工厂 = SimpleClientHttpRequestFactory
spring.http.clients.imperative.factory=simple 时 RestClient 的请求头:
rc-boot 服务端看到的请求头 = {"User-Agent": "Java/21.0.12.1", "Accept": "*/*", "Connection": "keep-alive"}有 Boot 配置时没有 Apache 就用 JDK HttpClient;new RestTemplate() 用 HttpURLConnection。
rc-boot 服务端看到的请求头 = {"User-Agent": "Java-http-client/21.0.12.1", "Connection": "Upgrade, HTTP2-Settings", "Upgrade": "h2c", "HTTP2-Settings": "AAEAAEAAAAIAAAAAAAMAAAAAAAQBAAAAAAUAAEAAAAYABgAA", "Accept-Encoding": "gzip"}
rc-create 服务端看到的请求头 = {"User-Agent": "Java-http-client/21.0.12.1", "Connection": "Upgrade, HTTP2-Settings", "Upgrade": "h2c", "HTTP2-Settings": "AAEAAEAAAAIAAAAAAAMAAAAAAAQBAAAAAAUAAEAAAAYABgAA", "Accept-Encoding": "gzip"}
rt-boot 服务端看到的请求头 = {"User-Agent": "Java-http-client/21.0.12.1", "Accept": "text/plain, application/json, application/*+json, */*", "Connection": "Upgrade, HTTP2-Settings", "Upgrade": "h2c", "HTTP2-Settings": "AAEAAEAAAAIAAAAAAAMAAAAAAAQBAAAAAAUAAEAAAAYABgAA", "Accept-Encoding": "gzip"}
rt-new 服务端看到的请求头 = {"User-Agent": "Java/21.0.12.1", "Accept": "text/plain, application/json, application/*+json, */*", "Connection": "keep-alive"}JDK 客户端在明文 HTTP 上会发 Upgrade: h2c。
顺序 20 次 Boot 的 RestClient.Builder:服务端收到 20 个请求、1 个新连接 并发 20 个线程(每个请求在服务端睡 300 ms)Boot 的 RestClient.Builder:服务端收到 20 个请求、20 个新连接;rc-boot par 20 次完成,用时 376 ms 顺序 20 次 RestClient.create():服务端收到 20 个请求、1 个新连接 并发 20 个线程(每个请求在服务端睡 300 ms)RestClient.create():服务端收到 20 个请求、20 个新连接;rc-create par 20 次完成,用时 387 ms 顺序 20 次 RestTemplateBuilder:服务端收到 20 个请求、1 个新连接 并发 20 个线程(每个请求在服务端睡 300 ms)RestTemplateBuilder:服务端收到 20 个请求、20 个新连接;rt-boot par 20 次完成,用时 382 ms 顺序 20 次 new RestTemplate():服务端收到 20 个请求、1 个新连接 并发 20 个线程(每个请求在服务端睡 300 ms)new RestTemplate():服务端收到 20 个请求、20 个新连接;rt-new par 20 次完成,用时 338 ms
keep-alive 自动复用,并发时没有默认上限。
不配置任何超时: RestClient 读到 slow-done,用时 3051 ms RestTemplate(Boot 构建)读到 slow-done,用时 3011 ms new RestTemplate() 读到 slow-done,用时 3013 ms spring.http.client.read-timeout=500ms(旧的属性名,没有 s): RestClient 读到 slow-done,用时 3046 ms RestTemplate(Boot 构建)读到 slow-done,用时 3010 ms new RestTemplate() 读到 slow-done,用时 3011 ms spring.http.clients.read-timeout=500ms: RestClient 异常 ResourceAccessException ← HttpTimeoutException,用时 522 ms RestTemplate(Boot 构建)异常 ResourceAccessException ← HttpTimeoutException,用时 514 ms new RestTemplate() 读到 slow-done,用时 3012 ms
默认没有;旧属性名静默无效;只对 Boot 构建的客户端生效。
spring.http.clients.connect-timeout=300ms: RestClient 连 10.255.255.1:81 异常 ResourceAccessException ← HttpConnectTimeoutException ← ConnectException,用时 331 ms 不配置连接超时(最多等 20 秒): 20 秒内没有返回,测试程序被强行终止(rc=-9)
不配置时连不可达地址会挂很久。
retrieve().body(String) /s/200 → 返回 'body-of-200' retrieve().body(String) /s/204 → 返回 null retrieve().body(String) /s/404 → 抛 NotFound,状态 404,body='body-of-404' retrieve().body(String) /s/500 → 抛 InternalServerError,状态 500,body='body-of-500' exchange() /s/404 → 不抛,拿到 '404 body-of-404' onStatus 自定义 /s/404 → 抛 IllegalStateException: 自定义 4xx 处理:404 NOT_FOUND onStatus 吞掉 500 → toBodilessEntity 状态 500 RestTemplate /s/404 → 抛 NotFound
4xx/5xx 默认抛异常,exchange 与 onStatus 可改。
api.echo(x=a b, h=H1) → x=a b,X-Test=H1 api.status(200) → body-of-200 api.statusEntity(404) 通过 ResponseEntity 接 → 仍然会抛? api.statusEntity(404) 抛 NotFound api.status(500) 抛 InternalServerError 状态 500
声明式接口,异常行为与 RestClient 一致。
默认: RestClient GET /redirect → 状态 200 body='ok' RestTemplate(Boot 构建)GET /redirect → 'ok' new RestTemplate() GET /redirect → 'ok' spring.http.clients.redirects=dont-follow: RestClient GET /redirect → 状态 302 body='null' RestTemplate(Boot 构建)GET /redirect → 'null' new RestTemplate() GET /redirect → 'ok'
默认跟随;dont-follow 只对 Boot 构建的客户端生效。
detect() 建出的工厂 = HttpComponentsClientHttpRequestFactory
Boot 的 RestClient.Builder(默认):30 个并发请求,成功 30,失败 {},总用时 1886 ms
服务端收到 30 个请求、5 个新连接
自己配的连接池(总 100、每个路由 50):30 个并发请求,成功 30,失败 {},总用时 361 ms
服务端收到 30 个请求、30 个新连接
连接池只有 2 个连接、等连接最多 200 ms:6 个并发请求,成功 2,失败 {ERR ResourceAccessException ← ConnectionRequestTimeoutException=4},总用时 642 ms
服务端收到 2 个请求、2 个新连接默认每个路由 5 个连接;池大小和等连接超时要显式配。