1. 前言
无论是微服务还是SOA,都面临着服务间的远程调用。那么服务间的远程调用方式有哪些呢?
常见的远程调用方式有以下2种:
- RPC:Remote Produce Call 远程过程调用,类似的还有RMI。自定义数据格式,基于原生TCP通信,速度快,效率高。早期的webservice,现在热门的dubbo,都是RPC的典型代表
- HTTP:http其实是一种网络传输协议,基于TCP,规定了数据传输的格式。现在客户端浏览器与服务端通信基本都是采用Http协议,也可以用来进行远程服务调用。缺点是消息封装臃肿,优势是对服务的提供和调用方没有任何技术限定,自由灵活,更符合微服务理念。
现在热门的Rest风格,就可以通过http协议来实现。
1.1 HTTP 客户端
在项目初始阶段,我们技术选型时 需要考虑自己来实现对请求和响应的处理,开源世界已经有很多的http客户端工具,能够帮助我们做这些事情
- HttpClient
- OKHttp
- JDK原生的URLConnection(默认的)
2. RestTemplate
spring框架提供的RestTemplate类可用于在应用中调用rest服务,它简化了与http服务的通信方式,统一了RESTful的标准,封装了http链接,我们只需要传入url及返回值类型即可。相较于之前常用的HttpClient,RestTemplate是一种更优雅的调用RESTful服务的方式。
RestTemplate默认依赖JDK提供http连接的能力(HttpURLConnection),如果有需要的话也可以通过setRequestFactory方法替换为例如Apache HttpComponents、Netty或OkHttp等其它HTTP library。
其实spring并没有真正的去实现底层的http请求(3次握手),而是集成了别的http请求,spring只是在原有的各种http请求进行了规范标准,让开发者更加简单易用,底层默认用的是jdk的http请求。
- 官方接口文档:快速访问
2.1 restTemplate的类结构
可以看出它继承自HttpAccessor这个统一的处理器,然后再继承自InterceptingHttpAccessor,这个拦截转换器,最终RestTemplate实现了封装httpClient的模板工具类。
整体结果布局:
2.2 RestTemplate 主要方法
以下是http方法和restTempalte方法的比对映射,可以看出restTemplate提供了操作http的方法,其中exchange方法可以用来做任何的请求,一般我们都是用它来封装不同的请求方式。
2.3 RestTemplate 的优劣势
- 优点:连接池、超时时间设置、支持异步、请求和响应的编解码
- 缺点:依赖别的spring版块、参数传递不灵活
3. SpringBoot 中使用 RestTemplate
3.1 maven 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
3.2 RestTemplate 配置
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(ClientHttpRequestFactory factory){
return new RestTemplate(factory);
}
@Bean
public ClientHttpRequestFactory simpleClientHttpRequestFactory(){
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
//客户端与服务端建立连接超时时间
factory.setConnectTimeout(1000);
//客户端从服务端读取数据的超时时间
factory.setReadTimeout(2000);
return factory;
}
}
3.3 RestTemplate 使用
@Component
public class HttpRequestHelper {
@Resource
private RestTemplate restTemplate;
//一些自定义的请求头参数
public static final String supplierID = "";
public static final String interfacekey = "";
/**
* get请求 返回 string
*
* @param url 请求的url
* @param jsonData 请求的json
* @return
*/
public String restGet(String url, String jsonData) {
return request(url, jsonData, HttpMethod.GET);
}
/**
* Get请求获取实体类
*
* @param url 请求的url
* @param responseType 返回的类型
* @param parms 不限定个数的参数
* @param <T> 泛型
* @return
*/
public <T> T getForEntity(String url, Class<T> responseType, Object... parms) {
return (T) restTemplate.getForEntity(url, responseType, parms);
}
/**
* Get请求
*
* @param url
* @param parm
* @return
*/
public String get(String url, Map<String, Object> parm) {
return restTemplate.getForEntity(url, String.class, parm).getBody();
}
/**
* @param url 请求的url
* @param jsonData json数据
* @param httpMethod
* @return
*/
private String request(String url, String jsonData, HttpMethod httpMethod) {
ResponseEntity<String> response = null;
try {
HttpEntity<String> requestEntity = new HttpEntity<String>(jsonData);
response = restTemplate.exchange(url, httpMethod, requestEntity, String.class);
} catch (Exception ex) {
ex.printStackTrace();
return "";
}
return response.getBody().toString();
}
/**
* DLT专用执行方法
*
* @param param 请求参数:可以添加一些常量请求值
* @param url 访问的url
* @param method 请求的方法
* @return
*/
private String execute(Map<String, Object> param, String url, HttpMethod method) {
HttpHeaders headers = this.getDefaultHeader();
Map<String, Object> requestor = this.getDefaultParam();
param.put("requestor", requestor);
param.put("supplierID", supplierID);
HttpEntity<Map<String, Object>> requestEntity = new HttpEntity<>(param, headers);
ResponseEntity<String> response = restTemplate.exchange(url, method, requestEntity, String.class);
return response.getBody();
}
/**
* 获取默认的头请求信息
*/
private HttpHeaders getDefaultHeader() {
String timestamp = "" + System.currentTimeMillis();
String signature = EncoderByMd5(supplierID + timestamp + interfacekey);
HttpHeaders headers = new HttpHeaders();
headers.add("signature", signature);
headers.add("timestamp", timestamp);
return headers;
}
/**
* 获取默认的参数
*/
private Map<String, Object> getDefaultParam() {
Map<String, Object> defParam = new HashMap<>();
defParam.put("invoker", "xx");
defParam.put("operatorName", "xx");
return defParam;
}
/**
* 通过MD5加密
*
* @param str
* @return
*/
public static String EncoderByMd5(String str) {
if (str == null) {
return null;
}
try {
// 确定计算方法
MessageDigest md5 = MessageDigest.getInstance("MD5");
BASE64Encoder base64en = new BASE64Encoder();
// 加密后的字符串
return base64en.encode(md5.digest(str.getBytes("utf-8"))).toUpperCase();
} catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
return null;
}
}
}
- 单元测试:
@SpringBootTest
@RunWith(SpringRunner.class)
public class RestTemplateTest {
@Resource
private HttpRequestHelper httpRequestHelper;
@Test
public void test_rest() {
String url="http://localhost:8080/getList";
//组装请求参数
Map<String,Object> parmMap =new HashMap<String,Object>();
String result = httpRequestHelper.get(url, parmMap);
System.out.println(result);
}
}
3.4 RestTemplate 文件上传下载
(1)二进制流文件上传(POST)
/**
* 二进制流作为请求体 上传文件
* body 请求体
* headers 请求头
* requestParams 请求参数
* HttpEntity(@Nullable T body, @Nullable MultiValueMap<String, String> headers)
*/
@Test
public void testMethod() throws Exception {
// 请求头
HttpHeaders headers = new HttpHeaders();
headers.set("Content-Type", "application/octet-stream");
File file = new File("/tmp/1.jpeg");
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
byte[] fileByte = outputStream.toByteArray();
// 请求体
HttpEntity<byte[]> entity = new HttpEntity<byte[]>(fileByte, headers);
RestTemplate restTemplate = new RestTemplate();
// 封装请求参数
MultiValueMap<String, String> requestParams = new LinkedMultiValueMap<>();
requestParams.add("expires", "259200");
requestParams.add("dir", "filepath");
String url = "http://localhost/binaryUpload/upload?fileName=test.jpg";
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url)
.queryParams(requestParams);
Object uploadResponse = restTemplate.postForObject(builder.toUriString(), entity, Object.class);
Assert.assertNotNull(uploadResponse);
}
3.5 总结
RestTemplate的作为一款非常不错的rest请求工具,屏蔽了复杂的HttpClient的实现细节,向外暴露出简单、易于使用的接口,使得我们的开发工作越来越简单、高效,更多的方法工具可以研究一下restTemplate的具体Api,打开源码,一切都了如指掌。
4. 升入源码理解RestTemplate核心点
4.1 转换器 HttpMessageConverter
4.2 底层连接工厂 ClientHttpRequestFactory
4.3 拦截器 ClientHttpRequestInterceptor