聊聊Http Client和RestTemplate

本文讲解和演示了几款主流的Http Client,以及如何使用spring-web中RestTemplate,结合生产故障说明了设置请求超时时间的重要性。阅读本文将提升你的Http客户端编码能力。

在分布式架构Java项目中,注册到同一注册中心的服务,相互调用时通常会使用Feign组件。当你需要在项目中对接第三方Http接口时,就需要一款能够发送Http请求的工具,即Http Client。今天我们一起来看看几款常用的Http Client:

  • Java 8中的HttpUrlConnection
  • Apache HttpComponents项目中的HttpClient或httpclient5
  • Squareup的okhttpokhttp3
  • spring-web的RestTemplateWebClient

本文示例代码gitHub地址,欢迎下载:github.com/kqcaihong/m...

一、准备web服务

1.1 创建项目

使用Java 8,创建一个Spring Boot的web项目作为服务端,pom依赖如下。

java 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.14</version>
    <relativePath/> <!-- lookup parent from repository -->
  </parent>
  
  <groupId>com.learn</groupId>
  <artifactId>more</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>more</name>

  <properties>
    <java.version>8</java.version>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.30</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>
</project>

在application.properties中添加配置(不引入数据库)

ini 复制代码
spring.application.name=learn-more
server.port=8010

创建启动类

typescript 复制代码
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MoreApplication {

  public static void main(String[] args) {
    SpringApplication.run(MoreApplication.class, args);
  }
}

创建UserController模拟用户管理功能,提供http接口。此处将记录保存在Map中,模拟数据库操作。

kotlin 复制代码
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
@JsonPropertyOrder({"id", "name", "age"})
public class User {

  private Long id;
  private String name;
  private int age;

  public User(String name, int age) {
    this.name = name;
    this.age = age;
  }

  @Override
  public String toString() {
    try {
      return new ObjectMapper().writeValueAsString(this);
    } catch (JsonProcessingException e) {
      return "";
    }
  }
}
java 复制代码
import com.learn.more.entiry.User;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/user")
@RestController
public class UserController implements InitializingBean {
  // 生成ID
  private static final AtomicLong ID_GENERATOR = new AtomicLong(0);
  // 模拟数据库保存记录
  private static final Map<Long, User> USER_MAP = new ConcurrentHashMap<>();

  @GetMapping("/queryById")
  public User queryById(@RequestParam Long id) {
    return USER_MAP.get(id);
  }

  @GetMapping("/queryAll")
  public List<User> queryAll() {
    return USER_MAP.values().stream().sorted(Comparator.comparingLong(User::getId)).collect(Collectors.toList());
  }

  @PostMapping("/add")
  public User add(@RequestBody User user) {
    user.setId(ID_GENERATOR.incrementAndGet());
    USER_MAP.put(user.getId(), user);
    return user;
  }

  @PostMapping("/addByParam")
  public User addByParam(@RequestParam String name, @RequestParam int age) {
    User user = new User(name, age);
    user.setId(ID_GENERATOR.incrementAndGet());
    USER_MAP.put(user.getId(), user);
    return user;
  }

  // 初始化一条记录
  @Override
  public void afterPropertiesSet() {
    User bob = new User(ID_GENERATOR.incrementAndGet(), "Bob", 33);
    USER_MAP.put(bob.getId(), bob);
  }
}

1.2 测试

启动项目,用postman来测试接口,然后我们用Http Client调用接口。

二、Http Client演示

2.1 HttpURLConnection

HttpURLConnection是JDK中的标准类,是一种比较原生的实现。Java11中用HTTPClient取代了HttpUrlConnection类。

通过下面示例可以看出,HttpURLConnection几乎没有封装,每次请求时需要自己创建连接、处理IO流等。除非你不想在项目中额外引入其他依赖时,否则不建议使用它。

ini 复制代码
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Objects;

public class HttpURLClient {

  public static void main(String[] args) {
    String result = doGet("http://localhost:8010/user/queryAll");
    System.out.println(result);

    String param = "{"name":"Andy","age":18}";
    result = doPost("http://localhost:8010/user/add", param);
    System.out.println(result);
  }

