前言

RestTemplateHttpClient 都可以用于发送 HTTP 请求,但它们定位不同。

可以把它们理解成:

  • HttpClient:底层 HTTP 通信工具
  • RestTemplate:Spring 对 HttpClient 等工具的高级封装

一、RestTemplate 是什么?

RestTemplate 是 Spring 提供的一个:

  • 同步阻塞式 HTTP 客户端
  • 用于调用:
    • 微服务接口
    • 第三方 API
    • 文件下载
    • 外部系统通信

它本质上是:

Spring 对 HTTP 请求进行了再次封装

让你:

  • 不用手动处理连接
  • 不用自己解析响应流
  • 不用自己转换 JSON

可以像调用普通方法一样发 HTTP 请求。


二、RestTemplate 和 HttpClient 区别

业务代码
   ↓
RestTemplate
   ↓
ClientHttpRequestFactory
   ↓
HttpClient / OkHttp / JDK HttpURLConnection

也就是说:

RestTemplate 是上层封装
HttpClient 是底层实现

三、二者核心区别

对比项 RestTemplate HttpClient
所属 Spring Apache
定位 高级HTTP工具 底层HTTP客户端
使用难度 简单 较复杂
JSON转换 自动 手动
与Spring整合 非常好 一般
学习成本
适合 微服务调用 底层定制
是否阻塞 有同步和异步
推荐状态 官方已维护模式 仍常用

四、为什么很多 Spring Boot 项目喜欢用 RestTemplate?

因为它:

  • 和 Spring 完美整合
  • 非常简单
  • 自动 JSON 转对象
  • 支持拦截器
  • 支持负载均衡
  • 支持超时配置
  • 支持统一异常处理

例如:

User user = restTemplate.getForObject(url, User.class);

就直接得到 Java 对象了。

不用:

  • 创建连接
  • 读取 InputStream
  • Jackson 转换 JSON

五、RestTemplate 的工作原理

当你:

restTemplate.getForObject(...)

时,内部会:

1. 创建HTTP请求
2. 建立TCP连接
3. 发送请求
4. 获取响应
5. 自动解析JSON
6. 转换成Java对象

六、Spring Boot 如何引入 RestTemplate

其实:

spring-web

里已经包含了。

Spring Boot Web 项目一般天然就有。


七、Maven 引入

如果没有,可以引入:

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

这里面已经包含:

  • Spring MVC
  • RestTemplate
  • Jackson

八、如何创建 RestTemplate

1.RestTemplate 创建

通常会放到配置类里。

@Configuration
public class RestTemplateConfig {

    @Bean
    public RestTemplate restTemplate() {
            SimpleClientHttpRequestFactory factory =
            new SimpleClientHttpRequestFactory();

    	// 连接超时
    	factory.setConnectTimeout(5000);
    	// 读取超时
    	factory.setReadTimeout(10000);
    	return new RestTemplate(factory);
    }
}

然后:

@Autowired
private RestTemplate restTemplate;

即可使用。


2.RestTemplate + Apache HttpClient 创建

因为 RestTemplate 默认实现性能一般。

很多公司会:

RestTemplate + Apache HttpClient

如果你使用 Spring Boot 2.x , 引入 httpclient :

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

如果你使用 Spring Boot 3.x , 引入 httpclient :

<dependency>
    <groupId>org.apache.httpcomponents.client5</groupId>
    <artifactId>httpclient5</artifactId>
</dependency>

通过 RestTemplate + Apache HttpClient可获得:

  • 连接池
  • 更高性能
  • 更稳定

示例:

@Bean
public RestTemplate restTemplate() {
    PoolingHttpClientConnectionManager manager = new PoolingHttpClientConnectionManager();
    //设置最大连接池
	manager.setMaxTotal(500);
    //每个目标主机最多连接
	manager.setDefaultMaxPerRoute(100);
    
    RequestConfig config = RequestConfig.custom()
            //建立TCP连接最大等待时间。例如:服务器不存在、网络不通
            .setConnectTimeout(5000)
            //读取超时。连接成功后服务器迟迟不返回数据,等待多久。
            .setResponseTimeout(10000)
            .build();

    CloseableHttpClient httpClient = HttpClients.custom()
                 .setConnectionManager(manager)
                .setDefaultRequestConfig(config)
                .build();

    HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);

    return new RestTemplate(factory);
}

