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的类结构 

springboot中远程调用的工具类get请求传对象 springboot远程调用rpc接口_Data

可以看出它继承自HttpAccessor这个统一的处理器,然后再继承自InterceptingHttpAccessor,这个拦截转换器,最终RestTemplate实现了封装httpClient的模板工具类。

整体结果布局:

springboot中远程调用的工具类get请求传对象 springboot远程调用rpc接口_json_02

2.2  RestTemplate 主要方法

以下是http方法和restTempalte方法的比对映射,可以看出restTemplate提供了操作http的方法,其中exchange方法可以用来做任何的请求,一般我们都是用它来封装不同的请求方式。

springboot中远程调用的工具类get请求传对象 springboot远程调用rpc接口_spring_03

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