  public static String doGet(String httpUrl) {
    HttpURLConnection connection = null;
    InputStream is = null;
    BufferedReader br = null;
    String result = null;
    try {
      // 创建连接对象
      connection = (HttpURLConnection) new URL(httpUrl).openConnection();
      connection.setRequestMethod("GET");
      // 设置连接超时时间:15秒
      connection.setConnectTimeout(15000);
      // 设置读取超时时间:30秒
      connection.setReadTimeout(30000);
      // 建立连接,发送请求
      connection.connect();
      // 获取输入流
      if (connection.getResponseCode() == 200) {
        is = connection.getInputStream();
        br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
        StringBuilder sbf = new StringBuilder();
        String line;
        while ((line = br.readLine()) != null) {
          sbf.append(line);
          sbf.append(System.lineSeparator());
        }
        result = sbf.toString();
      }
    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      try {
        if (Objects.nonNull(br)) {
          br.close();
        }
        if (Objects.nonNull(is)) {
          is.close();
        }
      } catch (Exception e) {
        e.printStackTrace();
      }
      if (Objects.nonNull(connection)) {
        connection.disconnect();
      }
    }
    return result;
  }

  public static String doPost(String httpUrl, String param) {
    HttpURLConnection connection = null;
    InputStream is = null;
    OutputStream os = null;
    BufferedReader br = null;
    String result = null;
    try {
      URL url = new URL(httpUrl);
      connection = (HttpURLConnection) url.openConnection();
      connection.setRequestMethod("POST");
      connection.setConnectTimeout(15000);
      connection.setReadTimeout(30000);
      // 默认值为:false,当向远程服务器写数据时,需要设置为true
      connection.setDoOutput(true);
      // 默认值为:true,当前向远程服务读取数据时,设置为true
      connection.setDoInput(true);
      // 设置入参、响应格式
      connection.setRequestProperty("Content-Type", "application/json");
      connection.setRequestProperty("Accept-Type", "application/json");
      connection.connect();
      // 向输出流写出入参
      os = connection.getOutputStream();
      os.write(param.getBytes());
      // 通过输入流读取响应
      if (connection.getResponseCode() == 200) {
        is = connection.getInputStream();
        br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
        StringBuilder sbf = new StringBuilder();
        String line;
        while ((line = br.readLine()) != null) {
          sbf.append(line);
        }
        result = sbf.toString();
      }
    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      try {
        if (Objects.nonNull(br)) {
          br.close();
        }
        if (Objects.nonNull(is)) {
          is.close();
        }
        if (Objects.nonNull(os)) {
          os.close();
        }
      } catch (Exception e) {
        e.printStackTrace();
      }
      if (Objects.nonNull(connection)) {
        connection.disconnect();
      }
    }
    return result;
  }
}

目前,我仅看到分布式定时任务调度框架xxl-job(2.2.0版本),以及spring-web中RestTemplate的SimpleClientHttpRequestFactory实现,使用了它。

2.2 Apache httpcomponents

httpcomponents是Apache的一个开源项目,功能强大,使用率也很高,基本上是 Java 平台中事实上的标准HTTP客户端。该项目下的组件分为以下两部分,还提供了客户端身份验证、HTTP状态管理和HTTP连接管理等组件

  • HttpCore:一组低级HTTP传输组件,可用于构建自定义客户端和服务器端HTTP服务;
  • HttpClient:基于HttpCore的符合HTTP协议的HTTP客户端实现。

引入依赖,目前最新版本是org.apache.httpcomponents.client5。我们先用4.5版本演示。

xml 复制代码
<dependency>
  <groupId>org.apache.httpcomponents</groupId>
  <artifactId>httpclient</artifactId>
  <version>4.5</version>
</dependency>

HttpClient支持对某个请求设置超时时间等配置。

