HTTP调用之编码实践

对于 HTTP 请求,读者应该都不陌生。打开浏览器,在地址栏中输入一个正确的网址即可获得响应内容,如图 5-5 所示。

在实际的服务调用中肯定不会返回一个页面,而是返回接口的响应内容,比较常见的是 JSON 格式的字符串,如图 5-6 所示。

image 2025 04 14 18 59 20 789
Figure 1. 图5-5 浏览器中的网页效果
image 2025 04 14 18 59 47 844
Figure 2. 图5-6 在浏览器中请求 API 后的响应内容

那么不借助浏览器,在 Java 代码中该如何处理呢?接下来通过几个代码示例讲解在 Java 代码中如何发起 HTTP 请求和处理 HTTP 响应结果。

被调用端编码实现

先创建一个名称为 service-demoSpring Boot 实例项目,将端口号设置为 8081,然后分别创建 ltd.newbee.cloud.service 包和 ltd.newbee.cloud.web 包,用于分别存放业务层实现类和 REST 层的 Controller 类。

ltd.newbee.cloud.service 包中新建 HelloServiceImpl 类,代码如下:

package ltd.newbee.cloud.service;

import org.springframework.stereotype.Component;

@Component
public class HelloServiceImpl {

    public String getName() {
        return "service01";
    }
}

定义了 getName() 方法,该方法的作用是返回一个字符串。

ltd.newbee.cloud.web 包中新建 HelloServiceController 类,代码如下:

package ltd.newbee.cloud.web;

import ltd.newbee.cloud.service.HelloServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloServiceController {

    @Autowired
    private HelloServiceImpl helloService;

    @GetMapping("/hello")
    public String hello() {
        // 调用本地方法,并通过 HTTP 进行响应
        return "hello from " + helloService.getName();
    }
}

HelloServiceController 类使用 @RestController 注解,并不会返回视图对象。该类中定义了 hello() 方法,映射地址为 /hello,在访问该地址后会返回一个字符串给调用端。

本节的代码主要用于功能展示,并没有演示复杂的功能逻辑。

接下来复制 service-demoservice-demo2,并修改类名和配置文件中的端口号。这样就有了两个被调用端的实例,代码结构如图 5-7 所示。

image 2025 04 14 19 05 21 256
Figure 3. 图5-7 service-demo 工程的代码结构

编码完成后分别启动两个实例,启动成功后,可以分别访问两个接口地址:

http://localhost:8081/hello
http://localhost:8082/hello

如果项目中没有报错,并且访问结果如图 5-8 所示,则编码完成。

被调用端的编码和验证都已经完成。下面编写调用端的代码。

image 2025 04 14 19 06 34 570
Figure 4. 图5-8 service01 和 service02 的请求结果

Java 项目开发中,向其他服务发起 HTTP 调用是常见的功能需求,编码实现时需要使用客户端工具或第三方提供的 HTTP 开发包。有很多常用的 HTTP 开发包供开发人员选择,如 Java 自带的 HttpUrlConnection 类、HttpClient 工具包、Spring 提供的 RestTemplate 工具和 WebClient 工具。

笔者将分别使用 HttpClientRestTemplateWebClient 来演示它们是如何对 HTTP 请求进行处理的。

使用HttpClient处理请求

新建一个名称为 request-demoSpring Boot 实例项目,将端口号设置为 8083。在 pom.xml 文件中添加 httpclient 的依赖配置,代码如下:

<dependency>
  <groupId>org.apache.httpcomponents</groupId>
  <artifactId>httpclient</artifactId>
</dependency>

之后创建 ltd.newbee.cloud.web 包,用于存放调用端所需的测试类。在 ltd.newbee.cloud.web 包中新建 ConsumerController 类,代码如下:

package ltd.newbee.cloud.web;

import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.IOException;

@RestController
public class ConsumerController {

    private final String SERVICE_URL = "http://localhost:8081";
    //private final String SERVICE_URL = "http://localhost:8082";

    /**
     * 使用 HttpClient 未处理 HTTP 请求
     * @return
     * @throws IOException
     */
    @GetMapping("/httpClientTest")
    public String httpClientTest() throws IOException {
        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpGet httpGet = new HttpGet(SERVICE_URL + "/hello");
        CloseableHttpResponse response = null;
        try {
            // 执行请求
            response = httpClient.execute(httpGet);
            // 判断返回状态码
            if (response.getStatusLine().getStatusCode() == 200) {
                String content = EntityUtils.toString(response.getEntity(), "UTF-8");
                // 打印请求结果
                System.out.println(content);
            }
        } finally {
            if (response != null) {
                response.close();
            }
        }
        httpClient.close();
        return "请求成功";
    }
}

代码中定义了 SERVICE_URL 变量,用于存放请求地址。在 httpClientTest() 方法中,使用 HttpClient 工具对目标服务的接口进行了请求,并打印接收的请求结果。

编码完成后,依次启动 service-demoservice-demo2request-demo 三个实例,启动成功后可以访问如下测试地址:

