在微服務架構中,服務間同步調用常用 RestTemplate、WebClient、OkHttp、Feign。本文演示 Spring Boot 2.x 整合 OkHttp 3.x,結合 String.format() 佔位符構建動態 URL,實現簡潔的服務消費端。
myboot1 (消費者, port 8080) ──HTTP GET──▶ myboot2 (提供者, port 8081)
│ │
├─ OkHttpClient (單例 Bean) ├─ HotelController
├─ application-dev.properties (URL模板) └─ GET /hotel/getHotelInfo?id={id}&name={name}
└─ AddressController (調用入口) 返回 Hotel JSON
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
server.port=8081
@Data @AllArgsConstructor @NoArgsConstructor
public class Hotel {
private int id;
private String hotelname;
}
@RestController
@RequestMapping("/hotel")
public class HotelController {
@GetMapping("/getHotelInfo")
public Hotel getHotelInfo(@RequestParam int id, @RequestParam String name) {
return new Hotel(id, name);
}
}
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.14.9</version> <!-- 3.x 版本 -->
</dependency>
service.hotel.url=https://localhost:8081/hotel/getHotelInfo?id=%d&name=%s
關鍵點:%d 對應 int,%s 對應 String,& 在 properties 中需轉義為 & 或直接寫 & (YAML 無需轉義)。
spring.profiles.active=dev
@Configuration
public class OkHttpConfig {
@Bean
public OkHttpClient okHttpClient() {
return new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.retryOnConnectionFailure(true)
.build();
}
}
@RestController
@RequestMapping("/address")
@Slf4j
public class AddressController {
@Value("${service.hotel.url}")
private String hotelUrlTemplate;
@Autowired
private OkHttpClient okHttpClient;
@GetMapping("/testOkHttp")
public String testOkHttp(@RequestParam int id, @RequestParam String name) {
String url = String.format(hotelUrlTemplate, id, name);
log.debug("調用URL: {}", url);
Request request = new Request.Builder().url(url).get().build();
try (Response response = okHttpClient.newCall(request).execute()) {
if (!response.isSuccessful()) {
log.error("調用失敗: HTTP {}", response.code());
return "ERROR: " + response.code();
}
String body = response.body().string();
log.debug("響應: {}", body);
return body;
} catch (IOException e) {
log.error("調用異常", e);
return "EXCEPTION: " + e.getMessage();
}
}
}
| 場景 | 代碼片段 |
|---|---|
| POST JSON | RequestBody.create(MediaType.parse("application/json"), jsonStr) |
| 表單提交 | new FormBody.Builder().add("k","v").build() |
| 文件上傳 | MultipartBody.Builder().addFormDataPart("file", name, RequestBody.create(MEDIA_TYPE, file)).build() |
| 同步調用 | client.newCall(req).execute() |
| 異步回調 | client.newCall(req).enqueue(new Callback() { onResponse/onFailure }) |
| 攔截器 (日誌/簽名) | .addInterceptor(chain -> { ... chain.proceed(req) ... }) |
| 連接池複用 | new ConnectionPool(5, 5, TimeUnit.MINUTES) 設置 OkHttpClient.Builder |
OkHttpClient 必須是單例,否則連接池、線程池重複創建導致資源耗盡retryOnConnectionFailure(true) 僅重試連接失敗,業務級重試需自行實現 (如 Resilience4j)HttpUrl.Builder 或 UriComponentsBuilder 替代 String.format,自動處理編碼HttpLoggingInterceptor (okhttp-logging-interceptor) 打印請求響應WebClient (響應式) 或 RestClient (Spring 6.1+),Feign 適合聲明式調用