webClient

官网介绍:https://docs.spring.io/spring-framework/reference/web/webflux-webclient.html

本部分原文地址:https://medium.com/hprog99/transitioning-from-resttemplate-to-webclient-in-spring-boot-a-detailed-guide-4febd21063ba

多年来,Spring 框架的 RestTemplate 一直是客户端 HTTP 访问的首选解决方案,它提供同步、阻塞 API 来以简单的方式处理 HTTP 请求。

然而,随着对非阻塞、反应式编程以更少的资源处理并发的需求不断增加,特别是在微服务架构中,RestTemplate 已经显示出其局限性。从 Spring Framework 5 开始,RestTemplate 已被标记为已弃用,Spring 团队推荐 WebClient 作为其继任者。

为什么 RestTemplate 被弃用

  1. 阻塞性质:

    RestTemplate 是一个阻塞、同步客户端。这意味着执行请求的线程会阻塞,直到操作完成,这可能会导致线程池耗尽,并在重负载下导致更高的延迟。此模型不能很好地扩展,特别是在应用程序必须有效处理数千个并发请求的微服务环境中。

  2. 可扩展性有限:

    RestTemplate 的同步特性限制了可扩展性。 需要高吞吐量、低延迟能力的现代系统发现这种方法不够。事件驱动、反应式编程范式的兴起是对这些需求的回应,导致了 WebClient 等非阻塞 API 的采用。

  3. 缺乏反应式编程支持:

    RestTemplate 不支持反应式编程,而反应式编程在基于云的生态系统中日益增长。响应式编程使系统更具响应性、弹性和弹性,但这是 RestTemplate 的阻塞性质无法实现的。

WebClient 的兴起

WebClient 是 Spring WebFlux 库的一部分,随 Spring 5 引入。与 RestTemplate 相比,它具有许多优势:

  1. 非阻塞操作:

    WebClient 使用 Project Reactor 在非阻塞、反应式范例上运行,使其能够以更少的线程和更少的开销处理并发,从而显着提高可扩展性和资源利用率。

  2. 反应式堆栈:

    WebClient 支持反应式堆栈,使其适合基于事件循环的运行时环境。它可以在微服务架构中典型的高并发场景下高效工作。

  3. JSON 处理及更多:

    WebClient 通过 Jackson 库提供与 JSON 的无缝集成,类似于 RestTemplate,但具有增强的处理能力。它还支持服务器发送事件 (SSE)、流场景和其他高级用例。

从 RestTemplate 过渡到 WebClient:

我们通过例子来探讨一下如何用 WebClient 替换 RestTemplate。我们将从一个简单的 GET 请求开始。

执行 GET 请求:

RestTemplate:

1
2
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response = restTemplate.getForEntity("http://example.com", String.class);

WebClient:

1
2
3
4
5
6
WebClient webClient = WebClient.create();
Mono<String> response = webClient.get()
    .uri("http://example.com")
    .retrieve()
    .bodyToMono(String.class);
response.subscribe(result -> System.out.println(result));

在 WebClient 中,一切都是非阻塞的。该retrieve()方法发起请求,并将bodyToMono响应主体转换为 Reactor Mono。该subscribe()方法用于订阅结果,一旦可用就会对其进行处理。

处理错误:

RestTemplate 的错误处理通过 ErrorHandler 接口进行,这需要单独的代码块。WebClient 通过更流畅的处理简化了这一过程。

WebClient:

1
2
3
4
5
6
7
8
9
WebClient webClient = WebClient.create();
webClient.get()
    .uri("http://example.com/some-error-endpoint")
    .retrieve()
    .onStatus(HttpStatus::isError, response -> {
        // Handle error status codes
        return Mono.error(new CustomException("Custom error occurred."));
    })
    .bodyToMono(String.class);

该 onStatus() 方法允许直接在操作链中处理特定的 HTTP 状态,从而提供更具可读性和可维护性的方法。

使用 JSON 的 POST 请求:

当发出 POST 请求并提交 JSON 时,WebClient 以其流畅的 API 提供了更直接的方法。

RestTemplate:

1
2
3
4
5
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> request = new HttpEntity<>("{\"key\":\"value\"}", headers);
ResponseEntity<String> response = restTemplate.postForEntity("http://example.com", request, String.class);j