九、RestTemplate 常用方法

方法 作用
getForObject GET请求返回对象
getForEntity GET返回完整响应
postForObject POST返回对象
postForEntity POST返回完整响应
exchange 最强大的通用方法

十、GET 请求示例


1. 请求接口

假设:

GET http://localhost:8080/user/1

返回:

{
  "id":1,
  "name":"Tom"
}

2. Java对象

@Data
public class User {

    private Long id;

    private String name;
}

3. GET调用

@Autowired
private RestTemplate restTemplate;

public void testGet() {

    String url = "http://localhost:8080/user/1";

    User user = restTemplate.getForObject(url, User.class);

    System.out.println(user);
}

十一、GET 带参数


方式1:拼接URL

String url = "http://localhost:8080/user?id=1";

方式2:占位符(推荐)

String url = "http://localhost:8080/user/{id}";

User user = restTemplate.getForObject(
        url,
        User.class,
        1
);

等价于:

/user/1

十二、POST 请求示例


1. 请求接口

POST /user/add

请求体:

{
  "name":"Tom"
}

2. 请求对象

@Data
public class UserReq {

    private String name;
}

3. POST调用

public void testPost() {

    String url = "http://localhost:8080/user/add";

    UserReq req = new UserReq();
    req.setName("Tom");

    User user = restTemplate.postForObject(
            url,
            req,
            User.class
    );

    System.out.println(user);
}

十三、exchange()(推荐)

实际项目中:

90% 会使用 exchange()

因为它:

  • 支持所有 HTTP 方法
  • 支持请求头
  • 支持 token
  • 支持文件上传
  • 支持复杂请求

1.GET 示例

HttpHeaders headers = new HttpHeaders();
headers.add("token", "123456");

HttpEntity<Void> entity = new HttpEntity<>(headers);

ResponseEntity<User> response =
        restTemplate.exchange(
                "http://localhost:8080/user/1",
                HttpMethod.GET,
                entity,
                User.class
        );

User user = response.getBody();

2.GET 请求 + token 示例

@Autowired
private RestTemplate restTemplate;

public void test() {

    String url = "http://localhost:8080/user/1";

    // 1. 创建请求头
    HttpHeaders headers = new HttpHeaders();

    // 2. 设置 token
    headers.set("Authorization", "Bearer abc123");

    // 3. 封装 HttpEntity
    HttpEntity<Void> entity = new HttpEntity<>(headers);

    // 4. 发起请求
    ResponseEntity<String> response =
            restTemplate.exchange(
                    url,
                    HttpMethod.GET,
                    entity,
                    String.class
            );

    // 5. 获取响应
    String body = response.getBody();

    System.out.println(body);
}

3. POST 示例

HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);

UserReq req = new UserReq();
req.setName("Tom");

HttpEntity<UserReq> entity = new HttpEntity<>(req, headers);

ResponseEntity<User> response =
        restTemplate.exchange(
                "http://localhost:8080/user/add",
                HttpMethod.POST,
                entity,
                User.class
        );

User user = response.getBody();

4.POST 请求 + token

public void testPost() {

    String url = "http://localhost:8080/order/create";

    // 请求头
    HttpHeaders headers = new HttpHeaders();

    headers.setContentType(MediaType.APPLICATION_JSON);

    headers.setBearerAuth("abc123");

    // 请求体
    Map<String, Object> body = new HashMap<>();
    body.put("name", "Tom");
    body.put("age", 18);

    // 封装
    HttpEntity<Map<String, Object>> entity = new HttpEntity<>(body, headers);

    // 请求
    ResponseEntity<String> response =
            restTemplate.exchange(
                    url,
                    HttpMethod.POST,
                    entity,
                    String.class
            );

    System.out.println(response.getBody());
}

