Spring Boot应用程序中使用RestTemplate发起HTTP请求

313 阅读10分钟

0-编辑历史

2023-10-17 16:20

宜改需求 太初有道,道生时空,故时空乃程序设计之根本

这篇文章的主要内容:在一个 Spring Boot 客户端应用程序中,使用 RestTemplate 发送 HTTP 请求,有一个 RESTful Web Service 作为服务端返回响应,并可能在响应体中返回数据。客户端使用 RestTemplate 的相关 API 来获取响应结果,并进行解析和处理。

实现这个客户端应用程序的前提是我们需要有一个服务端应用程序,在这个项目中,为了简化起见,我将客户端与服务端合二为一。

2023-10-18 15:09

宜不接需求 心若向阳,何惧悲伤

1-参考资料

chatgpt: website

project: juejin.cn/post/728896…

spring documentation: docs.spring.io/spring-fram…

httpbin.org : httpbin.org/#/

2-构建服务端应用程序

服务端应用程序使用如下技术构建:Spring Boot \ Spring Web \ H2 \ Mybatis-Plus \ springdoc \ lombok

项目结构目录如下图所示

Snipaste_2023-10-17_16-59-05.png

2023-10-17 17:00,如下试是对代码的展示,可以快速跳过此部分代码直接浏览第三部分

2-0-build.gradle

plugins {
    id 'java'
    id 'org.springframework.boot' version '2.7.15'
    id 'io.spring.dependency-management' version '1.0.15.RELEASE'
}


group = 'com.example'
version = '0.0.1-SNAPSHOT'

java {
    sourceCompatibility = '1.8'
}

repositories {
    mavenCentral()
}

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