WebClient:

1
2
3
4
5
6
7
WebClient webClient = WebClient.create();
Mono<String> response = webClient.post()
    .uri("http://example.com")
    .contentType(MediaType.APPLICATION_JSON)
    .bodyValue("{\"key\":\"value\"}")
    .retrieve()
    .bodyToMono(String.class);

使用 WebClient,设置标头和正文内容更加直观,并且需要更少的样板代码。和 contentType() 方法 bodyValue() 允许直接设置内容类型和正文。

异步处理:

使用 WebClient 最显着的优点之一是它支持异步处理。当您的应用程序需要进行多个独立的 API 调用时,这尤其有用;这些可以同时执行,从而大大减少这些操作所需的总时间。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
WebClient webClient = WebClient.create();
Mono<String> responseOne = webClient.get()
    .uri("http://example.com/endpointOne")
    .retrieve()
    .bodyToMono(String.class);

Mono<String> responseTwo = webClient.get()
    .uri("http://example.com/endpointTwo")
    .retrieve()
    .bodyToMono(String.class);

// 使用 Mono.zip 并发执行请求
Mono.zip(responseOne, responseTwo).subscribe(results -> {
    System.out.println("Result 1: " + results.getT1());
    System.out.println("Result 2: " + results.getT2());
});

在此示例中,Mono.zip用于组合多个请求的结果。这些请求是并发执行的,一旦所有请求完成,就会处理结果。这种方法比 RestTemplate 同步操作中固有的顺序执行效率要高得多。

流数据:

WebClient 还支持以数据流的形式检索响应主体,这在处理您不想一次将其全部保存在内存中的大量数据时特别有用。

1
2
3
4
5
6
7
WebClient  webClient  = WebClient.create(); 
webClient.get() 
    .uri( "http://example.com/stream" ) 
    .accept(MediaType.TEXT_EVENT_STREAM) // 用于服务器发送事件 (SSE)
    .retrieve() 
    .bodyToFlux(String.class) //将响应正文转换为 Flux
    .subscribe(data -> System.out.println( "Received: " + data));

在此场景中,bodyToFlux用于将响应正文转换为Flux,表示数据流。然后,该subscribe方法用于在每条数据到达时对其进行处理。这与 RestTemplate 形成鲜明对比,后者要求在处理之前将整个响应主体加载到内存中,无论大小如何。

重试机制:

WebClient 利用反应式编程模型提供了一种更复杂的方法来重试失败的请求。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
WebClient webClient = WebClient.builder().baseUrl("http://example.com").build();
Mono<String> response = webClient.get()
    .uri("/retry-endpoint")
    .retrieve()
    .bodyToMono(String.class)
    .retryWhen(Retry.backoff(3, Duration.ofSeconds(1)) // number of retries and backoff configuration
               .maxBackoff(Duration.ofSeconds(10))) // maximum backoff time
    .onErrorResume(e -> Mono.just("Fallback response")); // fallback if retries all fail

response.subscribe(result -> System.out.println(result));

在此示例中,该retryWhen方法用于定义重试策略,指定重试次数和退避配置。如果所有重试都失败,onErrorResume 则提供后备机制。

自定义 Web 客户端配置:

WebClient 具有高度可配置性,您可能会发现默认设置无法满足您的需求。例如,您可能想要调整连接超时,或者可能需要添加应随每个请求发送的默认标头。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// 构建一个具有指定超时和默认标头的自定义 WebClient 
WebClient customWebClient = WebClient.builder()
    .baseUrl("http://example.com")
    .clientConnector(new ReactorClientHttpConnector(
        HttpClient.create()
        .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 2000) // 2 seconds timeout
        .responseTimeout(Duration.ofSeconds(2)) // Set response timeout
        .doOnConnected(conn -> 
                       conn.addHandlerLast(new ReadTimeoutHandler(2)) // 2 seconds read timeout
                       .addHandlerLast(new WriteTimeoutHandler(2))))) // 2 seconds write timeout
    .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) // Default header
    .defaultHeader("Another-Header", "Another-Value") // Another default header
    .build();

