RestTemplate客户端

RestTemplate是Spring Web中用于和Restful API交互的客户端工具类,使用RestTemplate我们可以很轻松的发送和处理HTTP请求和响应。

注入RestTemplate对象

实际开发中,RestTemplate通常以SpringBean的形式注入其它对象,以便复用这个配置好的Bean。在传统Spring工程中,我们可以通过XML进行配置。

<bean id="restTemplate" class="org.springframework.web.client.RestTemplate"/>

在SpringBoot工程中,我们则可能更倾向于使用JavaConfig配置。

package com.gacfox.demo.config;

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

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

发送GET请求

假设http://localhost:8080/api/v1/getUserById是一个返回JSON数据的HTTP接口,它的返回内容对应User类。这里我们可以使用RestTemplategetForObject()方法发起GET请求获取User对象,下面是一个例子。

package com.gacfox.demo.service;

import com.gacfox.demo.bean.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;

@Service("demoService")
public class DemoService {
    @Autowired
    private RestTemplate restTemplate;

    public User getUserById(Long userId) {
        UriComponents uriComponents = UriComponentsBuilder
                .fromHttpUrl("http://localhost:8080/api/v1/getUserById")
                .queryParam("userId", userId)
                .build();
        String url = uriComponents.encode().toString();
        return restTemplate.getForObject(url, User.class);
    }
}

代码中,我们使用UriComponentsBuilder构建了GET请求的URL,然后使用restTemplate.getForObject()方法发起GET请求,并指定了返回值类型为User。请求发出时,RestTemplate会自动添加请求头Accept: application/json,表示期望返回JSON数据,得到响应数据后,则会自动将JSON数据反序列化为User对象。

如果服务端未返回200状态码或解析返回数据时出错,RestTemplate将抛出对应的异常类型。

发送POST请求

发送POST请求也是类似的,下面代码我们使用POST请求发送JSON数据并接收JSON响应。

package com.gacfox.demo.service;

import com.gacfox.demo.bean.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

@Service("demoService")
public class DemoService {
    @Autowired
    private RestTemplate restTemplate;

    public User postUser(User user) {
        return restTemplate.postForObject("http://localhost:8080/api/v1/postUser", user, User.class);
    }
}

代码中,postForObject()方法的3个参数分别是HTTP接口地址、请求参数和响应类型。请求参数user会被自动序列化为JSON数据,响应数据也会被自动反序列化为对应类型。这里RestTemplate也会对应添加Content-TypeAccept请求头。

exchange()方法

RestTemplate为各种HTTP方法都封装了对应的请求方法,不过实际开发中,对于更复杂的业务场景,我们可能更倾向于使用exchange()方法。exchange()方法相比前面介绍的getForObject()postForObject()更加底层一些,它能够处理GET、POST等各种请求类型,此外它还能够更精确的控制请求头,此外还支持复杂的泛型参数类型。

package com.gacfox.demo.service;

import com.gacfox.demo.bean.QueryParam;
import com.gacfox.demo.bean.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.util.List;

@Service("demoService")
public class DemoService {
    @Autowired
    private RestTemplate restTemplate;

    public List<User> getUserList(QueryParam queryParam) {
        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-Type", MediaType.APPLICATION_JSON_VALUE);
        headers.add("Accept", MediaType.APPLICATION_JSON_VALUE);
        HttpEntity<QueryParam> httpEntity = new HttpEntity<>(queryParam, headers);
        ResponseEntity<List<User>> responseEntity = restTemplate.exchange(
                "http://localhost:8080/api/v1/getUserList",
                HttpMethod.POST,
                httpEntity,
                new ParameterizedTypeReference<List<User>>() {
                });
        return responseEntity.getBody();
    }
}

上面代码比之前更复杂,exchange()方法需要HttpEntity类型作为参数,它可以视作HTTP请求的封装,其中包含了请求头、请求JSON体等信息,注意构造HttpEntity时请求头信息是必要参数的,不过Content-TypeAccept请求头仍是默认添加的,此外exchange()方法的第4个参数是请求体的类型,除了直接传Class类型,它也支持通过ParameterizedTypeReference传递泛型参数,这在实际开发中非常有用。

exchange()方法的返回值是ResponseEntity,它包含了HTTP响应的详细信息,如响应状态码、响应头、响应体等,这里我们使用getBody()方法获取响应体,它会被自动反序列化为对应的类型。