setBearerAuth 是什么?

这个:

headers.setBearerAuth("abc123");

等价于:

Authorization: Bearer abc123

它是 Spring 专门提供的快捷方法。

如果不是 Bearer token 怎么办?

有些系统:

token: xxx

或者:

Authorization: xxx

那就:

headers.set("token", "abc123");

或者:

headers.set("Authorization", "abc123");

即可。

5.简单文件下载(byte[])示例

对于一些小文件可以使用字节数组来下载:

public void download() throws Exception {

    String url = "http://localhost:8080/file/test.pdf";

    // 请求头
    HttpHeaders headers = new HttpHeaders();

    headers.setBearerAuth("abc123");

    HttpEntity<Void> entity = new HttpEntity<>(headers);

    // 请求
    ResponseEntity<byte[]> response = restTemplate.exchange(
                    url,
                    HttpMethod.GET,
                    entity,
                    byte[].class
            );

    // 获取文件字节
    byte[] bytes = response.getBody();

    // 保存文件
    Files.write(
            Paths.get("D:/test.pdf"),
            bytes
    );

    System.out.println("下载完成");
}

6. InputStream 文件下载(推荐)

上面的示例使用 byte[] 会一次性加载整个文件到内存,对于大文件 大文件:500MB 或者1GB 这样的文件 容易 OOM(内存溢出)。

更推荐下面的使用 Resource 和 inputStream 边读边写大文件,它不会一次性加载整个文件,更适合大文件

public void download2() throws Exception {

    String url = "http://localhost:8080/file/big.zip";

    HttpHeaders headers = new HttpHeaders();

    headers.setBearerAuth("abc123");

    HttpEntity<Void> entity = new HttpEntity<>(headers);

    ResponseEntity<Resource> response =
            restTemplate.exchange(
                    url,
                    HttpMethod.GET,
                    entity,
                    Resource.class
            );

    // 获取输入流
    InputStream inputStream = response.getBody().getInputStream();

    // 输出流
    FileOutputStream out = new FileOutputStream("D:/big.zip");

    byte[] buffer = new byte[8192];

    int len;

    while ((len = inputStream.read(buffer)) != -1) {
        out.write(buffer, 0, len);
    }

    out.close();
    inputStream.close();

    System.out.println("下载成功");
}

十四、拦截器

上面的 exchange() 示例代码里,我们每次发送请求时都会往请求头里设置 token 或者参数以及请求头,如果你的项目调用很多请求,每次构建请求时都需要手动设置 token 或者往请求头里塞一些公共的参数,这很不方便,我们可以在配置的时候使用拦截器。

拦截器常见用途:

  • 自动添加 token或签名、请求头等
  • 自动打印日志(URL、参数、响应时间)
  • 请求失败自动重试

让我们回到上面 第八步 如何创建 RestTemplate

1.在配置类里使用 setInterceptors 增加拦截器

@Configuration
public class RestTemplateConfig {

	@Bean
	public RestTemplate restTemplate() {
	    SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
	
		// 连接超时
		factory.setConnectTimeout(5000);
		// 读取超时
		factory.setReadTimeout(10000);
		
		RestTemplate restTemplate = new RestTemplate(factory);
        //设置拦截器
	    restTemplate.setInterceptors(Collections.singletonList((request, body, execution) -> {
	        HttpHeaders headers = request.getHeaders();
	        headers.set("Authorization", getToken());
            // 继续执行请求
	        return execution.execute(request, body);
	    }));
	    return restTemplate;
	}
}

解释:

上面的配置代码会给所有 RestTemplate 请求自动添加 token,这样以后每次发送请求时执行 时不需要每次手动往请求头塞 token,它会自动帮你加。例如:执行 restTemplate.exchange(...)