http://localhost:8083/httpClientTest

如果项目中没有报错,控制台打印出了此次的请求结果,则编码完成:

hello from service01

hello from service02

使用RestTemplate处理请求

RestTemplateSpring 提供的一个 HTTP 请求工具,它提供了常见的 REST 请求方案的模板,简化了在 Java 代码中处理 HTTP 请求的编码过程。接下来笔者将使用 RestTemplate 工具来完成 HTTP 请求的处理。

依然在 request-demo 项目中进行编码。先创建 ltd.newbee.cloud.config 包,并新建 RestTemplate 的配置类,代码如下:

package ltd.newbee.cloud.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import org.springframework.http.converter.StringHttpMessageConverter;

import java.nio.charset.Charset;

@Configuration
public class RestTemplateConfig {

    @Bean
    public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
        RestTemplate restTemplate = new RestTemplate(factory);
        // UTF-8 编码设置
        restTemplate.getMessageConverters().set(1,
                new StringHttpMessageConverter(Charset.forName("UTF-8")));
        return restTemplate;
    }

    @Bean
    public ClientHttpRequestFactory simpleClientHttpRequestFactory() {
        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
        // 超时时间为 10 秒
        factory.setReadTimeout(10 * 1000);
        // 超时时间为 5 秒
        factory.setConnectTimeout(5 * 1000);
        return factory;
    }
}

然后在 ConsumerController 类中引入 RestTemplate 对象,并使用它来发起请求和处理请求回调结果,代码如下:

@Resource
private RestTemplate restTemplate;

/**
 * 使用 RestTemplate 来处理 HTTP 请求
 * @return
 * @throws IOException
 */
@GetMapping("/restTemplateTest")
public String restTemplateTest() {
    // 打印请求结果
    System.out.println(restTemplate.getForObject(SERVICE_URL + "/hello", String.class));
    return "请求成功";
}

restTemplateTest() 方法中,使用 RestTemplate 工具对目标服务的接口进行了请求,并打印接收的请求结果。相较于 HttpClient 工具,RestTemplate 工具编码更加简单,不管是发起请求还是请求回调的处理都做了很多封装,方便开发人员使用。

编码完成后,依次启动 service-demoservice-demo2request-demo 三个实例,启动成功后可以访问如下测试地址:

http://localhost:8083/restTemplateTest

如果项目中没有报错,控制台打印出了此次的请求结果,则编码完成:

hello from service01

hello from service02

使用WebClient处理请求

WebClient 是从 Spring WebFlux 5.0 版本开始提供的一个非阻塞的基于响应式编程的进行 HTTP 请求的客户端工具。它的响应式编程基于 Reactor。与 RestTemplate 工具类似,它们都是 Spring 官方提供的 HTTP 请求工具,方便开发人员进行网络编程。

只是二者有些许不同,RestTemplate 是阻塞式客户端,WebClient 是非阻塞式客户端,并且二者所依赖的 Servlet 环境不同,WebClientSpring WebFlux 开发库的一部分,引入 starter 场景启动器时使用的依赖如下:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

不用引入 spring-boot-starter-web。使用 RestTemplate 工具直接引用 spring-boot-starter-web 即可。

复制 request-demorequest-demo2,修改 pom.xml 文件中的 web 场景启动器为 spring-boot-starter-webflux,之后新建 ConsumerController2 类,并新增如下代码:

package ltd.newbee.cloud.web;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

@RestController
public class ConsumerController2 {

    private final String SERVICE_URL = "http://localhost:8081";
    //private final String SERVICE_URL = "http://localhost:8082";

    private WebClient webClient = WebClient.builder()
        .baseUrl(SERVICE_URL)
        .build();

    /**
     * 使用 WebClient 处理 HTTP 请求
     * @return
     */
    @GetMapping("/webClientTest")
    public String webClientTest() {
        Mono<String> mono = webClient
                .get()                     // GET 请求方式
                .uri("/hello")              // 请求地址
                .retrieve()                // 获取响应结果
                .bodyToMono(String.class); // 响应结果转换

        // 打印请求结果
        mono.subscribe(result -> {
            System.out.println(result);
        });
        return "请求成功";
    }
}

webClientTest() 方法中,使用 WebClient 工具对目标服务的接口进行请求,并打印接收的请求结果。相较于 RestTemplate 工具,WebClient 工具的编码方式有所不同,可以应用函数式编程与流式 API,支持 Reactive 类型(MonoFlux)。

编码完成后,依次启动 service-demoservice-demo2request-demo2 三个实例,启动成功后可以访问如下测试地址:

http://localhost:8084/webClientTest

如果项目中没有报错,控制台打印出了此次的请求结果,则编码完成:

hello from service01

hello from service02

最终代码目录结构如图 5-9 所示。

当然,读者也可以自行编码,分别创建四个 Spring Boot 项目并进行编码。笔者为了功能演示和源码整理,把所有代码放在了同一个工程里。

image 2025 04 14 20 38 39 374
Figure 5. 图5-9 最终代码目录结构