此外,和前面的方法类似,当服务端没能返回200状态码或响应解析出错时,RestTemplate会抛出对应的异常类型。

发送form-data类型的表单请求

RestTemplate默认发送JSON数据,但有些情况如文件上传等我们可能需要发送form-data类型的表单请求,postForObject()方法是不支持表单请求的,但我们可以通过exchange()发送手动构造的表单数据实现。

package com.gacfox.demo.service;

import com.gacfox.bean.ApiResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

@Service("demoService")
public class DemoService {
    @Autowired
    private RestTemplate restTemplate;

    public void upload(Long userId, byte[] imageData) {
        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-Type", MediaType.MULTIPART_FORM_DATA_VALUE);
        headers.add("Accept", MediaType.APPLICATION_JSON_VALUE);
        MultiValueMap<String, Object> formData = new LinkedMultiValueMap<>();
        formData.add("userId", String.valueOf(userId));
        ByteArrayResource byteArrayResource = new ByteArrayResource(imageData) {
            @Override
            public String getFilename() {
                return "avatar.jpg";
            }
        };
        formData.add("image", byteArrayResource);
        HttpEntity<MultiValueMap<String, Object>> httpEntity = new HttpEntity<>(formData, headers);
        restTemplate.exchange(
                "http://localhost:8080/api/v1/upload",
                HttpMethod.POST,
                httpEntity,
                new ParameterizedTypeReference<ApiResult<Void>>() {
                });
    }
}

代码中,我们设置发送数据的Content-Typemultipart/form-data,然后构造了一个MultiValueMap类型的表单数据,其中userId字段是普通文本字段,image字段是文件字段,注意image字段我们使用ByteArrayResource类型重新封装byte[]并重写了getFilename()方法,这样服务端才能正确解析。

处理文件下载

RestTemplate处理文件下载非常简单,我们只需要将返回值接收类型设置为byte[],这样就可以二进制数据的形式读取服务端返回的内容。

package com.gacfox.demo.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

@Service("demoService")
public class DemoService {
    @Autowired
    private RestTemplate restTemplate;

    public byte[] download() {
        return restTemplate.getForObject("http://localhost:8080/api/v1/download", byte[].class);
    }
}

客户端配置

RestTemplate底层的配置信息可以在创建Bean时通过ClientHttpRequestFactory指定,下面是JavaConfig的例子,代码中我们配置了RestTemplate的连接超时和读超时时间。

package com.gacfox.demo.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;

@Configuration
public class RestTemplateConfig {
    @Bean
    public ClientHttpRequestFactory simpleClientHttpRequestFactory() {
        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
        factory.setReadTimeout(15000);
        factory.setConnectTimeout(15000);
        return factory;
    }

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

对于XML配置方式也是类似的,这里就不再赘述了。

自定义HTTP客户端

实际上,RestTemplate底层默认是基于HttpURLConnection的,我们也可以将其换成其它的HTTP客户端,如Apache HttpClient、OkHttp等,下面是JavaConfig的例子,代码中我们配置了RestTemplate使用OkHttpClient作为底层实现。首先我们引入OkHttp3的依赖。

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

如下JavaConfig配置了RestTemplate使用OkHttpClient作为底层实现。

package com.gacfox.demo.config;

import okhttp3.OkHttpClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

import java.util.concurrent.TimeUnit;

@Configuration
public class RestTemplateConfig {
    @Bean
    public RestTemplate restTemplate() {
        OkHttpClient okHttpClient = new OkHttpClient.Builder()
                .connectionPool(new okhttp3.ConnectionPool(20, 300, TimeUnit.SECONDS))
                .connectTimeout(15, TimeUnit.SECONDS)
                .readTimeout(15, TimeUnit.SECONDS)
                .build();
        OkHttp3ClientHttpRequestFactory factory = new OkHttp3ClientHttpRequestFactory(okHttpClient);
        return new RestTemplate(factory);
    }
}

代码中我们先创建了OkHttp3的客户端,并为其配置了超时、连接池等信息,随后我们使用该客户端实例化RestTemplate

注意:随着时间的发展和OkHttp等HTTP客户端库的迭代更新,RestTemplate配置底层客户端的API未来情况可能发生变化,不过原理都是类似的,以上代码仅供参考。

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。
Copyright © 2017-2024 Gacfox All Rights Reserved.
Build with NextJS | Sitemap