ini 复制代码
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import org.apache.http.HttpEntity;
import org.apache.http.client.config.RequestConfig;
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 ApacheHttpClient {

  public static void main(String[] args) {
    String result = doGet("http://localhost:8010/user/queryAll");
    System.out.println(result);

    String param = "{"name":"Andy","age":18}";
    result = doPost("http://localhost:8010/user/add", param);
    System.out.println(result);
  }

  public static String doGet(String url) {
    // 创建一个httpClient实例
    CloseableHttpClient httpClient = HttpClients.createDefault();
    // 创建httpGet连接实例
    HttpGet httpGet = new HttpGet(url);
    httpGet.setHeader("Accept-Type", "application/json");
    RequestConfig requestConfig = RequestConfig.custom()
        // 连接超时时间
        .setConnectTimeout(10000)
        // 请求超时时间
        .setConnectionRequestTimeout(30000)
        // 读取超时时间
        .setSocketTimeout(60000)
        .build();
    // 为请求设置配置
    httpGet.setConfig(requestConfig);
    CloseableHttpResponse response = null;
    String result = "";
    try {
      response = httpClient.execute(httpGet);
      // 通过返回对象获取返回数据
      HttpEntity entity = response.getEntity();
      result = EntityUtils.toString(entity);
    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      try {
        if (Objects.nonNull(response)) {
          response.close();
        }
        if (Objects.nonNull(httpClient)) {
          httpClient.close();
        }
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
    return result;
  }

  public static String doPost(String url, String param) {
    CloseableHttpClient httpClient = HttpClients.createDefault();
    // 创建httpPost远程连接实例
    HttpPost httpPost = new HttpPost(url);
    // 配置请求参数实例
    RequestConfig requestConfig = RequestConfig.custom()
        .setConnectTimeout(10000)
        .setConnectionRequestTimeout(30000)
        .setSocketTimeout(60000)
        .build();
    httpPost.setConfig(requestConfig);
    httpPost.addHeader("Content-Type", "application/json");
    httpPost.setEntity(new StringEntity(param, StandardCharsets.UTF_8));
    CloseableHttpResponse response = null;
    String result = "";
    try {
      response = httpClient.execute(httpPost);
      // 从响应对象中获取响应内容
      HttpEntity entity = response.getEntity();
      result = EntityUtils.toString(entity);
    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      try {
        if (Objects.nonNull(response)) {
          response.close();
        }
        if (Objects.nonNull(httpClient)) {
          httpClient.close();
        }
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
    return result;
  }
}

再用HttpClient5演示,引入下面依赖

xml 复制代码
<dependency>
  <groupId>org.apache.httpcomponents.client5</groupId>
  <artifactId>httpclient5</artifactId>
  <version>5.1.1</version>
</dependency>
java 复制代码
import java.nio.charset.StandardCharsets;
import java.util.concurrent.Future;
import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
import org.apache.hc.client5.http.async.methods.SimpleRequestBuilder;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
import org.apache.hc.client5.http.impl.async.HttpAsyncClients;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.concurrent.FutureCallback;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.io.entity.StringEntity;
import org.apache.hc.core5.util.Timeout;

public class ApacheHttpClient5 {

  public static void main(String[] args) {
    doGet("http://localhost:8010/user/queryAll");

    String param = "{"name":"Andy","age":18}";
    doPost("http://localhost:8010/user/add", param);
  }

  // 异步get
  public static void doGet(String url) {
    try (CloseableHttpAsyncClient client = HttpAsyncClients.createDefault()) {
      client.start();
      // 支持request设置超时
      RequestConfig config = RequestConfig.custom()
          .setConnectTimeout(Timeout.ofSeconds(30))
          .setResponseTimeout(Timeout.ofSeconds(30))
          .build();
      SimpleHttpRequest request = SimpleRequestBuilder.get(url)
          .setRequestConfig(config)
          .build();
      Future<SimpleHttpResponse> future = client.execute(request, new FutureCallback<SimpleHttpResponse>() {
        @Override
        public void completed(SimpleHttpResponse simpleHttpResponse) {
          System.out.println(simpleHttpResponse.getBodyText());
        }

        @Override
        public void failed(Exception e) {

        }

        @Override
        public void cancelled() {

        }
      });
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  // 同步post
  public static void doPost(String url, String param) {
    HttpPost httpPost = new HttpPost(url);
    httpPost.setEntity(new StringEntity(param, StandardCharsets.UTF_8));
    httpPost.setHeader("Accept", ContentType.APPLICATION_JSON.toString());
    httpPost.setHeader("Content-Type", ContentType.APPLICATION_JSON.toString());
    try (CloseableHttpClient client = HttpClients.createDefault()) {
      CloseableHttpResponse response = client.execute(httpPost);
      HttpEntity entity = response.getEntity();
      System.out.println(EntityUtils.toString(entity));
      response.close();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

2.3 OkHttpClient

OkHttpClient是由美国Square公司提供的开源库,支持链接复用、响应缓存、请求失败自动重连等。从Android4.4开始HttpURLConnection的底层实现采用的是okHttp。 我们同时引入okhttp和okhttp3依赖,实际使用中选择一个合适版本即可。

xml 复制代码
<dependency>
  <groupId>com.squareup.okhttp</groupId>
  <artifactId>okhttp</artifactId>
  <version>2.7.5</version>
  <!--  因为下方引入了okhttp3,避免冲突-->
  <exclusions>
    <exclusion>
      <groupId>com.squareup.okio</groupId>
      <artifactId>okio</artifactId>
    </exclusion>
  </exclusions>
</dependency>

<dependency>
  <groupId>com.squareup.okhttp3</groupId>
  <artifactId>okhttp</artifactId>
  <version>4.8.0</version>
</dependency>

从demo可以看出,OkHttpClient支持客户端复用,针对client设置超时时间。

java 复制代码
import com.squareup.okhttp.Callback;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;
import java.io.IOException;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import okhttp3.MediaType;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;

public class OkHttpClientTest {

  public static void main(String[] args) throws IOException {
    doAsyncGet("http://localhost:8010/user/queryAll");

    String param = "{"name":"Andy","age":18}";
    String result = doPostByOkhttp3("http://localhost:8010/user/add", param);
    System.out.println(result);
  }

  private static void doAsyncGet(String url) {
    try {
      // client复用,对客户端设置超时
      OkHttpClient client = new OkHttpClient();
      client.setConnectTimeout(30L, TimeUnit.SECONDS);
      client.setReadTimeout(30L, TimeUnit.SECONDS);
      client.setWriteTimeout(30L, TimeUnit.SECONDS);

      Request request = new Request.Builder()
          .get()
          .url(url)
          .build();
      client.newCall(request).enqueue(new Callback() {
        @Override
        public void onFailure(Request request, IOException e) {

        }

        @Override
        public void onResponse(Response response) throws IOException {
          System.out.println(response.body().string());
        }
      });
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  private static String doPostByOkhttp3(String url, String param) throws IOException {
    okhttp3.OkHttpClient client = new okhttp3.OkHttpClient.Builder()
        .connectTimeout(30L, TimeUnit.SECONDS)
        .readTimeout(30L, TimeUnit.SECONDS)
        .build();
    RequestBody requestBody = RequestBody.create(param, MediaType.parse("application/json"));
    okhttp3.Request request = new okhttp3.Request.Builder()
        .url(url)
        .post(requestBody)
        .addHeader("Accept-Type", "application/json")
        .build();
    okhttp3.Response response = client.newCall(request).execute();
    if (response.isSuccessful()) {
      ResponseBody body = response.body();
      return Objects.nonNull(body) ? body.string() : "";
    }
    return "";
  }
}

三、spring-web中RestTemplate

RestTemplate是Spring封装的处理同步HTTP请求的模版类。从官方文档可知,在Spring 5.0之后,官方推荐一种非阻塞响应式的HTTP 请求处理方案WebClient,支持同步和异步以及流场景。RestTemplate将在未来的版本中弃用,并且不会添加主要的新特性。

Spring官方文档地址:docs.spring.io/spring-fram...

今天,我们来简单了解一下如何使用RestTemplate。

3.1 创建RestTemplate

RestTemplate在spring-web.jar中。当我们引入spring-boot-starter-web依赖时,间接引入了spring-web。如文档所说,RestTemplate作为模板类,底层支持多种Http Client,可根据项目情况选择其一,默认采用HttpURLConnection 在启动类中创建ClientHttpRequestFactoryRestTemplate实例。

java 复制代码
@Bean
public RestTemplate restTemplate(@Qualifier("simpleClientHttpRequestFactory") ClientHttpRequestFactory factory) {
  return new RestTemplate(factory);
}

@Bean
public ClientHttpRequestFactory simpleClientHttpRequestFactory() {
  SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
  // HttpURLConnection只支持设置这两个超时时间
  factory.setConnectTimeout(10000);
  factory.setReadTimeout(30000);
  return factory;
}

// 当使用okHttp3作为RestTemplate底层Http Client时
@Bean
public ClientHttpRequestFactory okHttp3ClientHttpRequestFactory() {
  OkHttp3ClientHttpRequestFactory factory = new OkHttp3ClientHttpRequestFactory();
  factory.setConnectTimeout(10000);
  factory.setReadTimeout(30000);
  factory.setWriteTimeout(30000);
  return factory;
}

当使用无参构造器创建RestTemplate时,默认使用SimpleClientHttpRequestFactory,它使用了JDK中HttpUrlConnection作为Http Client。

typescript 复制代码
@Bean
public RestTemplate restTemplate() {
  return new RestTemplate();
}

3.2 GET请求

创建测试类,用getForObject()调用/user/queryAll接口,响应类型声明为List.class时,集合中元素类型不是User而是LinkedHashMap。另外,我们可以将响应类型声明为User[]

java 复制代码
import com.learn.more.entiry.User;
import java.util.Collections;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = MoreApplication.class)
class RestTemplateTest {

  public static final String BASE_URL = "http://localhost:8010";

  @Autowired
  private RestTemplate restTemplate;

  @Test
  void get() {
    String url = BASE_URL + "/user/queryAll";
    // restTemplate 会把复杂的对象转换成 LinkedHashMap
    // 
    List allUser = restTemplate.getForObject(url, List.class);
    System.out.println(allUser);
  }
  
  @Test
  void getAsArray() {
    String url = BASE_URL + "/user/queryAll";
    User[] allUser = restTemplate.getForObject(url, User[].class);
    System.out.println(allUser);
  }
}

为了从响应中直接获得List<User>,可以使用抽象类ParameterizedTypeReference,创建它的匿名子类。此时需要使用exchange()execute()方法发送请求,更细致地控制请求行为,如HttpMethod、Header信息、响应类型等。

Java 复制代码
@Test
void getAsList() {
  String url = BASE_URL + "/user/queryAll";
  MultiValueMap<String, String> header = new LinkedMultiValueMap<>();
  header.put(HttpHeaders.CONTENT_TYPE, Collections.singletonList(MediaType.APPLICATION_JSON_VALUE));
  header.put(HttpHeaders.ACCEPT, Collections.singletonList(MediaType.APPLICATION_JSON_VALUE));
  HttpEntity<User> entity = new HttpEntity<>(header);
  // 匿名子类
  ParameterizedTypeReference<List<User>> type = new ParameterizedTypeReference<List<User>>() {
  };
  ResponseEntity<List<User>> allUser = restTemplate.exchange(url, HttpMethod.GET, entity, type);
  System.out.println(allUser.getBody());
}

3.3 POST请求

我们来看看Content-Type分别为application/x-www-form-urlencodedapplication/json 两种情况。

Java 复制代码
  @Test
  void postByParam() {
    String url = BASE_URL + "/user/addByParam";
    // form表单传参
    MultiValueMap<String, String> header = new LinkedMultiValueMap<>();
    header.add(HttpHeaders.CONTENT_TYPE, (MediaType.APPLICATION_FORM_URLENCODED_VALUE));
    MultiValueMap<String, Object> map = new LinkedMultiValueMap<>();
    map.add("name", "Cathy");
    map.add("age", 23);
    HttpEntity<MultiValueMap<String, Object>> httpEntity = new HttpEntity<>(map, header);
    ResponseEntity<User> response = restTemplate.exchange(url, HttpMethod.POST, httpEntity, User.class);
    System.out.println(response.getBody());
  }

  @Test
  void postByBody() {
    String url = BASE_URL + "/user/add";
    User tom = new User("Cathy", 23);
    // body体传参,json格式
    User user = restTemplate.postForObject(url, tom, User.class);
    System.out.println(user);
  }

此外,form表单传参时,还可以将请求参数以K=V方式用&拼接来构建HttpEntity,如下。

java 复制代码
  @Test
  void postByParam() {
    String url = BASE_URL + "/user/addByParam";
    // 设置请求的 Content-Type
    MultiValueMap<String, String> header = new LinkedMultiValueMap<>();
    header.add(HttpHeaders.CONTENT_TYPE, (MediaType.APPLICATION_FORM_URLENCODED_VALUE));
    String param = new StringBuilder().append("name=Judy").append("&").append("age=18").toString();
    HttpEntity<String> httpEntity = new HttpEntity<>(param, header);
    ResponseEntity<User> response = restTemplate.exchange(url, HttpMethod.POST, httpEntity, User.class);
    System.out.println(response.getBody());
  }

使用RestTemplate执行DELETE、PUT等请求,以及文件上传功能,本文将不做演示。

3.4 请求超时设置

同步请求Http接口时,如果不设置超时时间,对方服务故障或接口响应时间异常,就会阻塞当前线程。

生产故障

我曾经遇到这样的生产故障:在公司将机房迁移到海外后,原本正常的某个定时任务,执行中经常出现阻塞:业务中请求某个国内第三方系统接口时,一直没有响应。 经过排查,发现使用SimpleClientHttpRequestFactory创建RestTemplate时,并没有主动设置超时时间:默认超时时间为-1,即永不超时。 因此,调用外部Http接口时,应该设置超时时间,避免因为第三方接口故障拖累我方服务。

故障处理

做如下修改之后,定时任务仍会偶尔某次执行失败,但是不会阻塞后续正常运行。

四、总结

各个工具的特点简单总结如下。在实际开发中,按照需求场景选择合适的即可。

  • 如果不想添加任何外部库,那么使用原生HttpURLConnectionHTTPClient(JDK11及之后版本)即可;
  • Apache HttpClient灵活性更高,有更多的参考文档;
  • OkHttpClient性能最佳,客户端对象可重复使用,功能丰富,高度可配置;
  • 使用SpringSpring Boot开发应用时,使用RestTemplateWebClient即可。
相关推荐
P7进阶路几秒前
实现用户登录系统的前后端开发
java
2401_857617621 分钟前
“无缝购物体验”:跨平台网上购物商城的设计与实现
java·开发语言·前端·安全·架构·php
事业运财运爆棚3 分钟前
7种server的服务器处理结构模型
java·linux·服务器
西岭千秋雪_19 分钟前
设计模式の中介者&发布订阅&备忘录模式
java·观察者模式·设计模式·中介者模式·备忘录模式
憶巷26 分钟前
MyBatis中动态SQL执行原理
java·sql·mybatis
重生之绝世牛码26 分钟前
Java设计模式 —— 【结构型模式】享元模式(Flyweight Pattern) 详解
java·大数据·开发语言·设计模式·享元模式·设计原则
seasugar32 分钟前
记一次Maven拉不了包的问题
java·maven
Allen Bright40 分钟前
【Java基础-26.1】Java中的方法重载与方法重写:区别与使用场景
java·开发语言
苹果酱056742 分钟前
Golang的文件解压技术研究与应用案例
java·vue.js·spring boot·mysql·课程设计
秀儿y1 小时前
单机服务和微服务
java·开发语言·微服务·云原生·架构