代码里的restTemplate.setInterceptors(...)是在给 RestTemplate 配置 HTTP 请求拦截器(Interceptor),
它的作用类似于AOP / Filter / Spring MVC Interceptor
即:

	在真正发送 HTTP 请求之前
	或者收到响应之后
	做统一处理

企业中的经典写法:

public class TokenInterceptor implements ClientHttpRequestInterceptor {

    @Override
    public ClientHttpResponse intercept(
            HttpRequest request,
            byte[] body,
            ClientHttpRequestExecution execution)
            throws IOException {

        request.getHeaders().setBearerAuth(getToken());

        return execution.execute(request, body);
    }
}

然后配置类里:

restTemplate.setInterceptors(
        Arrays.asList(
                new TokenInterceptor()
        )
);

2.请求前和请求后处理

上面的示例是在请求发送前拦截进行增加 token 处理,在拦截器里不仅可以在请求发送前进行拦截操作,也可以在请求后拦截处理。

@Bean(name = "restTemplateClient")
public RestTemplate restTemplate() {

    SimpleClientHttpRequestFactory requestFactory =
            new SimpleClientHttpRequestFactory();

    requestFactory.setReadTimeout(readTimeout);

    requestFactory.setConnectTimeout(connectTimeout);

    //使用 BufferingClientHttpRequestFactory 重复读取响应流
    RestTemplate restTemplate = new RestTemplate(new BufferingClientHttpRequestFactory(requestFactory));

    restTemplate.setInterceptors(
            Collections.singletonList(
                    (request, body, execution) -> {

                        // ======================
                        // 请求前
                        // ======================

                        System.out.println("请求开始");
                        System.out.println("请求URL: " + request.getURI());
                        System.out.println("请求方法: " + request.getMethod());

                        request.getHeaders().setBearerAuth(getToken());

                        // 记录开始时间
                        long start = System.currentTimeMillis();

                        // 真正发送请求
                        ClientHttpResponse response = execution.execute(request, body);

                        // ======================
                        // 响应后
                        // ======================

                        long end = System.currentTimeMillis();

                        System.out.println("请求结束");

                        System.out.println("耗时: " + (end - start));

                        System.out.println("响应状态码: " + response.getStatusCode());

                        //只有使用了 BufferingClientHttpRequestFactory 才能重复读取,否则 拦截器里读完后业务代码就读不到了,会导致返回body为空
                        String responseBody = StreamUtils.copyToString(
                                        response.getBody(),
                                        StandardCharsets.UTF_8);

                        System.out.println("响应: " +responseBody);
                        
                        return response;
                    }
            ));

    return restTemplate;
}

3.setInterceptors 是什么?

它的定义:

	public void setInterceptors(List<ClientHttpRequestInterceptor> interceptors)

意思:给 RestTemplate 配置请求拦截器链