在此示例中,我们自定义 WebClient 以具有特定的超时配置和默认标头。WebClient 的此实例会将这些设置应用于它执行的所有请求,确保整个应用程序的行为一致。

网络客户端过滤器:

WebClient 支持使用过滤器来处理横切关注点。这些过滤器可用于操纵请求或响应,甚至可以处理日志记录、指标或授权等问题。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// 带过滤器的自定义 WebClient 
WebClient filteredWebClient = WebClient.builder()
    .baseUrl("http://example.com")
    .filter((request, next) -> {
        // 打印请求数据
        System.out.println("Request: " + request.method() + " " + request.url());
        return next.exchange(request).doOnSuccessOrError((response, error) -> {
            if (response != null) {
                // 打印响应数据
                System.out.println("Response Status: " + response.statusCode());
            }
            if (error != null) {
                // 打印错误
                System.out.println("Error: " + error.getMessage());
            }
        });
    })
    .build();

此过滤器记录通过此 WebClient 实例发出的每个请求的 HTTP 方法和 URL,以及收到的每个响应的状态代码。它还记录交换期间可能发生的任何错误。

相互 TLS 身份验证:

在需要增强安全性的场景中(例如内部微服务通信),您可能需要相互 TLS (mTLS) 身份验证。可以针对此类场景配置 WebClient。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// 使用信任存储和密钥存储准备 SSL 上下文
SslContext sslContext = SslContextBuilder
    .forClient()
    .trustManager(InsecureTrustManagerFactory.INSTANCE) // for demo purposes only!
    .keyManager(new File("path/to/client.crt"), new File("path/to/client.key")) // client certificate and private key
    .build();

// 使用 SSL 上下文配置 WebClient WebClient 
WebClient secureWebClient = WebClient.builder()
    .clientConnector(new ReactorClientHttpConnector(HttpClient.create().secure(sslContextSpec -> sslContextSpec.sslContext(sslContext))))
    .build();

在此示例中,我们通过使用包含客户端证书和私钥的 SSL 上下文配置 WebClient 来设置 mTLS。此设置可确保客户端和服务器在 SSL 握手期间相互验证。

WebClient 最佳实践

以下是一些确保您高效且有效地使用 WebClient 的指南:

单例模式:

与通常根据请求或服务实例化的 RestTemplate 不同,WebClient 被设计为用作单例。这意味着您应该创建 WebClient 的单个实例并在您的应用程序中重用它。这种方式保证了资源的高效利用,避免了重复创建和销毁WebClient实例的开销。

1
2
3
4
@Bean
public WebClient webClient(){
    return WebClient.builder().build();
}

通过在配置中定义 WebClient.Builder bean,您可以在任何需要的地方自动装配它,并针对特定用例对其进行自定义,而无需每次都创建新的 WebClient 实例。

错误处理:

反应性流向下游传播错误,直到错误被处理或到达流的末尾。始终处理反应链中的错误,以避免意外行为。onErrorResume、onErrorReturn 和 doOnError 运算符特别有用。

1
2
3
4
5
6
webClient.get()
    .uri("/endpoint")
    .retrieve()
    .bodyToMono(String.class)
    .doOnError(e -> log.error("Error occurred", e))
    .onErrorResume(e -> Mono.just("Fallback value"));

超时配置:

始终配置超时。如果没有超时,如果服务器没有响应,WebClient 请求可能会无限期挂起。使用timeout运算符设置特定的持续时间,在此之后请求将被终止。

1
2
3
4
5
webClient.get()
    .uri("/endpoint")
    .retrieve()
    .bodyToMono(String.class)
    .timeout(Duration.ofSeconds(10));

背压:

反应式编程的核心原则之一是背压,它允许消费者向生产者发出他们可以处理多少数据的信号。处理Flux(0 到 N 个项目的流)时,请注意背压并确保不会让消费者感到不知所措。使用运算符来limitRate控制数据流的速率。

日志记录:

日志记录对于调试和监控至关重要。WebClient 提供内置日志记录功能。通过将记录器设置reactor.netty.http.client.HttpClient 为 DEBUG,您可以查看请求和响应的详细日志。

线程上下文:

在反应式编程中,操作可能会多次切换线程。如果您依赖线程局部变量(如日志记录或安全上下文中使用的变量),请注意这些变量可能不会自动跨线程传播。像这样的库reactor-context可以帮助在反应流中跨线程传播上下文。

避免阻塞呼叫:

WebClient 和反应式编程的主要好处之一是操作的非阻塞性质。但是,如果您在反应式链中引入阻塞调用,那么这种好处就会被抵消。始终避免在反应流中阻塞操作。如果必须使用阻塞调用,请考虑使用将其卸载到单独的线程池subscribeOn。

1
2
Mono.fromCallable(() ->blockingMethod()) 
    .subscribeOn(Schedulers.boundedElastic());

WebClient 提供了一种现代的、非阻塞的、反应式的方法来发出 HTTP 请求,使其成为大多数用例中优于已弃用的 RestTemplate 的选择。然而,随着它的力量而来的是正确使用它的责任。通过遵循最佳实践、了解响应式范例并意识到潜在的陷阱,您可以充分利用 WebClient 的潜力并构建高效、可扩展且响应迅速的应用程序。

spring boot 中使用webClient

编辑 pom.xml 文件,添加 Spring WebFlux 依赖,从而可以使用 WebClient。

1
2
3
4
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
@Component
public class WebClientUtils {

    // 单例
    @Bean
    public WebClient webClient(){
        return WebClient.builder().build();
    }

    // 注入
    @Autowired
    private WebClient webClient;

    // 测试
    public static void main(String[] args) {
        String result = WebClient.create()
            .get().uri("http://localhost:6688/api/v1/heart/check")
            .retrieve().bodyToMono(String.class).block();
        System.out.println(result);
    }

    // post请求
    public <T> T post(String apiName,Object object,Class<T> toClass){
        long timestamp = System.currentTimeMillis();
        String signature = DigestUtils.md5DigestAsHex((apiKey + secretKey + timestamp).getBytes());

        Mono<T> response = webClient.post()
            .uri(i-> i.scheme(protocols)
                 .host(baseUrl)
                 .path(prefix)
                 .queryParam("apiKey", apiKey)
                 .queryParam("time", String.valueOf(timestamp))
                 .queryParam("sign", signature)
                 .queryParam("method", apiName)
                 .build())
            .contentType(MediaType.APPLICATION_JSON)
            .body(Mono.just(object),Object.class)
            .retrieve().bodyToMono(toClass)
            .timeout(Duration.ofMillis(WebClientHelper.DEFAULT_REQUEST_TIMEOUT));
        return response.block();
    }

}

在service中注入:

1
2
@Autowired
private WebClientUtils webClientUtils;

在方法中使用:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// 查询信息
Map<String, String> map = new HashMap<>();
map.put("ids", "你的参数");
String result = webClientUtils.post("http://api.test.com", map, String.class);// 传入map参数,也支持传入实体类
log.info("返回结果:{}", result);
// 序列化
ErpResponse erpResponse = JacksonUtils.jsonToPojo(result, ErpResponse.class);

// 支持序列化泛型类(Jackson的特性,和webClient无关)
ErpResponse<BookTracedInfo> erpResponse = JacksonUtils.jsonToPojoGeneric(result, new TypeReference<>() {});

使用Java 11+的HttpClient (推荐)

Java 11引入了新的HttpClient API,位于java.net.http包中:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;

public class Java11HttpClientExample {
    
    // GET请求示例
    public static void sendGetRequest() throws Exception {
        HttpClient client = HttpClient.newBuilder()
                .version(HttpClient.Version.HTTP_2)
                .connectTimeout(Duration.ofSeconds(10))
                .build();
        
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://jsonplaceholder.typicode.com/posts/1"))
                .header("User-Agent", "Java 11 HttpClient")
                .GET()
                .build();
        
        HttpResponse<String> response = client.send(
                request, HttpResponse.BodyHandlers.ofString());
        
        System.out.println("GET Response Code :: " + response.statusCode());
        System.out.println(response.body());
    }
    