dependencies {
    // https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter
    implementation group: 'com.baomidou', name: 'mybatis-plus-boot-starter', version: '3.5.3.1'
    // https://mvnrepository.com/artifact/org.springdoc/springdoc-openapi-ui
    implementation group: 'org.springdoc', name: 'springdoc-openapi-ui', version: '1.7.0'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    runtimeOnly 'com.h2database:h2'
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

tasks.named('test') {
    useJUnitPlatform()
}

2-1-实体类

package com.example.lkcoffee.persistence.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

import java.math.BigDecimal;

/**
 * @version 1.0
 * @since 2023/10/10 17:22
 */
@Schema(description = "coffee model")
@Data
@TableName(value = "coffee")
@JsonIgnoreProperties(ignoreUnknown=true)
public class Coffee {


    @TableId(value = "ID", type = IdType.AUTO)
    private Integer id;

    @TableField(value = "name")
    private String name;

    @TableField(value = "brand")
    private String brand;

    @TableField(value = "price")
    private BigDecimal price;

    @TableField(value = "introduction")
    private String introduction;

    @TableField(value = "rating")
    private BigDecimal rating;
    
}

2-2-Mapper && Xml

package com.example.lkcoffee.persistence.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.lkcoffee.persistence.entity.Coffee;
import org.apache.ibatis.annotations.Mapper;

/**
 * <p>
 *  Mapper 接口
 * </p>
 * @version 1.0
 * @since 2023/10/11 10:04
 */
@Mapper
public interface CoffeeMapper extends BaseMapper<Coffee> {

}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.example.lkcoffee.persistence.mapper">
    
</mapper>

2-3-Interface && ServiceImpl

package com.example.lkcoffee.service;


import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.lkcoffee.persistence.entity.Coffee;
import com.example.lkcoffee.search.CoffeeSearchCriteria;


/**
 * @version 1.0
 * @since 2023/10/11 10:08
 */
public interface CoffeeService extends IService<Coffee> {

    IPage<Coffee> getCoffeePage(CoffeeSearchCriteria coffeeSearchCriteria);

}
package com.example.lkcoffee.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.lkcoffee.persistence.entity.Coffee;
import com.example.lkcoffee.persistence.mapper.CoffeeMapper;
import com.example.lkcoffee.search.CoffeeSearchCriteria;
import com.example.lkcoffee.service.CoffeeService;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

/**
 * @version 1.0
 * @since 2023/10/11 10:07
 */
@Service
public class CoffeeServiceImpl extends ServiceImpl<CoffeeMapper, Coffee> implements CoffeeService {
    @Override
    public IPage<Coffee> getCoffeePage(CoffeeSearchCriteria coffeeSearchCriteria) {
        Page<Coffee> coffeePage = new Page<>(
                coffeeSearchCriteria.getCurrentPage(), coffeeSearchCriteria.getPageSize());
        // 取消单页分页条数限制
        coffeePage.setMaxLimit(-1L);
        QueryWrapper<Coffee> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda()
                .ge(Coffee::getRating , coffeeSearchCriteria.getRating());
        return this.page(coffeePage, queryWrapper);
    }
}

2-4-Controller

package com.example.lkcoffee.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.example.lkcoffee.persistence.entity.Coffee;
import com.example.lkcoffee.search.CoffeeSearchCriteria;
import com.example.lkcoffee.service.CoffeeService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * The type Coffee controller.
 *
 * @version 1.0
 * @since 2023 /10/11 10:09
 */
@Tag(name = "lkCoffee API")
@RestController
@RequestMapping("/coffee")
public class CoffeeController {

    @Autowired
    private CoffeeService coffeeService;

    /**
     * Gets coffee page.
     *
     * @param rating     the rating
     * @param pageNumber the page number
     * @param pageSize   the page size
     * @return the user page
     */
    @Operation(summary = "按评分获取咖啡分页列表")
    @ApiResponses(value = {
            @ApiResponse(responseCode = "200", description = "成功获取咖啡分页列表"),
            @ApiResponse(responseCode = "500", description = "服务器内部错误")
    })
    @GetMapping("/page")
    public ResponseEntity<IPage<Coffee>> getCoffeePage(
            @Parameter(description = "咖啡评分") @RequestParam float rating,
            @Parameter(description = "页码,默认为1") @RequestParam(defaultValue = "1") Long pageNumber,
            @Parameter(description = "每页记录数,默认为10") @RequestParam(defaultValue = "10") Long pageSize
    ) {
        CoffeeSearchCriteria coffeeSearchCriteria = new CoffeeSearchCriteria();
        coffeeSearchCriteria.setRating(rating);
        coffeeSearchCriteria.setCurrentPage(pageNumber);
        coffeeSearchCriteria.setPageSize(pageSize);
        IPage<Coffee> coffeeIPage = coffeeService.getCoffeePage(coffeeSearchCriteria);
        return ResponseEntity.ok(coffeeIPage);
    }

    /**
     * Gets coffee list.
     *
     * @return the coffee list
     */
    @Operation(summary = "获取咖啡列表")
    @ApiResponses(value = {
            @ApiResponse(responseCode = "200", description = "成功获取咖啡列表"),
            @ApiResponse(responseCode = "500", description = "服务器内部错误")
    })
    @GetMapping("/list")
    public ResponseEntity<List<Coffee>> getCoffeeList() {
        List<Coffee> coffeeList = coffeeService.list();
        return ResponseEntity.ok(coffeeList);
    }

    /**
     * Gets coffee by id.
     *
     * @param id the id
     * @return the coffee by id
     */
    @Operation(summary = "根据 ID 获取咖啡")
    @ApiResponses(value = {
            @ApiResponse(responseCode = "200", description = "成功获取咖啡"),
            @ApiResponse(responseCode = "500", description = "服务器内部错误")
    })
    @GetMapping("/{id}")
    public ResponseEntity<Coffee> getCoffeeById(
            @Parameter(description = "咖啡 ID") @PathVariable Integer id) {
        Coffee coffee = coffeeService.getById(id);
        return ResponseEntity.ok(coffee);
    }

    /**
     * Save coffee response entity.
     *
     * @param coffee the coffee
     * @return the response entity
     */
    @Operation(summary = "保存咖啡")
    @ApiResponses(value = {
            @ApiResponse(responseCode = "201", description = "成功保存咖啡"),
            @ApiResponse(responseCode = "500", description = "服务器内部错误")
    })
    @PostMapping("/save")
    public ResponseEntity<Void> saveCoffee(@RequestBody Coffee coffee) {
        coffeeService.save(coffee);
        return ResponseEntity.status(HttpStatus.CREATED).build();
    }

    /**
     * Update coffee by id response entity.
     *
     * @param coffee the coffee
     * @return the response entity
     */
    @Operation(summary = "更新咖啡")
    @ApiResponses(value = {
            @ApiResponse(responseCode = "204", description = "成功更新咖啡"),
            @ApiResponse(responseCode = "500", description = "服务器内部错误")
    })
    @PutMapping("/update")
    public ResponseEntity<Void> updateCoffeeById(@RequestBody Coffee coffee) {
        coffeeService.updateById(coffee);
        return ResponseEntity.noContent().build();
    }

    /**
     * Delete coffee by id response entity.
     *
     * @param id the id
     * @return the response entity
     */
    @Operation(summary = "根据 ID 删除咖啡")
    @ApiResponses(value = {
            @ApiResponse(responseCode = "204", description = "成功删除咖啡"),
            @ApiResponse(responseCode = "500", description = "服务器内部错误")
    })
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteCoffeeById(
            @Parameter(description = "咖啡 ID") @PathVariable Integer id
    ) {
        coffeeService.removeById(id);
        return ResponseEntity.noContent().build();
    }
}

2-5-Swagger

package com.example.lkcoffee.config;

import io.swagger.v3.oas.models.ExternalDocumentation;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * The type Swagger config.
 *
 * @version 1.0
 * @since 2023 /10/13 13:14
 */
@Configuration
public class SwaggerConfig {
    /**
     * Lk coffee open api.
     *
     * @return the open api
     */
    @Bean
    public OpenAPI lkCoffeeOpenAPI() {
        return new OpenAPI()
                .info(new Info().title("lkCoffee API")
                        .description("lkCoffee application")
                        .version("v0.0.1")
                        .license(new License().name("Apache 2.0").url("http://springdoc.org")))
                .externalDocs(new ExternalDocumentation()
                        .description("lkCoffee Wiki Documentation")
                        .url("http://springdoc.org"));
    }
}

2-6-配置文件-application.yml

spring:
  application:
    name: lkcoffee
  datasource:
    url: jdbc:h2:mem:lkcoffee
    driverClassName: org.h2.Driver
    username: root
    password: toor
    generate-unique-name: false
    name: lkcoffee
  jpa:
    show-sql: true
    hibernate:
      ddl-auto: update
      use-new-id-generator-mappings: false
  h2:
    console:
      path: /h2-console
      enabled: true
      settings:
        trace: false
        web-allow-others: false
  sql:
    init:
      schema-locations: classpath:/schema-h2.sql
      data-locations: classpath:/data-h2.sql

server:
  port: 8888

springdoc:
  api-docs:
    enabled: true
    path: /v3/api-docs
    version: openapi_3_0
  swagger-ui:
    path: /swagger-ui/index.html
    tagsSorter: alpha
    use-root-path: true
  cache:
    disabled: true

2-7-H2数据预配置

schema-h2.sql

DROP TABLE IF EXISTS coffee;

CREATE TABLE coffee
(
    id           INT PRIMARY KEY AUTO_INCREMENT,
    name         VARCHAR(100)  NOT NULL,
    brand        VARCHAR(50)   NOT NULL,
    price        DECIMAL(6, 2) NOT NULL,
    introduction VARCHAR(500),
    rating       DECIMAL(3, 1) NOT NULL DEFAULT 0
);

data-h2.sql

DROP TABLE IF EXISTS coffee;

CREATE TABLE coffee
(
    id           INT PRIMARY KEY AUTO_INCREMENT,
    name         VARCHAR(100)  NOT NULL,
    brand        VARCHAR(50)   NOT NULL,
    price        DECIMAL(6, 2) NOT NULL,
    introduction VARCHAR(500),
    rating       DECIMAL(3, 1) NOT NULL DEFAULT 0
);

2-8-启动应用程序

package com.example.lkcoffee;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.core.env.Environment;

import java.net.InetAddress;
import java.net.UnknownHostException;

/**
 * @author yang.jianming3
 */
@SpringBootApplication
public class LkcoffeeApplication {

    private static final Logger logger = LoggerFactory.getLogger(LkcoffeeApplication.class);

    /**
     * Swagger地址:http://localhost:8080/swagger-ui/index.html#/
     * 参考资料:https://dzone.com/articles/openapi-3-documentation-with-spring-boot
     * @param args args
     */
    public static void main(String[] args) throws UnknownHostException {

        ApplicationContext applicationContext =  SpringApplication.run(LkcoffeeApplication.class, args);
        Environment environment = applicationContext.getEnvironment();

        logger.info("\n---------------------------------------------lkcoffee-----------------------------------------------------------------------\n" +
                        "应用 '{}' 运行成功! \n" +
                        "Swagger-UI-Interface 访问连接: http://{}:{}{}{} \n" +
                        "API-Docs 访问连接: http://{}:{}{}{} \n" +
                        "--------------------------------------------------------------------------------------------------------------------",

                environment.getProperty("spring.application.name"),

                InetAddress.getLocalHost().getHostAddress(),
                environment.getProperty("server.port", "8080"),
                environment.getProperty("server.servlet.context-path", ""),
                environment.getProperty("springdoc.swagger-ui.path", "/swagger-ui/index.html"),

                InetAddress.getLocalHost().getHostAddress(),
                environment.getProperty("server.port", "8080"),
                environment.getProperty("server.servlet.context-path", ""),
                environment.getProperty("springdoc.api-docs.path", "/v3/api-docs")

        );
    }
}

3-发送HTTP请求并处理响应

在 Spring Boot 客户端应用程序中,发送HTTP请求并处理响应有如下方法

1-使用 Java 原生的 HttpURLConnection:Java 提供了 HttpURLConnection 类来进行基本的 HTTP 请求和响应操作。可以使用该类创建一个连接,并发送 GET、POST等请求到目标资源,并读取响应数据。

2-使用 Apache HttpClient:Apache HttpClient 是一个功能强大的 HTTP 客户端库,提供了更多的灵活性和控制力。可以使用它来发送 HTTP 请求并处理响应。

3-使用 Spring 的 RestTemplate:RestTemplate 是 Spring 提供的一个用于发送 HTTP 请求并处理响应的类。它封装了许多常见的 HTTP 操作,使得发送请求和处理响应更加简单。可以使用 RestTemplate 发送 GET、POST 等请求,并使用相应的方法获取响应数据。

4-使用 Spring WebFlux WebClient:Spring WebFlux 是 Spring 提供的响应式编程框架,其中包含一个 WebClient 类。WebClient 提供了一种非阻塞的方式发送 HTTP 请求,并处理响应。它适用于需要高吞吐量和低延迟的应用程序场景。

无论选择哪种方法,都可以根据具体的需求和场景来决定。如果正在开发一个新的 Spring Boot 应用程序,推荐使用 Spring 的 RestTemplate 或 Spring WebFlux WebClient,因为它们与 Spring 生态系统无缝集成,并提供了更多的功能和扩展性。

4-RestTemplate

4-1-RestTemplate 介绍

RestTemplateSpring 提供的一个用于发送 HTTP 请求并处理响应的类。它封装了许多常见的 HTTP 操作,使得发送请求和处理响应更加简单。可以使用 RestTemplate 发送 GETPOST 等请求,并使用相应的方法获取响应数据。

RestTemplate Class 位于 org.springframework.web.client package, 实现了如下接口: RestOperations

public class RestTemplate extends InterceptingHttpAccessor implements RestOperations {}

Synchronous client to perform HTTP requests, exposing a simple, template method API over underlying HTTP client libraries such as the JDK HttpURLConnection, Apache HttpComponents, and others.

RestTemplate offers templates for common scenarios by HTTP method, in addition to the generalized exchange and execute methods that support less frequent cases.

4-3-RestTemplate 构造

在 Constructor Summary 一节,有三种构造方法

4-3-1-使用默认设置构造

默认构造函数会根据默认设置创建一个 RestTemplate 对象。如果只需要进行简单的 GET/POST 请求,并且对请求的细节不太关注,那么这种构造方式就足够了。

Constructor : RestTemplate()

Description: Create a new instance of the RestTemplate using default settings.

代码示例如下

package com.example.lkcoffee.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

/**
 * @author yang.jianming3
 * @version 1.0
 * @since 2023/10/16 10:20
 */
@Slf4j
@Configuration
public class RestTemplateConfig {

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

}

4-3-2-使用自定义的 ClientHttpRequestFactory 构造

如果需要自定义 HTTP 请求的细节,例如超时时间、连接池管理等,那么可以实现自定义的 ClientHttpRequestFactory,并把它传给 RestTemplate 的构造函数来创建 RestTemplate 对象。这种方式非常灵活,可以实现更高级的 HTTP 请求定制。

Constructor : RestTemplate(ClientHttpRequestFactory requestFactory)

Description: Create a new instance of the RestTemplate based on the given **ClientHttpRequestFactory**.

代码示例如下

package com.example.lkcoffee.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

/**
 * @author yang.jianming3
 * @version 1.0
 * @since 2023/10/16 10:20
 */
@Slf4j
@Configuration
public class RestTemplateConfig {

    /**
     * Create Rest template Bean Use ClientHttpRequestFactory
     *
     * @param clientHttpRequestFactory the clientHttpRequestFactory
     * @return the rest template
     */
    @Bean
    public RestTemplate restTemplate(ClientHttpRequestFactory clientHttpRequestFactory) {
        RestTemplate restTemplate = new RestTemplate(clientHttpRequestFactory);
        return restTemplate;
    }

    /**
     *
     * Client http request factory.
     *
     * @return the client http request factory
     */
    @Bean
    public ClientHttpRequestFactory bufferingClientHttpRequestFactory() {
        SimpleClientHttpRequestFactory simpleClientHttpRequestFactory = new SimpleClientHttpRequestFactory();
        //Set the underlying URLConnection's read timeout (in milliseconds)
        simpleClientHttpRequestFactory.setReadTimeout(20000);
        // Set the underlying URLConnection's connect timeout (in milliseconds).
        simpleClientHttpRequestFactory.setConnectTimeout(20000);
        return new BufferingClientHttpRequestFactory(simpleClientHttpRequestFactory);
    }

}

在上面的示例中,涉及到如下知识点

ClientHttpRequestFactory:它是一个接口,定义了用于创建 ClientHttpRequest 对象的方法。Spring 提供了多个实现,用于适应不同的场景和需求。具体取决于你的项目配置和需求,可以选择合适的 ClientHttpRequestFactory 实现。

SimpleClientHttpRequestFactory:它是 ClientHttpRequestFactory 的一个简单实现,使用标准的 JDK HttpURLConnection 类库来创建 ClientHttpRequest 对象。它适用于大多数简单的 HTTP 请求场景,不需要复杂的自定义配置。

BufferingClientHttpRequestFactory:它是 ClientHttpRequestFactory 的一个装饰器(Decorator)实现,可以在底层 HTTP 请求的基础上添加请求和响应的缓冲功能。它内部使用了一个 ClientHttpRequestFactory 对象来创建 ClientHttpRequest,然后对 ClientHttpRequest 进行包装以实现缓冲的功能。这对于需要读取请求或响应内容多次的情况非常有用。

4-3-3-使用自定义的 HttpMessageConverter 构造

如果需要处理自定义的 HTTP 响应格式,例如 XML、CSV、JSON 等,可以实现自定义的 HttpMessageConverter,并在 RestTemplate 的构造函数中传入。这种方式可以自由控制响应数据的转换过程,支持更多的数据格式

Constructor : RestTemplate(List<HttpMessageConverter<?>> messageConverters)

Description: Create a new instance of the RestTemplate using the given list of HttpMessageConverter to use.

这个我没有研究

5-使用RestTemplate.exchange() 方法发送 HTTP 请求

2023-10-17 18:16,其他方法我还没有研究,目前只研究了使用 RestTemplate.exchange() 方法发送 HTTP 请求

RestTemplate 源码中, exchange() 方法有多个重载实现,例如如下方法

url 表示要发送请求的 URL,method 表示请求方法(GET、POST 等),requestEntity 表示请求体,responseType 表示期望的响应类型等

   @Override
   public <T> ResponseEntity<T> exchange(
            URI url, 
            HttpMethod method, 
            @Nullable HttpEntity<?> requestEntity, 
            Class<T> responseType
        ) throws RestClientException {
            // ……
   }

下面直接给出代码实例

package com.example.lkcoffee.controller;

import com.example.lkcoffee.persistence.entity.Coffee;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.*;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;

import java.net.URI;
import java.util.*;

/**
 * @author yang.jianming3
 * @version 1.0
 * @since 2023/10/16 10:19
 */
@Tag(name = "rest-template API")
@Slf4j
@RestController
@RequestMapping("/rest-template")
public class RestTemplateController {

    @Autowired
    private RestTemplate restTemplate;
    
    @Operation(summary = "The request's query parameters.")
    @GetMapping("/get-coffee-list")
    public ResponseEntity<?> getCoffeeList() {

        // 1. 设置请求头
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity<?> requestEntity = new HttpEntity<>(httpHeaders);

        // 2. 设置查询参数组成的 URI
        URI url = UriComponentsBuilder.fromHttpUrl("http://localhost:8888/coffee/list")
                .build()
                .toUri();

        // 3. 发送请求并获取响应
        HttpMethod httpMethod = HttpMethod.GET;
        
        //4. 设置期望的响应类型
        Class<ArrayList> responseType = ArrayList.class;

        ResponseEntity<ArrayList> response = restTemplate.exchange(url, httpMethod, requestEntity, responseType);

        // 获取响应体内容
        ArrayList responseBody = response.getBody();
        return ResponseEntity.ok(responseBody);
    }

    @Operation(summary = "根据 ID 获取咖啡")
    @GetMapping("/get-coffee")
    public ResponseEntity<?> getCoffee(@RequestParam Integer id) {

        // 1. 设置请求头
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity<?> requestEntity = new HttpEntity<>(httpHeaders);

        // 2. 设置有 路径 组成的 URI
        URI url = UriComponentsBuilder.fromHttpUrl("http://localhost:8888/coffee/{id}")
                .buildAndExpand(id)
                .toUri();

        log.info("url : {}", url);

        // 3. 发送请求并获取响应
        HttpMethod httpMethod = HttpMethod.GET;
        
        //4. 设置期望的响应类型
        Class<Coffee> responseType = Coffee.class;

        ResponseEntity<Coffee> response = restTemplate.exchange(url, httpMethod, requestEntity, responseType);

        ObjectMapper objectMapper = new ObjectMapper();

        String responseString = null;
        try {
            responseString = objectMapper.writeValueAsString(response);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        log.info("response :  {}", responseString);

        Coffee responseBody = new Coffee();
        
        if (Objects.equals(HttpStatus.OK, response.getStatusCode())) {
            // 获取响应体内容
            responseBody = response.getBody();
            return ResponseEntity.ok(responseBody);
        } else if (Objects.equals(HttpStatus.NOT_FOUND, response.getStatusCode())) {
            return ResponseEntity.notFound().build();
        } else {
            return ResponseEntity.ok(responseBody);
        }
    }
}

设置HTTP请求头部属性待补充

6-RestTemplate其他方法

待补充

7-总结

下面还可以介绍使用 UriComponents 、UriComponentsBuilder 构造 URL

还可以介绍一下 HttpHeaders \ HttpEntity \ RequestEntity \ ResponseEntity 等与HTTP请求相关的类