Lambda 参数是什么?
这里:

	(request, body, execution) -> {

其实等价于:

	new ClientHttpRequestInterceptor() {
	@Override
	public ClientHttpResponse intercept(
	        HttpRequest request,
	        byte[] body,
	        ClientHttpRequestExecution execution
	) throws IOException {
	
	}
}

(1).request

HttpRequest request 代表:HTTP请求对象

里面有:

URL
请求头
请求方法

例如:

request.getURI()
request.getMethod()
request.getHeaders()

(2). body

byte[] body代表:请求体数据

例如 :

POST 请求里的 JSON:

{
“name”:“Tom”
}

实际上最终会变成:byte[]

(3).execution

这个最重要。

ClientHttpRequestExecution execution

它代表:真正执行HTTP请求的人

4.多个拦截器执行顺序

例如:

restTemplate.setInterceptors(
        Arrays.asList(
                interceptor1,
                interceptor2,
                interceptor3
        )
);

执行顺序:

interceptor1 before
    interceptor2 before
        interceptor3 before
            真正请求
        interceptor3 after
    interceptor2 after
interceptor1 after

类似责任链模式

十五、封装类

如果 每个请求都需要设置:token、timestamp、sign 你可以使用上面第十四部分拦截器进行统一拦截设置。但是如果你每个请求都要动态的根据参数计算并设置 sign ,那仅靠拦截器不够。因为拦截器拿不到业务层的签名逻辑。

这时通常可以封装一个工具类使用。

@Component
public class HttpClient {

    @Autowired
    private RestTemplate restTemplate;

    public <T> T post(String url, Object req, Class<T> clazz) {
        HttpHeaders headers = buildHeaders(req);

        HttpEntity<Object> entity = new HttpEntity<>(req, headers);

        ResponseEntity<T> response = restTemplate.exchange(
                        url,
                        HttpMethod.POST,
                        entity,
                        clazz);

        return response.getBody();
    }

    private HttpHeaders buildHeaders(Object req) {
        HttpHeaders headers = new HttpHeaders();

        String timestamp = String.valueOf(System.currentTimeMillis());

        headers.set("timestamp", timestamp);

        headers.set("token", getToken());

        headers.set("sign",generateSign(req, timestamp));

        return headers;
    }
}

调用:

UserResp resp = httpClient.post("https://api.xxx.com/user/query", req, UserResp.class);

十六、如何获取文件名、文件类型、判断请求成功?

HTTP 响应头里通常有:

Content-Disposition

例如:

Content-Disposition:
attachment; filename=test.pdf

获取文件名:

String disposition = response.getHeaders().getFirst("Content-Disposition");

System.out.println(disposition);

获取 Content-Type:

MediaType contentType = response.getHeaders().getContentType();

//application/pdf
System.out.println(contentType);

如何判断下载成功:

if (response.getStatusCode() == HttpStatus.OK) {
    System.out.println("成功");
}

十七、RestTemplate 支持哪些底层实现?

默认:

JDK HttpURLConnection

但也可以替换成:

  • Apache HttpClient(最常见)
  • OkHttp

例如:

HttpComponentsClientHttpRequestFactory

底层用 HttpClient。


十八、RestTemplate 缺点

Spring 官方现在:

已不再积极推荐 RestTemplate

而推荐:

WebClient

原因:

  • RestTemplate 是阻塞式
  • 高并发性能一般
  • 不适合响应式

十九、现在为什么还大量使用?

因为:

  • 老项目太多
  • 简单稳定
  • 易于维护
  • 大量公司还在用

特别是:

传统 Spring Boot 项目

仍然大量使用。


二十、RestTemplate vs WebClient

对比 RestTemplate WebClient
模型 同步阻塞 异步非阻塞
性能 一般
学习难度 简单 较高
使用广泛度 非常广 越来越广
推荐程度 维护模式 官方推荐

二十一、什么时候用 RestTemplate?

适合:

  • 公司老项目
  • 普通微服务调用
  • 中小并发
  • 简单HTTP调用

二十二、什么时候用 WebClient?

适合:

  • 高并发
  • 网关
  • 响应式系统
  • 大量IO请求

二十三、企业里真实情况

很多公司:

老项目:RestTemplate
新项目:OpenFeign/WebClient

因为:

Feign 更适合微服务

例如:

@FeignClient("user-service")
public interface UserClient {

    @GetMapping("/user/{id}")
    User get(@PathVariable Long id);
}

比 RestTemplate 更方便。


二十四、学习路线

建议学习顺序:

1. HTTP基础
2. RestTemplate
3. HttpClient
4. OpenFeign
5. WebClient

因为:

RestTemplate 是理解微服务调用最好的入门

二十五、一个完整实际案例

@Service
public class UserService {

    @Autowired
    private RestTemplate restTemplate;

    public User getUser(Long id) {

        String url =
                "http://localhost:8080/user/{id}";

        HttpHeaders headers = new HttpHeaders();

        headers.add("token", "abc123");

        HttpEntity<Void> entity =
                new HttpEntity<>(headers);

        ResponseEntity<User> response =
                restTemplate.exchange(
                        url,
                        HttpMethod.GET,
                        entity,
                        User.class,
                        id
                );

        return response.getBody();
    }
}

更多推荐