    // POST请求示例
    public static void sendPostRequest() throws Exception {
        HttpClient client = HttpClient.newBuilder()
                .version(HttpClient.Version.HTTP_2)
                .connectTimeout(Duration.ofSeconds(10))
                .build();
        
        String requestBody = "{\"title\": \"foo\", \"body\": \"bar\", \"userId\": 1}";
        
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://jsonplaceholder.typicode.com/posts"))
                .header("User-Agent", "Java 11 HttpClient")
                .header("Content-Type", "application/json")
                .POST(HttpRequest.BodyPublishers.ofString(requestBody))
                .build();
        
        HttpResponse<String> response = client.send(
                request, HttpResponse.BodyHandlers.ofString());
        
        System.out.println("POST Response Code :: " + response.statusCode());
        System.out.println(response.body());
    }
    
    public static void main(String[] args) throws Exception {
        sendGetRequest();
        sendPostRequest();
    }
}

使用Spring的RestTemplate(已过时)

RestTemplate 是 Spring 提供的用于同步 HTTP 请求的经典类。虽然在 Spring 6 中已经被标记为 过时 (deprecated),但仍然广泛使用。
需要添加Spring Web依赖:

1
2
3
4
5
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.5.0</version>
</dependency>

代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import org.springframework.http.*;
import org.springframework.web.client.RestTemplate;

public class RestTemplateExample {
    
    // GET请求示例
    public static void sendGetRequest() {
        RestTemplate restTemplate = new RestTemplate();
        String url = "https://jsonplaceholder.typicode.com/posts/1";
        
        HttpHeaders headers = new HttpHeaders();
        headers.set("User-Agent", "RestTemplate");
        
        HttpEntity<String> entity = new HttpEntity<>(headers);
        
        ResponseEntity<String> response = restTemplate.exchange(
                url, HttpMethod.GET, entity, String.class);
        
        System.out.println("GET Response Code :: " + response.getStatusCodeValue());
        System.out.println(response.getBody());
    }
    
    // POST请求示例
    public static void sendPostRequest() {
        RestTemplate restTemplate = new RestTemplate();
        String url = "https://jsonplaceholder.typicode.com/posts";
        
        HttpHeaders headers = new HttpHeaders();
        headers.set("User-Agent", "RestTemplate");
        headers.setContentType(MediaType.APPLICATION_JSON);
        headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
        
        String requestBody = "{\"title\": \"foo\", \"body\": \"bar\", \"userId\": 1}";
        
        HttpEntity<String> entity = new HttpEntity<>(requestBody, headers);
        
        ResponseEntity<String> response = restTemplate.exchange(
                url, HttpMethod.POST, entity, String.class);
        
        System.out.println("POST Response Code :: " + response.getStatusCodeValue());
        System.out.println(response.getBody());
    }
    
    public static void main(String[] args) {
        sendGetRequest();
        sendPostRequest();
    }
}

使用OkHttp

添加需要的依赖:

1
2
3
4
5
<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>4.9.1</version>
</dependency>

代码示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import okhttp3.*;

import java.io.IOException;

public class OkHttpExample {
    
    // GET请求示例
    public static void sendGetRequest() throws IOException {
        OkHttpClient client = new OkHttpClient();
        
        Request request = new Request.Builder()
                .url("https://jsonplaceholder.typicode.com/posts/1")
                .addHeader("User-Agent", "OkHttp")
                .build();
        
        try (Response response = client.newCall(request).execute()) {
            System.out.println("GET Response Code :: " + response.code());
            System.out.println(response.body().string());
        }
    }
    
    // POST请求示例
    public static void sendPostRequest() throws IOException {
        OkHttpClient client = new OkHttpClient();
        
        MediaType JSON = MediaType.get("application/json; charset=utf-8");
        String json = "{\"title\": \"foo\", \"body\": \"bar\", \"userId\": 1}";
        RequestBody body = RequestBody.create(json, JSON);
        
        Request request = new Request.Builder()
                .url("https://jsonplaceholder.typicode.com/posts")
                .addHeader("User-Agent", "OkHttp")
                .post(body)
                .build();
        
        try (Response response = client.newCall(request).execute()) {
            System.out.println("POST Response Code :: " + response.code());
            System.out.println(response.body().string());
        }
    }
    
    public static void main(String[] args) throws IOException {
        sendGetRequest();
        sendPostRequest();
    }
}

使用Apache HttpClient

需要添加依赖:

1
2
3
4
5
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.13</version>
</dependency>

代码示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

public class ApacheHttpClientExample {
    
    // GET请求示例
    public static void sendGetRequest() throws Exception {
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            HttpGet request = new HttpGet("https://jsonplaceholder.typicode.com/posts/1");
            
            // 添加请求头
            request.addHeader("User-Agent", "Apache HttpClient");
            
            try (CloseableHttpResponse response = httpClient.execute(request)) {
                System.out.println("GET Response Code :: " + response.getStatusLine().getStatusCode());
                
                HttpEntity entity = response.getEntity();
                if (entity != null) {
                    String result = EntityUtils.toString(entity);
                    System.out.println(result);
                }
            }
        }
    }
    
    // POST请求示例
    public static void sendPostRequest() throws Exception {
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            HttpPost request = new HttpPost("https://jsonplaceholder.typicode.com/posts");
            
            // 添加请求头
            request.addHeader("User-Agent", "Apache HttpClient");
            request.addHeader("Content-Type", "application/json");
            request.addHeader("Accept", "application/json");
            
            // 请求体
            String json = "{\"title\": \"foo\", \"body\": \"bar\", \"userId\": 1}";
            request.setEntity(new StringEntity(json));
            
            try (CloseableHttpResponse response = httpClient.execute(request)) {
                System.out.println("POST Response Code :: " + response.getStatusLine().getStatusCode());
                
                HttpEntity entity = response.getEntity();
                if (entity != null) {
                    String result = EntityUtils.toString(entity);
                    System.out.println(result);
                }
            }
        }
    }
    
    public static void main(String[] args) throws Exception {
        sendGetRequest();
        sendPostRequest();
    }
}

使用Java原生HttpURLConnection

这是Java标准库提供的HTTP客户端,无需额外依赖。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;

public class HttpUrlConnectionExample {
    
    // GET请求示例
    public static void sendGetRequest() throws Exception {
        String url = "https://jsonplaceholder.typicode.com/posts/1";
        URL obj = new URL(url);
        HttpURLConnection con = (HttpURLConnection) obj.openConnection();
        
        // 设置请求方法
        con.setRequestMethod("GET");
        
        // 添加请求头
        con.setRequestProperty("User-Agent", "Mozilla/5.0");
        
        int responseCode = con.getResponseCode();
        System.out.println("GET Response Code :: " + responseCode);
        
        if (responseCode == HttpURLConnection.HTTP_OK) { // 成功
            BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
            String inputLine;
            StringBuilder response = new StringBuilder();
            
            while ((inputLine = in.readLine()) != null) {
                response.append(inputLine);
            }
            in.close();
            
            System.out.println(response.toString());
        } else {
            System.out.println("GET request not worked");
        }
    }
    
    // POST请求示例
    public static void sendPostRequest() throws Exception {
        String url = "https://jsonplaceholder.typicode.com/posts";
        URL obj = new URL(url);
        HttpURLConnection con = (HttpURLConnection) obj.openConnection();
        
        // 设置请求方法
        con.setRequestMethod("POST");
        con.setRequestProperty("User-Agent", "Mozilla/5.0");
        con.setRequestProperty("Content-Type", "application/json; utf-8");
        con.setRequestProperty("Accept", "application/json");
        
        // 启用输出流
        con.setDoOutput(true);
        
        // 请求体
        String jsonInputString = "{\"title\": \"foo\", \"body\": \"bar\", \"userId\": 1}";
        
        try(OutputStream os = con.getOutputStream()) {
            byte[] input = jsonInputString.getBytes(StandardCharsets.UTF_8);
            os.write(input, 0, input.length);
        }
        
        int responseCode = con.getResponseCode();
        System.out.println("POST Response Code :: " + responseCode);
        
        if (responseCode == HttpURLConnection.HTTP_CREATED) { // 成功
            BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
            String inputLine;
            StringBuilder response = new StringBuilder();
            
            while ((inputLine = in.readLine()) != null) {
                response.append(inputLine);
            }
            in.close();
            
            System.out.println(response.toString());
        } else {
            System.out.println("POST request not worked");
        }
    }
    
    public static void main(String[] args) throws Exception {
        sendGetRequest();
        sendPostRequest();
    }
}