GVKun编程网logo

Spring MVC中使用RestTemplate访问REST服务详解(spring mvc restful)

21

在这里,我们将给大家分享关于SpringMVC中使用RestTemplate访问REST服务详解的知识,让您更了解springmvcrestful的本质,同时也会涉及到如何更有效地java–Sprin

在这里,我们将给大家分享关于Spring MVC中使用RestTemplate访问REST服务详解的知识,让您更了解spring mvc restful的本质,同时也会涉及到如何更有效地java – SpringBoot @WebMvcTest,自动装配RestTemplateBuilder、RestTemplate相关组件:ClientHttpRequestInterceptor【享学Spring MVC】、spring boot / cloud (八) 使用RestTemplate来构建远程调用服务、Spring Boot 入门 - 进阶篇(4)- REST访问(RestTemplate)的内容。

本文目录一览:

Spring MVC中使用RestTemplate访问REST服务详解(spring mvc restful)

Spring MVC中使用RestTemplate访问REST服务详解(spring mvc restful)

在Java Web开发中,我们通常需要通过GET、POST请求其他系统提供的服务。其中,JDK自带的HttpURLConnection、Apache HttpClient等方式都可以实现。当然,这些方式都有一个很明显的缺陷,那就是代码很繁琐。而Spring提供的RestTemplate封装了这些库的实现,可以让我们的HTTP请求更加简洁、直观。

在RestTemplate中定义了11个独立的操作,它们分别是:

 方法  描述
 delete()  在特定的URL上对资源执行HTTP DELETE操作
 exchange()  在URL上执行特定的HTTP方法,返回的ResponseEntity包含了响应体所映射成的对象
 execute()  在URL上执行特定的HTTP方法,返回一个从响应体映射得到的对象
 getForEntity()  发送一个HTTP GET请求,返回的ResponseEntity包含了响应体所映射成的对象
 getForObject()  发送一个HTTP GET请求,返回根据响应体映射形成的对象
 postForEntity()  POST数据到一个URL,返回的ResponseEntity包含了响应体所映射成的对象
 postForLocation()  POST数据到一个URL,返回新创建资源的URL
 postForObject()  POST数据到一个URL,返回根据响应体映射形成的对象
 put()  PUT资源到特定的URL
 headForHeaders()  发送HTTP HEAD请求,返回包含特定资源URL的HTTP头
 optionsForAllow()  发送HTTP OPTIONS请求,返回对特定URL的Allow头信息

接下来,我将对常用的几个方法分别介绍。

(1)在项目中添加依赖:

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

<!-- Jackson对自动解析JSON和XML格式的支持 -->

<dependency>

    <groupId>com.fasterxml.jackson.jaxrs</groupId>

    <artifactId>jackson-jaxrs-json-provider</artifactId>

</dependency>

<dependency>

    <groupId>com.fasterxml.jackson.dataformat</groupId>

    <artifactId>jackson-dataformat-xml</artifactId>

</dependency>

 

<!-- HttpClient -->

<dependency>

    <groupId>org.apache.httpcomponents</groupId>

    <artifactId>httpclient</artifactId>

</dependency>

 

<!-- Fastjson -->

<dependency>

    <groupId>com.alibaba</groupId>

    <artifactId>fastjson</artifactId>

    <version>1.2.46</version>

</dependency>

 

(2)在项目中注入RestTemplate:

在注入RestTemplate的bean的时候,可以通过ClientHttpRequestFactory指定RestTemplate发起HTTP请求的底层实现所采用的类库。对此,ClientHttpRequestFactory接口主要提供了以下两种实现方法:

i)SimpleClientHttpRequestFactory:

也就是底层使用java.net包提供的方式创建Http连接请求。示例代码如下:

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

package cn.zifangsky.springbootdemo.config;

 

import java.nio.charset.Charset;

import java.util.ArrayList;

import java.util.List;

 

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.http.converter.FormHttpMessageConverter;

import org.springframework.http.converter.HttpMessageConverter;

import org.springframework.http.converter.StringHttpMessageConverter;

import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;

import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter;

import org.springframework.web.client.RestTemplate;

 

@Configuration

public class RestTemplateConfig {

 

    /**

     * 返回RestTemplate

     * @param factory

     * @return

     */

    @Bean

    public RestTemplate restTemplate(ClientHttpRequestFactory factory){

        //消息转换器,一般情况下可以省略,只需要添加相关依赖即可

//        List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();

//        messageConverters.add(new StringHttpMessageConverter(Charset.forName("UTF-8")));

//        messageConverters.add(new FormHttpMessageConverter());

//        messageConverters.add(new MappingJackson2XmlHttpMessageConverter());

//        messageConverters.add(new MappingJackson2HttpMessageConverter());

        

        RestTemplate restTemplate = new RestTemplate(factory);

//        restTemplate.setMessageConverters(messageConverters);

        

        return restTemplate;

    }

    

    /**

     * ClientHttpRequestFactory接口的第一种实现方式,即:

     * SimpleClientHttpRequestFactory:底层使用java.net包提供的方式创建Http连接请求

     * @return

     */

    @Bean

    public SimpleClientHttpRequestFactory simpleClientHttpRequestFactory(){

        SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();

        

        requestFactory.setReadTimeout(5000);

        requestFactory.setConnectTimeout(5000);

        

        return requestFactory;

    }

 

}

 

 

ii)HttpComponentsClientHttpRequestFactory(推荐使用):

也就是底层使用Httpclient连接池的方式创建Http连接请求。示例代码如下:

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

package cn.zifangsky.springbootdemo.config;

 

import java.nio.charset.Charset;

import java.util.ArrayList;

import java.util.List;

import java.util.concurrent.TimeUnit;

 

import org.apache.http.Header;

import org.apache.http.client.HttpClient;

import org.apache.http.impl.client.DefaultConnectionKeepAliveStrategy;

import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;

import org.apache.http.impl.client.HttpClientBuilder;

import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;

import org.apache.http.message.BasicHeader;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.http.client.ClientHttpRequestFactory;

import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;

import org.springframework.http.converter.FormHttpMessageConverter;

import org.springframework.http.converter.HttpMessageConverter;

import org.springframework.http.converter.StringHttpMessageConverter;

import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;

import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter;

import org.springframework.web.client.RestTemplate;

 

@Configuration

public class RestTemplateConfig {

 

    /**

     * 返回RestTemplate

     * @param factory

     * @return

     */

    @Bean

    public RestTemplate restTemplate(ClientHttpRequestFactory factory){

        //消息转换器,Spring Boot环境可省略,只需要添加相关依赖即可

//        List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();

//        messageConverters.add(new StringHttpMessageConverter(Charset.forName("UTF-8")));

//        messageConverters.add(new FormHttpMessageConverter());

//        messageConverters.add(new MappingJackson2XmlHttpMessageConverter());

//        messageConverters.add(new MappingJackson2HttpMessageConverter());

        

        RestTemplate restTemplate = new RestTemplate(factory);

//        restTemplate.setMessageConverters(messageConverters);

        

        return restTemplate;

    }

    

    /**

     * ClientHttpRequestFactory接口的另一种实现方式(推荐使用),即:

     * HttpComponentsClientHttpRequestFactory:底层使用Httpclient连接池的方式创建Http连接请求

     * @return

     */

    @Bean

    public HttpComponentsClientHttpRequestFactory httpComponentsClientHttpRequestFactory(){

        //Httpclient连接池,长连接保持30秒

        PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(30, TimeUnit.SECONDS);

        

        //设置总连接数

        connectionManager.setMaxTotal(1000);

        //设置同路由的并发数

        connectionManager.setDefaultMaxPerRoute(1000);

        

        //设置header

        List<Header> headers = new ArrayList<Header>();

        headers.add(new BasicHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.3; rv:36.0) Gecko/20100101 Firefox/36.04"));

        headers.add(new BasicHeader("Accept-Encoding", "gzip, deflate"));

        headers.add(new BasicHeader("Accept-Language", "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3"));

        headers.add(new BasicHeader("Connection", "keep-alive"));

        

        //创建HttpClient

        HttpClient httpClient = HttpClientBuilder.create()

                .setConnectionManager(connectionManager)

                .setDefaultHeaders(headers)

                .setRetryHandler(new DefaultHttpRequestRetryHandler(3, true)) //设置重试次数

                .setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy()) //设置保持长连接

                .build();

        

        //创建HttpComponentsClientHttpRequestFactory实例

        HttpComponentsClientHttpRequestFactory requestFactory =

                new HttpComponentsClientHttpRequestFactory(httpClient);

        

        //设置客户端和服务端建立连接的超时时间

        requestFactory.setConnectTimeout(5000);

        //设置客户端从服务端读取数据的超时时间

        requestFactory.setReadTimeout(5000);

        //设置从连接池获取连接的超时时间,不宜过长

        requestFactory.setConnectionRequestTimeout(200);

        //缓冲请求数据,默认为true。通过POST或者PUT大量发送数据时,建议将此更改为false,以免耗尽内存

        requestFactory.setBufferRequestBody(false);

        

        return requestFactory;

    }

    

}

 

 

(3)使用getForObject()方法发起GET请求:

getForObject()方法实际上是对getForEntity()方法的进一步封装,二者用法类似。 唯一的区别在于getForObject()方法只返回所请求类型的对象, 而getForEntity()方法会返回请求的对象以及响应的Header、响应状态码等额外信息。

三个getForObject()方法的签名如下:

 

1

2

3

4

5

<T> T getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException;

 

<T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException;

 

<T> T getForObject(URI url, Class<T> responseType) throws RestClientException;

示例代码如下:

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

package cn.zifangsky.SpringBootDemo.controller;

 

import java.util.HashMap;

import java.util.Map;

 

import org.junit.Test;

import org.junit.runner.RunWith;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.http.HttpEntity;

import org.springframework.http.HttpHeaders;

import org.springframework.http.HttpMethod;

import org.springframework.http.ResponseEntity;

import org.springframework.test.context.ContextConfiguration;

import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import org.springframework.test.context.web.WebAppConfiguration;

import org.springframework.web.client.RestTemplate;

 

import cn.zifangsky.springbootdemo.config.RestTemplateConfig;

import cn.zifangsky.springbootdemo.config.WebMvcConfig;

import cn.zifangsky.springbootdemo.model.DemoObj;

 

@RunWith(SpringJUnit4ClassRunner.class)

@ContextConfiguration(classes={WebMvcConfig.class,RestTemplateConfig.class})

@WebAppConfiguration("src/main/resources")

public class TestRestTemplate {

 

    @Autowired

    private RestTemplate restTemplate;

    

    /**

     * 测试最基本的Get请求

     */

    @Test

    public void testGetMethod1(){

        DemoObj obj = restTemplate.getForObject("http://127.0.0.1:9090/rest/testJson2?id={1}&name={2}"

                , DemoObj.class

                , 1,"Tom");

        

        System.out.println(obj);

    }

 

}

注:本篇文章的单元测试请求的REST服务均来源于上一篇文章:https://www.zifangsky.cn/1215.html

上面代码设置请求参数使用了数字占位符,同时getForObject()方法的最后一个参数是一个可变长度的参数,用于一一替换前面的占位符。当然,除了这种方式之外,还可以使用Map来设置参数,比如:

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

    /**

     * 测试Get请求另一种设置参数的方式

     */

    @Test

    public void testGetMethod2(){

        Map<String, String> uriVariables = new HashMap<String, String>();

        uriVariables.put("var_id", "1");

        uriVariables.put("var_name", "Tom");

        

        DemoObj obj = restTemplate.getForObject("http://127.0.0.1:9090/rest/testJson2?id={var_id}&name={var_name}"

                , DemoObj.class

                , uriVariables);

        

        System.out.println(obj);

    }

运行单元测试之后,最后输出如下:

DemoObj [id=2, name=Tom Ret]

此外需要注意的是,由于上面代码只是简单的单元测试,因此请求URL就直接硬编码在代码中了。实际开发则需要将之配置到配置文件或者Zookeeper、Redis中。比如这样:

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

package cn.zifangsky.springbootdemo.controller;

 

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.beans.factory.annotation.Value;

import org.springframework.http.MediaType;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RestController;

import org.springframework.web.client.RestTemplate;

 

import cn.zifangsky.springbootdemo.model.DemoObj;

 

@RestController

@RequestMapping("/restTemplate")

public class RestTemplateController {

 

    @Value("${SERVER_URL}")

    private String SERVER_URL;

    

    @Autowired

    private RestTemplate restTemplate;

    

    @RequestMapping(path="/getDemoObj",produces={MediaType.APPLICATION_JSON_UTF8_VALUE})

    public DemoObj getDemoObj(){

        DemoObj obj = restTemplate.getForObject(SERVER_URL + "/rest/testXML?id={1}&name={2}"

                , DemoObj.class

                , 1,"Tom");

        

        return obj;

    }

    

}

 

 

(4)使用getForEntity()方法发起GET请求:

三个getForEntity()方法的签名如下:

 

1

2

3

4

5

<T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables) throws RestClientException;

 

<T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException;

 

<T> ResponseEntity<T> getForEntity(URI url, Class<T> responseType) throws RestClientException;

示例代码如下:

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

    /**

     * 测试Get请求返回详细信息,包括:响应正文、响应状态码、响应Header等

     */

    @Test

    public void testGetMethod3(){

        ResponseEntity<DemoObj> responseEntity = restTemplate.getForEntity("http://127.0.0.1:9090/rest/testJson2?id={1}&name={2}"

                , DemoObj.class

                , 1,"Tom");

        

        DemoObj body = responseEntity.getBody();

        int statusCodeValue = responseEntity.getStatusCodeValue();

        HttpHeaders headers = responseEntity.getHeaders();

        

        System.out.println("responseEntity.getBody():" + body);

        System.out.println("responseEntity.getStatusCodeValue():" + statusCodeValue);

        System.out.println("responseEntity.getHeaders():" + headers);

    }

运行单元测试之后,最后输出如下:

responseEntity.getBody():DemoObj [id=2, name=Tom Ret]
responseEntity.getStatusCodeValue():200
responseEntity.getHeaders():{Date=[Fri, 09 Feb 2018 06:22:28 GMT], Content-Type=[application/json;charset=utf-8], Transfer-Encoding=[chunked]}

(5)使用postForObject()方法发起POST请求:

在RestTemplate中,POST请求跟GET请求类似,也可以使用如下三个方法来请求:

 

1

2

3

4

5

6

7

<T> T postForObject(String url, Object request, Class<T> responseType, Object... uriVariables)

            throws RestClientException;

 

<T> T postForObject(String url, Object request, Class<T> responseType, Map<String, ?> uriVariables)

            throws RestClientException;

 

<T> T postForObject(URI url, Object request, Class<T> responseType) throws RestClientException;

示例代码如下:

 

1

2

3

4

5

6

7

8

9

10

11

12

    /**

     * 测试最基本的Post请求

     */

    @Test

    public void testPostMethod1(){

        DemoObj request = new DemoObj(1l, "Tim");

        

        DemoObj obj = restTemplate.postForObject("http://127.0.0.1:9090/rest/testJson1"

                , request, DemoObj.class);

        

        System.out.println(obj);

    }

运行单元测试之后,最后输出如下:

DemoObj [id=2, name=Tim Ret]

(6)使用postForEntity()方法发起POST请求:

三个postForEntity()方法的签名如下:

 

1

2

3

4

5

6

7

<T> ResponseEntity<T> postForEntity(String url, Object request, Class<T> responseType, Object... uriVariables)

            throws RestClientException;

 

<T> ResponseEntity<T> postForEntity(String url, Object request, Class<T> responseType, Map<String, ?> uriVariables)

            throws RestClientException;

 

<T> ResponseEntity<T> postForEntity(URI url, Object request, Class<T> responseType) throws RestClientException;

示例代码如下:

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

    /**

     * 测试Post请求返回详细信息,包括:响应正文、响应状态码、响应Header等

     */

    @Test

    public void testPostMethod2(){

        DemoObj request = new DemoObj(1l, "Tim");

        

        ResponseEntity<DemoObj> responseEntity = restTemplate.postForEntity("http://127.0.0.1:9090/rest/testJson1"

                , request, DemoObj.class);

        

        DemoObj body = responseEntity.getBody();

        int statusCodeValue = responseEntity.getStatusCodeValue();

        HttpHeaders headers = responseEntity.getHeaders();

        

        System.out.println("responseEntity.getBody():" + body);

        System.out.println("responseEntity.getStatusCodeValue():" + statusCodeValue);

        System.out.println("responseEntity.getHeaders():" + headers);

    }

运行单元测试之后,最后输出如下:

responseEntity.getBody():DemoObj [id=2, name=Tim Ret]
responseEntity.getStatusCodeValue():200
responseEntity.getHeaders():{Date=[Fri, 09 Feb 2018 06:32:02 GMT], Content-Type=[application/json;charset=utf-8], Transfer-Encoding=[chunked]}

(7)使用exchange()方法执行指定的HTTP请求:

exchange()方法跟上面的getForObject()、getForEntity()、postForObject()、postForEntity()等方法不同之处在于它可以指定请求的HTTP类型。示例代码如下:

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

    /**

     * 测试Exchange请求

     */

    @Test

    public void testExchange(){

        //设置header

        HttpHeaders headers = new HttpHeaders();

        headers.add("Content-Type", "application/x-zifangsky");

        

        //设置参数

        String requestBody = "1#Converter";

        HttpEntity<String> requestEntity = new HttpEntity<String>(requestBody,headers);

 

        ResponseEntity<String> responseEntity = restTemplate.exchange("http://127.0.0.1:9090/convert"

                , HttpMethod.POST, requestEntity, String.class);

        

        System.out.println("responseEntity.getBody():" + responseEntity.getBody());

        System.out.println("responseEntity.getHeaders():" + responseEntity.getHeaders());

    }

运行单元测试之后,最后输出如下:

responseEntity.getBody():{“id”:2,”name”:”Converter Ret”}
responseEntity.getHeaders():{Date=[Fri, 09 Feb 2018 06:42:29 GMT], Content-Type=[application/x-zifangsky], Transfer-Encoding=[chunked]}

参考:

  • https://segmentfault.com/a/1190000011093597
  • http://rensanning.iteye.com/blog/2362105

java – SpringBoot @WebMvcTest,自动装配RestTemplateBuilder

java – SpringBoot @WebMvcTest,自动装配RestTemplateBuilder

我在测试 Spring Controller时遇到了问题.
我在我的测试类中使用注释@WebMvcTest.
当我运行测试时,我收到此错误:
没有’org.springframework.boot.web.client.RestTemplateBuilder’类型的限定bean

我在我的项目中使用RestTemplate用于其他类,所以我在我的主类中定义了一个bean:

@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
    return builder.build();
}

为了使它工作,我必须以这种方式定义我的restTemplate bean:

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

这是注释@WebMvcTest的问题还是我错过了什么?

谢谢

解决方法

是的,这确实感觉像一个bug. 但是,您可以通过将@AutoConfigureWebClient与现有的@WebMvcTest一起添加到测试类中来轻松解决

RestTemplate相关组件:ClientHttpRequestInterceptor【享学Spring MVC】

RestTemplate相关组件:ClientHttpRequestInterceptor【享学Spring MVC】

每篇一句

做事的人和做梦的人最大的区别就是行动力

前言

本文为深入了解Spring提供的Rest调用客户端RestTemplate开山,对它相关的一些组件做讲解。

Tips:请注意区分RestTemplateRedisTemplate哦~

ClientHttpRequestFactory

它是个函数式接口,用于根据URIHttpMethod创建出一个ClientHttpRequest来发送请求~

ClientHttpRequest它代表请求的客户端,该接口继承自HttpRequestHttpOutputMessage,只有一个ClientHttpResponse execute() throws IOException方法。其中Netty、HttpComponents、OkHttp3,HttpUrlConnection对它都有实现~
// @since 3.0  RestTemplate这个体系都是3.0后才有的
@FunctionalInterface
public interface ClientHttpRequestFactory {    

    // 返回一个ClientHttpRequest,这样调用其execute()方法就可以发送rest请求了~
    ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException;
}

它的继承树如下:
在这里插入图片描述
可以直观的看到,我们可以使用ApacheHttpClientOkHttp3Netty4都可,但这些都需要额外导包,默认情况下Spring使用的是java.net.HttpURLConnection

HttpClient最新版本:4.5.10
OkHttp最新版本:4.1.1(虽然版本号是4,但是GAV还是3哦:com.squareup.okhttp3)
Netty最新版本:4.1.39.Final(它的5版本可以宣告已死)

Spring4.0是新增了一个对异步支持的AsyncClientHttpRequestFactory(Spring5.0后标记为已废弃):

// 在Spring5.0后被标记为过时了,被org.springframework.http.client.reactive.ClientHttpConnector所取代(但还是可用的嘛)
@Deprecated
public interface AsyncClientHttpRequestFactory {

    // AsyncClientHttpRequest#executeAsync()返回的是ListenableFuture<ClientHttpResponse>
    // 可见它的异步是通过ListenableFuture实现的
    AsyncClientHttpRequest createAsyncRequest(URI uri, HttpMethod httpMethod) throws IOException;
}

使用工厂创建ClientHttpRequest ,然后我们发请求就不用关心具体httpClient内部的细节了(可插拔使用二方库、三方库)

SimpleClientHttpRequestFactory

它是Spring内置默认的实现,使用的是JDK内置的java.net.URLConnection作为client客户端。

public class SimpleClientHttpRequestFactory implements ClientHttpRequestFactory, AsyncClientHttpRequestFactory {

    private static final int DEFAULT_CHUNK_SIZE = 4096;
    @Nullable
    private Proxy proxy; //java.net.Proxy
    private boolean bufferRequestBody = true; // 默认会缓冲body
    
    // URLConnection''s connect timeout (in milliseconds).
    // 若值设置为0,表示永不超时 @see URLConnection#setConnectTimeout(int)
    private int connectTimeout = -1;
    // URLConnection#setReadTimeout(int) 
    // 超时规则同上
    private int readTimeout = -1;
    
    //Set if the underlying URLConnection can be set to ''output streaming'' mode.
    private boolean outputStreaming = true;

    // 异步的时候需要
    @Nullable
    private AsyncListenableTaskExecutor taskExecutor;
    ... // 省略所有的set方法
    
    @Override
    public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
        
        // 打开一个HttpURLConnection
        HttpURLConnection connection = openConnection(uri.toURL(), this.proxy);
        // 设置超时时间、请求方法等一些参数到connection
        prepareConnection(connection, httpMethod.name());

        //SimpleBufferingClientHttpRequest的excute方法最终使用的是connection.connect();
        // 然后从connection中得到响应码、响应体~~~
        if (this.bufferRequestBody) {
            return new SimpleBufferingClientHttpRequest(connection, this.outputStreaming);
        } else {
            return new SimpleStreamingClientHttpRequest(connection, this.chunkSize, this.outputStreaming);
        }
    }

    // createAsyncRequest()方法略,无非就是在线程池里异步完成请求
    ...
}

需要注意的是:JDK <1.8 doesn''t support getOutputStream with HTTP DELETE,也就是说如果JDK的版本低于1.8的话,那么Delete请求是不支持body体的。

Demo Show:

public static void main(String[] args) throws IOException {
    SimpleClientHttpRequestFactory clientFactory  = new SimpleClientHttpRequestFactory();
    
    // ConnectTimeout只有在网络正常的情况下才有效,因此两个一般都设置
    clientFactory.setConnectTimeout(5000); //建立连接的超时时间  5秒
    clientFactory.setReadTimeout(5000); // 传递数据的超时时间(在网络抖动的情况下,这个参数很有用)

    ClientHttpRequest client = clientFactory.createRequest(URI.create("https://www.baidu.com"), HttpMethod.GET);
    // 发送请求
    ClientHttpResponse response = client.execute();
    System.out.println(response.getStatusCode()); //200 OK
    System.out.println(response.getStatusText()); // OK
    System.out.println(response.getHeaders()); //

    // 返回内容 是个InputStream
    byte[] bytes = FileCopyUtils.copyToByteArray(response.getBody());
    System.out.println(new String(bytes, StandardCharsets.UTF_8)); // 百度首页内容的html
}

关于HttpURLConnection的API使用,需注意如下几点:

  1. HttpURLConnection对象不能直接构造,需要通过URL类中的openConnection()方法来获得
  2. HttpURLConnection的connect()函数,实际上只是建立了一个与服务器的TCP连接,并没有实际发送HTTP请求。HTTP请求实际上直到我们获取服务器响应数据(如调用getInputStream()、getResponseCode()等方法)时才正式发送出去

        1. 配置信息都需要在connect()方法执行之前完成
  3. HttpURLConnection是基于HTTP协议的,其底层通过socket通信实现。如果不设置超时(timeout),在网络异常的情况下,可能会导致程序僵死而不继续往下执行。==请务必100%设置==
  4. HTTP正文的内容是通过OutputStream流写入的, 向流中写入的数据不会立即发送到网络,而是存在于内存缓冲区中,待流关闭时,根据写入的内容生成HTTP正文
  5. 调用getInputStream()方法时,返回一个输入流,用于从中读取服务器对于HTTP请求的返回信息。
  6. HttpURLConnection.connect()不是必须的。当我们需要返回值时,比如我们使用HttpURLConnection.getInputStream()方法的时候它就会自动发送请求了,所以完全没有必要调用connect()方法了(没必要先建立Tcp嘛~)。

使用哪一个底层http库?

我们知道HttpURLConnection它在功能上是有些不足的(简单的提交参数可以满足)。绝大部分情况下Web站点的网页可能没这么简单,这些页面并不是通过一个简单的URL就可访问的,可能需要用户登录而且具有相应的权限才可访问该页面。在这种情况下,就需要涉及Session、Cookie的处理了,如果打算使用HttpURLConnection来处理这些细节,当然也是可能实现的,只是处理起来难度就大了。

这个时候,Apache开源组织提供了一个HttpClient项目,可以用于发送HTTP请求,接收HTTP响应(包含HttpGet、HttpPost...等各种发送请求的对象)。

它不会缓存服务器的响应,不能执行HTML页面中嵌入的Javascript代码;也不会对页面内容进行任何解析、处理

因此,下面我就让Spring使用HttpClient为示例演示使用三方库:
1、导包

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.10</version>
</dependency>
Tips:Requires Apache HttpComponents 4.3 or higher, as of Spring 4.0.

2、案例使用
案例内容仅仅只需把上例第一句话换成使用HttpComponentsClientHttpRequestFactory它的实例,其余都不用变化即可成功看到效果。可以看看这个类它具体做了什么

// @since 3.1 3.1后出现的。
public class HttpComponentsClientHttpRequestFactory implements ClientHttpRequestFactory, DisposableBean {

    private HttpClient httpClient;
    @Nullable
    private RequestConfig requestConfig; // 这个配置就是可以配置超时等等乱七八糟client属性的类
    private boolean bufferRequestBody = true;

    //=========下面是构造函数们=========
    public HttpComponentsClientHttpRequestFactory() {
        // HttpClientBuilder.create().useSystemProperties().build();
        // 所有若是这里,配置超时时间可以这么来设置也可:
        // System.setProperty(”sun.net.client.defaultConnectTimeout”, “5000″);
        this.httpClient = HttpClients.createSystem();
    }
    // 当然可以把你配置好了的Client扔进来
    public HttpComponentsClientHttpRequestFactory(HttpClient httpClient) {
        this.httpClient = httpClient;
    }
    ... // 省略设置超时时间。。。等等属性的一些get/set
    // 超时信息啥的都是保存在`RequestConfig`里的


    @Override
    public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
        HttpClient client = getHttpClient(); // 拿到你指定的client)=(或者系统缺省的)
        // switch语句逻辑:HttpMethod == GET --> HttpGet HEAD --> HttpHead ...
        HttpUriRequest httpRequest = createHttpUriRequest(httpMethod, uri);
        postProcessHttpRequest(httpRequest);
        ...
    }
}

实际使用的是HttpClient完成的请求。另外OkHttp3ClientHttpRequestFactory使用的是okhttp3.OkHttpClient发送请求;Netty4ClientHttpRequestFactory使用的是io.netty.channel.EventLoopGroup。此处就不一一例举了

Spring5.0以后,Netty4ClientHttpRequestFactory过期了,建议使用org.springframework.http.client.reactive.ReactorClientHttpConnector代替~

关于HttpURLConnectionHttpClientOkHttpClient的简单比较:
  • HttpURLConnection

        - 优点:JDK内置支持,java的标准类
        - 缺点:API不够友好,什么都没封装,用起来太原始,不方便(这其实有时候也算优点,原始就证明好控~)
  • HttpClient

        - 优点:功能强大,API友好,使用率够高,几乎成为了实际意义上的标准(相当于对`HttpURLConnection`的封装)
        - 缺点:性能稍低(比`HttpURLConnection`低,但4.3后使用连接池进行了改善),API较臃肿,其实Android已经弃用了它~
  • OkHttpClient:新一代的Http访问客户端

        - 优点:一个专注于**性能和易用性**的HTTP客户端(节约宽带,Android推荐使用),它设计的首要目标就是高效。提供了最新的 HTTP 协议版本 HTTP/2 和 SPDY 的支持。如果 HTTP/2 和 SPDY 不可用,OkHttp 会使用连接池来复用连接以提高效率
        - 暂无。
    

在这里插入图片描述
关于Apache HttpClientAndroid5.0之后已经废弃使用它了(API太多,太重),推荐使用更轻量的HttpUrlConnection。(Java开发还是推荐用HttpClient

OkHttp优点较多:支持SPDY,可以合并多个到同一个主机的请求;OkHttp实现的诸多技术如:连接池,gziping,缓存等;OkHttp 处理了很多网络疑难杂症:会从很多常用的连接问题中自动恢复。如果您的服务器配置了多个IP地址,当第一个IP连接失败的时候,OkHttp会自动尝试下一个IP;OkHttp是一个Java的HTTP+SPDY客户端开发包,同时也支持Android。默认情况下,OKHttp会自动处理常见的网络问题,像二次连接、SSL的握手问题。支持文件上传、下载、cookie、session、https证书等几乎所有功能。支持取消某个请求

综上所述,不管是Java还是Android,我推荐的自然都是OkHttp(OkHttp使用Okio进行数据传输。都是Square公司自家的,Square公司还出了一个Retrofit库配合OkHttp战斗力翻倍)~~~

池化技术一般用于长连接,那么像Http这种适合连接池吗?
HttpClient 4.3以后中使用了PoolingHttpClientConnectionManager连接池来管理持有连接,同一条TCP链路上,连接是可以复用的。HttpClient通过连接池的方式进行连接持久化(所以它这个连接池其实是tcp的连接池。它里面有一个很重要的概念:Route的概念,代表一条线路。比如baidu.com是一个route,163.com是一个route...)。
连接池:可能是http请求,也可能是https请求
加入池话技术,就不用每次发起请求都新建一个连接(每次连接握手三次,效率太低)

AbstractClientHttpRequestFactoryWrapper

对其它ClientHttpRequestFactory的一个包装抽象类,它有如下两个子类实现

InterceptingClientHttpRequestFactory(重要)

Interceptor拦截的概念,还是蛮重要的。它持有的ClientHttpRequestInterceptor对于我们若想要拦截发出去的请求非常之重要(比如全链路压测中,可以使用它设置token之类的~)

// @since 3.1
public class InterceptingClientHttpRequestFactory extends AbstractClientHttpRequestFactoryWrapper {
    // 持有所有的请求拦截器
    private final List<ClientHttpRequestInterceptor> interceptors;

    public InterceptingClientHttpRequestFactory(ClientHttpRequestFactory requestFactory, @Nullable List<ClientHttpRequestInterceptor> interceptors) {
        super(requestFactory);
        // 拦截器只允许通过构造函数设置进来,并且并没有提供get方法方法~
        this.interceptors = (interceptors != null ? interceptors : Collections.emptyList());
    }

    // 此处返回的是一个InterceptingClientHttpRequest,显然它肯定是个ClientHttpRequest嘛~
    @Override
    protected ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod, ClientHttpRequestFactory requestFactory) {
        return new InterceptingClientHttpRequest(requestFactory, this.interceptors, uri, httpMethod);
    }

}

InterceptingClientHttpRequestexecute()方法的特点是:若存在拦截器,交给给拦截器去执行发送请求return nextInterceptor.intercept(request, body, this),否则就自己上。


ClientHttpRequestInterceptor

关于请求拦截器,Spring MVC内置了两个最基础的实现
在这里插入图片描述
==BasicAuthorizationInterceptor==:

// @since 4.3.1  但在Spring5.1.1后推荐使用BasicAuthenticationInterceptor
@Deprecated
public class BasicAuthorizationInterceptor implements ClientHttpRequestInterceptor {
    
    private final String username;
    private final String password;
    
    // 注意:username不允许包含:这个字符,但是密码是允许的
    public BasicAuthorizationInterceptor(@Nullable String username, @Nullable String password) {
        Assert.doesNotContain(username, ":", "Username must not contain a colon");
        this.username = (username != null ? username : "");
        this.password = (password != null ? password : "");
    }

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        // 用户名密码连接起来后,用Base64对字节码进行编码~
        String token = Base64Utils.encodeToString((this.username + ":" + this.password).getBytes(StandardCharsets.UTF_8));
    
        // 放进请求头:key为`Authorization`  然后执行请求的发送
        request.getHeaders().add("Authorization", "Basic " + token);
        return execution.execute(request, body);
    }
}

这个拦截器木有对body有任何改动,只是把用户名、密码帮你放进了请求头上。

需要注意的是:若你的header里已经存在了Authorization这个key,这里也不会覆盖的,这会添加哦。但并不建议你有覆盖现象~

==BasicAuthenticationInterceptor==:
它是用来代替上类的。它使用标准的授权头来处理,参考HttpHeaders#setBasicAuth、HttpHeaders#AUTHORIZATION

public class BasicAuthenticationInterceptor implements ClientHttpRequestInterceptor {
    private final String username;
    private final String password;
    // 编码,一般不用指定
    @Nullable
    private final Charset charset;
    ... // 构造函数略

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

        HttpHeaders headers = request.getHeaders();
        // 只有当请求里不包含`Authorization`这个key的时候,此处才会设置授权头哦
        if (!headers.containsKey(HttpHeaders.AUTHORIZATION)) {
            
            // 这个方法是@since 5.1之后才提供的~~~~~
            // 若不包含此key,就设置标准的授权头(根据用户名、密码) 它内部也有这如下三步:
            
            // String credentialsString = username + ":" + password;
            // byte[] encodedBytes = Base64.getEncoder().encode(credentialsString.getBytes(charset));
            // String encodedCredentials = new String(encodedBytes, charset);
            
            // 注意:它内部最终还是调用set(AUTHORIZATION, "Basic " + encodedCredentials);这个方法的
            headers.setBasicAuth(this.username, this.password, this.charset);
        }
        return execution.execute(request, body);
    }
}

说明:这两个请求拦截器虽是Spring提供,但默认都是没有被"装配"的,所亲需要,请手动装配~

BufferingClientHttpRequestFactory

包装其它ClientHttpRequestFactory,使得具有缓存的能力。若开启缓存功能(有开关可控),会使用BufferingClientHttpRequestWrapper包装原来的ClientHttpRequest。这样发送请求后得到的是BufferingClientHttpResponseWrapper响应。


ResponseErrorHandler

用于确定特定响应是否有错误的策略接口。

// @since 3.0
public interface ResponseErrorHandler {

    // response里是否有错
    boolean hasError(ClientHttpResponse response) throws IOException;
    // 只有hasError = true时才会调用此方法
    void handleError(ClientHttpResponse response) throws IOException;
     // @since 5.0
    default void handleError(URI url, HttpMethod method, ClientHttpResponse response) throws IOException {
        handleError(response);
    }
}

继承树如下:
在这里插入图片描述

DefaultResponseErrorHandler

Spring对此策略接口的默认实现,RestTemplate默认使用的错误处理器就是它。

// @since 3.0
public class DefaultResponseErrorHandler implements ResponseErrorHandler {

    // 是否有错误是根据响应码来的,所以请严格遵守响应码的规范啊
    // 简单的说4xx和5xx都会被认为有错,否则是无错的  参考:HttpStatus.Series
    @Override
    public boolean hasError(ClientHttpResponse response) throws IOException {
        int rawStatusCode = response.getRawStatusCode();
        HttpStatus statusCode = HttpStatus.resolve(rawStatusCode);
        return (statusCode != null ? hasError(statusCode) : hasError(rawStatusCode));
    }
    ...
    // 处理错误
    @Override
    public void handleError(ClientHttpResponse response) throws IOException {
        HttpStatus statusCode = HttpStatus.resolve(response.getRawStatusCode());
        if (statusCode == null) {
            throw new UnknownHttpStatusCodeException(response.getRawStatusCode(), response.getStatusText(), response.getHeaders(), getResponseBody(response), getCharset(response));
        }
        handleError(response, statusCode);
    }
    
    // protected方法,子类对它有复写
    protected void handleError(ClientHttpResponse response, HttpStatus statusCode) throws IOException {
        String statusText = response.getStatusText();
        HttpHeaders headers = response.getHeaders();
        byte[] body = getResponseBody(response); // 拿到body,把InputStream转换为字节数组
        Charset charset = getCharset(response); // 注意这里的编码,是从返回的contentType里拿的~~~
        
        // 分别针对于客户端错误、服务端错误 包装为HttpClientErrorException和HttpServerErrorException进行抛出
        // 异常内包含有状态码、状态text、头、body、编码等等信息~~~~
        switch (statusCode.series()) {
            case CLIENT_ERROR:
                throw HttpClientErrorException.create(statusCode, statusText, headers, body, charset);
            case SERVER_ERROR:
                throw HttpServerErrorException.create(statusCode, statusText, headers, body, charset);
            default:
                throw new UnknownHttpStatusCodeException(statusCode.value(), statusText, headers, body, charset);
        }
    }
    ...
}

到这里就可以给大家解释一下,为何经常能看到客户端错误,然后还有状态码+一串信息了,就是因为这两个异常。


HttpClientErrorException:

public class HttpClientErrorException extends HttpStatusCodeException {
    ...
    public static HttpClientErrorException create(
            HttpStatus statusCode, String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) {

        switch (statusCode) {
            case BAD_REQUEST:
                return new HttpClientErrorException.BadRequest(statusText, headers, body, charset);
            case UNAUTHORIZED:
                return new HttpClientErrorException.Unauthorized(statusText, headers, body, charset);
            case FORBIDDEN:
                return new HttpClientErrorException.Forbidden(statusText, headers, body, charset);
            case NOT_FOUND:
                return new HttpClientErrorException.NotFound(statusText, headers, body, charset);
            case METHOD_NOT_ALLOWED:
                return new HttpClientErrorException.MethodNotAllowed(statusText, headers, body, charset);
            case NOT_ACCEPTABLE:
                return new HttpClientErrorException.NotAcceptable(statusText, headers, body, charset);
            case CONFLICT:
                return new HttpClientErrorException.Conflict(statusText, headers, body, charset);
            case GONE:
                return new HttpClientErrorException.Gone(statusText, headers, body, charset);
            case UNSUPPORTED_MEDIA_TYPE:
                return new HttpClientErrorException.UnsupportedMediaType(statusText, headers, body, charset);
            case TOO_MANY_REQUESTS:
                return new HttpClientErrorException.TooManyRequests(statusText, headers, body, charset);
            case UNPROCESSABLE_ENTITY:
                return new HttpClientErrorException.UnprocessableEntity(statusText, headers, body, charset);
            default:
                return new HttpClientErrorException(statusCode, statusText, headers, body, charset);
        }
    }
    ...
}

它针对不同的状态码HttpStatus,创建了不同的类型进行返回,方便使用者控制,这在监控上还是蛮有意义的

BadRequest、Unauthorized、Forbidden...等等都是HttpClientErrorException的子类

HttpServerErrorException代码类似,略~


ExtractingResponseErrorHandler

继承自DefaultResponseErrorHandler。在RESTful大行其道的今天,Spring5.0开始提供了此类。它将http错误响应利用HttpMessageConverter转换为对应的RestClientException

// @since 5.0 它出现得还是很晚的。继承自DefaultResponseErrorHandler 
// 若你的RestTemplate想使用它,请调用RestTemplate#setErrorHandler(ResponseErrorHandler)设置即可
public class ExtractingResponseErrorHandler extends DefaultResponseErrorHandler {
    private List<HttpMessageConverter<?>> messageConverters = Collections.emptyList();
    
    // 对响应码做缓存
    private final Map<HttpStatus, Class<? extends RestClientException>> statusMapping = new LinkedHashMap<>();
    private final Map<HttpStatus.Series, Class<? extends RestClientException>> seriesMapping = new LinkedHashMap<>();

    // 构造函数、set方法给上面两个Map赋值。因为我们可以自己控制哪些状态码应该报错,哪些不应该了~
    // 以及可以自定义:那个状态码抛我们自定义的异常,哪一系列状态码抛我们自定义的异常,这个十分的便于我们做监控
    ... // 省略构造函数和set方法。。。


    // 增加缓存功能~~~  否则在交给父类
    @Override
    protected boolean hasError(HttpStatus statusCode) {
        if (this.statusMapping.containsKey(statusCode)) {
            return this.statusMapping.get(statusCode) != null;
        } else if (this.seriesMapping.containsKey(statusCode.series())) {
            return this.seriesMapping.get(statusCode.series()) != null;
        } else {
            return super.hasError(statusCode);
        }
    }

    // 这个它做的事:extract:提取
    @Override
    public void handleError(ClientHttpResponse response, HttpStatus statusCode) throws IOException {
        if (this.statusMapping.containsKey(statusCode)) {
            extract(this.statusMapping.get(statusCode), response);
        } else if (this.seriesMapping.containsKey(statusCode.series())) {
            extract(this.seriesMapping.get(statusCode.series()), response);
        } else {
            super.handleError(response, statusCode);
        }
    }


    private void extract(@Nullable Class<? extends RestClientException> exceptionClass, ClientHttpResponse response) throws IOException {
        if (exceptionClass == null) {
            return;
        }

        // 这里使用到了ResponseExtractor返回值提取器,从返回值里提取内容(本文是提取异常)
        HttpMessageConverterExtractor<? extends RestClientException> extractor =
                new HttpMessageConverterExtractor<>(exceptionClass, this.messageConverters);
        RestClientException exception = extractor.extractData(response);
        if (exception != null) { // 若提取到了异常信息,抛出即可
            throw exception;
        }
    }
}

若你想定制请求异常的处理逻辑,你也是可以自定义这个接口的实现的,当然还是建议你通过继承DefaultResponseErrorHandler来扩展~


ResponseExtractor

响应提取器:从Response中提取数据。RestTemplate请求完成后,都是通过它来从ClientHttpResponse提取出指定内容(比如请求头、请求Body体等)~
在这里插入图片描述
它的直接实现似乎只有HttpMessageConverterExtractor,当然它也是最为重要的一个实现,和HttpMessageConverter相关。
在解释它之前,先看看这个:MessageBodyClientHttpResponseWrapper,它的特点:它不仅可以通过实际读取输入流来检查响应是否有消息体,还可以检查其长度是否为0(即空)

// @since 4.1.5  它是一个访问权限是default的类,是对其它ClientHttpResponse的一个包装
class MessageBodyClientHttpResponseWrapper implements ClientHttpResponse {
    private final ClientHttpResponse response;
    // java.io.PushbackInputStream
    @Nullable
    private PushbackInputStream pushbackInputStream;
    
    // 判断相应里是否有body体
    // 若响应码是1xx 或者是204;或者getHeaders().getContentLength() == 0 那就返回false  否则返回true
    public boolean hasMessageBody() throws IOException {
        HttpStatus status = HttpStatus.resolve(getRawStatusCode());
        if (status != null && (status.is1xxInformational() || status == HttpStatus.NO_CONTENT || status == HttpStatus.NOT_MODIFIED)) {
            return false;
        }
        if (getHeaders().getContentLength() == 0) {
            return false;
        }
        return true;
    }

    // 上面是完全格局状态码(ContentLength)来判断是否有body体的~~~这里会根据流来判断
    // 如果response.getBody() == null,返回true
    // 若流里有内容,最终就用new PushbackInputStream(body)包装起来~~~
    public boolean hasEmptyMessageBody() throws IOException {
        ...
    }
    
    ...  // 其余接口方法都委托~
    @Override
    public InputStream getBody() throws IOException {
        return (this.pushbackInputStream != null ? this.pushbackInputStream : this.response.getBody());
    }
}

它的作用就是包装后,提供两个方法hasMessageBody、hasEmptyMessageBody方便了对body体内容进行判断

// @since 3.0 泛型T:the data type
public class HttpMessageConverterExtractor<T> implements ResponseExtractor<T> {
    // java.lang.reflect.Type
    private final Type responseType;
    // 这个泛型也是T,表示数据的Class嘛~
    // 该calss有可能就是上面的responseType
    @Nullable
    private final Class<T> responseClass;
    // 重要:用于消息解析的转换器
    private final List<HttpMessageConverter<?>> messageConverters;
    ... // 省略构造函数


    // 从ClientHttpResponse 里提取值
    @Override
    @SuppressWarnings({"unchecked", "rawtypes", "resource"})
    public T extractData(ClientHttpResponse response) throws IOException {
        MessageBodyClientHttpResponseWrapper responseWrapper = new MessageBodyClientHttpResponseWrapper(response);
        // 若没有消息体(状态码不对 或者 消息体为空都被认为是木有)
        if (!responseWrapper.hasMessageBody() || responseWrapper.hasEmptyMessageBody()) {
            return null;
        }
    
        // content-type若响应头header里没有指定,那默认是它MediaType.APPLICATION_OCTET_STREAM
        MediaType contentType = getContentType(responseWrapper);
        
        // 遍历所有的messageConverters,根据contentType 来选则一个消息转换器
        // 最终return messageConverter.read((Class) this.responseClass, responseWrapper)
        ...
    }
}

它的处理逻辑理解起来非常简单:利用contentType找到一个消息转换器,最终HttpMessageConverter.read()把消息读出来转换成Java对象。

它还有两个内部类的实现如下(都是RestTemplate的私有内部类):

RestTemplate:

    // 提取为`ResponseEntity`  最终委托给HttpMessageConverterExtractor完成的
    private class ResponseEntityResponseExtractor<T> implements ResponseExtractor<ResponseEntity<T>> {

        @Nullable
        private final HttpMessageConverterExtractor<T> delegate;

        public ResponseEntityResponseExtractor(@Nullable Type responseType) {
            // 显然:只有请求的返回值不为null 才有意义~
            if (responseType != null && Void.class != responseType) {
                this.delegate = new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger);
            } else {
                this.delegate = null;
            }
        }

        // 数据提取。都是交给`delegate.extractData(response)`做了,然后new一个ResponseEntity出来包装进去
        // 若木有返回值(delegate=null),那就是一个`ResponseEntity`实例,body为null
        @Override
        public ResponseEntity<T> extractData(ClientHttpResponse response) throws IOException {
            if (this.delegate != null) {
                T body = this.delegate.extractData(response);
                return ResponseEntity.status(response.getRawStatusCode()).headers(response.getHeaders()).body(body);
            }
            else {
                return ResponseEntity.status(response.getRawStatusCode()).headers(response.getHeaders()).build();
            }
        }
    }

    // 提取请求头
    private static class HeadersExtractor implements ResponseExtractor<HttpHeaders> {
        @Override
        public HttpHeaders extractData(ClientHttpResponse response) {
            return response.getHeaders();
        }
    }

UriTemplateHandler

这个组件它用于定义用变量扩展uri模板的方法

// @since 4.2 出现较晚  
// @see RestTemplate#setUriTemplateHandler(UriTemplateHandler)
public interface UriTemplateHandler {
    URI expand(String uriTemplate, Map<String, ?> uriVariables);
    URI expand(String uriTemplate, Object... uriVariables);
}

关于URI的处理,最终都是委托给UriComponentsBuilder来完成。若对这块还存在一定疑问的,强烈强烈强烈 参考这里

推荐阅读

RestTemplate的使用和原理你都烂熟于胸了吗?【享学Spring MVC】

总结

本文介绍的组件是去理解RestTemplate必备的组件们,属于开山篇。因为RestTemplate使用频繁,并且经常需要调优,因此我寄希望大家也能对它做较为深入的了解,这也是我写本系列的目的,共勉。

== 若对Spring、SpringBoot、MyBatis等源码分析感兴趣,可加我wx:fsx641385712,手动邀请你入群一起飞 ==
== 若对Spring、SpringBoot、MyBatis等源码分析感兴趣,可加我wx:fsx641385712,手动邀请你入群一起飞 ==

spring boot / cloud (八) 使用RestTemplate来构建远程调用服务

spring boot / cloud (八) 使用RestTemplate来构建远程调用服务

spring boot / cloud (八) 使用RestTemplate来构建远程调用服务

##前言

上周因家里突发急事,请假一周,故博客没有正常更新

###RestTemplate介绍:

RestTemplate是spring框架中自带的rest客户端工具类,具有丰富的API,并且在spring cloud中,标记@LoadBalanced注解,可以实现客户端负载均衡的rest调用.

##思路

RestTemplate虽然提供了丰富的API,但是这些API过于底层,如果不稍加控制,让开发人员随意使用,那后续的代码也将会变的五花八门,难以维护.

同时,当系统规模大了之后,将会有更多的服务,并且服务之间的调用关系也将更加复杂,如果不进行管控治理的话,同样,项目同期也将越来越不可控,

最后,服务间调用也需要有明确的权限认证机制,最好是能通过配置的方式来明确,哪些服务可以调用那些服务.从而来把控项目的复杂度.

本文将从以下几点来提供一个解决问题的思路:

  • 通过spring boot的@ConfigurationProperties机制来定义远程服务的元数据,从而实现权限认证的配置化

  • 使用HandlerInterceptor来进行拦截,实现权限的验证

  • 定义通用Rms类,来规范RestTemplate的使用

##实现

###1.实现权限配置

####1.定义Application元数据

public class ApplicationMeta implements Serializable {
  //ID
  private static final long serialVersionUID = 1L;
  //服务ID
  private String serviceId;
  //私钥
  private String secret;
  //权限
  private String purview;
  //所有服务的调用权限(优先判定)
  private Boolean all = false;
  //禁止服务调用
  private Boolean disabled = false;
  //描述
  private String description;
}

####2.定义Service元数据

public class ServiceMeta implements Serializable {
  //ID
  private static final long serialVersionUID = 1L;
  //应用名称
  private String owner;
  //地址
  private String uri;
  //服务方法
  private String method;
  //是否HTTPS
  private Boolean isHttps = false;
  //描述
  private String description;

####3.定义RmsProperties类

@Component
@ConfigurationProperties(prefix = "com.egridcloud.rms.properties")
public class RmsProperties implements Serializable {
  //ID
  private static final long serialVersionUID = 1L;
  //应用清单(应用名称 : 应用地址)
  private Map<String, ApplicationMeta> application;
  //服务路径(服务编号 : 服务元数据)
  private Map<String, ServiceMeta> service;

####4.在properties文件中进行配置

#定义了一个叫udf-demo(跟spring boot的应用ID一致),设置了私钥,以及可调用的服务
com.egridcloud.rms.properties.application.udf-demo.serviceId=127.0.0.1:8080
com.egridcloud.rms.properties.application.udf-demo.secret=ADSFHKW349546RFSGF
com.egridcloud.rms.properties.application.udf-demo.purview=FILE_3
com.egridcloud.rms.properties.application.udf-demo.all=false
com.egridcloud.rms.properties.application.udf-demo.disabled=false
com.egridcloud.rms.properties.application.udf-demo.description=sample application

#定义了一个叫FILE_3的服务,后续使用这个服务编号进行调用即可
com.egridcloud.rms.properties.service.FILE_3.owner=udf-demo
com.egridcloud.rms.properties.service.FILE_3.uri=/service/file/download
com.egridcloud.rms.properties.service.FILE_3.method=POST
com.egridcloud.rms.properties.service.FILE_3.isHttps=false
com.egridcloud.rms.properties.service.FILE_3.description=文件下载

###2.实现权限校验

####1.定义RmsAuthHandlerInterceptor拦截器

public class RmsAuthHandlerInterceptor implements HandlerInterceptor {
  //环境标识
  private static final String DEV_PROFILES = "dev";
  //配置
  @Autowired
  private RmsProperties rmsProperties;
  //环境变量
  @Autowired
  private Environment env;
  
  @Override
  public boolean preHandle(HttpServletRequest request, 
      HttpServletResponse response,
      Object handler) {
      
      .......
      
  }
}

####2.完善preHandle方法-取出认证信息

    String rmsApplicationName = request.getHeader(Constant.HEADER_RMS_APPLICATION_NAME_CODE);
    if (StringUtils.isBlank(rmsApplicationName)) {
      rmsApplicationName = request.getParameter(Constant.HEADER_RMS_APPLICATION_NAME_CODE);
    }
    //获取认证信息(sign)
    String rmsSign = request.getHeader(Constant.HEADER_RMS_SIGN_CODE);
    if (StringUtils.isBlank(rmsSign)) {
      rmsSign = request.getParameter(Constant.HEADER_RMS_SIGN_CODE);
    }
    //获取认证信息(服务代码)
    String rmsServiceCode = request.getHeader(Constant.HEADER_SERVICE_CODE_CODE);
    if (StringUtils.isBlank(rmsServiceCode)) {
      rmsServiceCode = request.getParameter(Constant.HEADER_SERVICE_CODE_CODE);
    }
    //获取请求地址
    String url = request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE).toString();
    //获取请求方法
    String method = request.getMethod();

####3.完善preHandle方法-校验

    //判断环境(开发环境无需校验)
    if (!DEV_PROFILES.equals(env.getProperty("spring.profiles.active"))) {
      //判断是否缺少认证信息
      if (StringUtils.isBlank(rmsApplicationName) || StringUtils.isBlank(rmsSign)
          || StringUtils.isBlank(rmsServiceCode)) {
        throw new AuthException("missing required authentication parameters (rmsApplicationName , rmsSign)");
      }
      //判断systemTag是否有效
      if (!this.rmsProperties.getApplication().containsKey(rmsApplicationName)) {
        throw new AuthException("unrecognized systemTag:" + rmsApplicationName);
      }
      //获得应用元数据
      ApplicationMeta applicationMeta = rmsProperties.getApplication().get(rmsApplicationName);
      //获得secret
      String secret = applicationMeta.getSecret();
      //计算sign
      String sign = Constant.sign(rmsApplicationName, secret);
      //比较sign
      if (!rmsSign.equals(sign)) {
        throw new AuthException("sign Validation failed");
      }
      //判断是否有调用所有服务的权限
      if (!applicationMeta.getAll()) {
        //判断是否禁止调用所有服务权限
        if (applicationMeta.getDisabled()) {
          throw new PermissionException(rmsApplicationName + " is disabled");
        }
        //判断是否有调用该服务的权限
        if (applicationMeta.getPurview().indexOf(rmsServiceCode) == -1) {
          throw new PermissionException("no access to this servoceCode : " + rmsServiceCode);
        }
        //判断服务元数据是否存在
        if (!rmsProperties.getService().containsKey(rmsServiceCode)) {
          throw new PermissionException("service code not exist");
        }
        //获得服务元数据
        ServiceMeta serviceMeta = rmsProperties.getService().get(rmsServiceCode);
        //比较url和method的有效性
        if (!serviceMeta.getUri().equals(url) || !serviceMeta.getMethod().equals(method)) {
          throw new PermissionException("url and method verification error");
        }
      }
    }

####4.定义RmsConfig类

@Configuration
@ConfigurationProperties(prefix = "com.egridcloud.rms.config")
@Validated
public class RmsConfig {

  //RMS扫描路径
  @NotNull
  private String rmsPathPatterns;

  .........
  
}

####5.定义RmsConfig类-注册bean

  @Bean
  @LoadBalanced
  RestTemplate restTemplate(ClientHttpRequestFactory requestFactory) {
    return new RestTemplate(requestFactory);
  }
  
  @Bean
  public RmsAuthHandlerInterceptor rmsAuthHandlerInterceptor() {
    return new RmsAuthHandlerInterceptor();
  }
  
  @Bean
  public WebMvcConfigurer rmsAuthConfigurer() { //NOSONAR
    return new WebMvcConfigurerAdapter() {
      @Override
      public void addInterceptors(InterceptorRegistry registry) {
        String[] rmsPathPatternsArray = rmsPathPatterns.split(",");
        registry.addInterceptor(rmsAuthHandlerInterceptor()).addPathPatterns(rmsPathPatternsArray);
        super.addInterceptors(registry);
      }
    };
  }

####6.在properties文件中进行配置

#拦截路径
com.egridcloud.rms.config.rmsPathPatterns=/service/**

###3.实现Rms类

####1.定义rms类

@Component
public class Rms {
  //应用名称
  @Value("${spring.application.name}")
  private String springApplicationName;
  //restTemplate
  @Autowired
  private RestTemplate restTemplate;
  //配置
  @Autowired
  private RmsProperties rmsProperties;

####2.定义rms类-call方法

  public <I, O> ResponseEntity<O> call(String serviceCode, I input, String uriParam,
      ParameterizedTypeReference<O> responseType, Map<String, ?> uriVariables) {
    //客户端权限验证
    verification(serviceCode);
    //构建请求路径
    String path = getRmsUrl(serviceCode);
    //获得请求方法
    String method = getRmsMethod(serviceCode);
    //拼装路径参数
    if (StringUtils.isNotBlank(uriParam)) {
      path += uriParam;
    }
    //构建请求头
    HttpHeaders httpHeaders = buildSystemTagHeaders(serviceCode);
    //构建请求消息体
    HttpEntity<I> requestEntity = new HttpEntity<>(input, httpHeaders);
    //请求并且返回
    LOGGER.info("rms url : {} , method : {} ", path, method);
    return restTemplate.exchange(path, HttpMethod.resolve(method), requestEntity, responseType,
        uriVariables != null ? uriVariables : new HashMap<String, String>());
  }

####3.定义rms类-其他方法

  //构建请求头
  private HttpHeaders buildSystemTagHeaders(String serviceCode) {
    String secret = rmsProperties.getApplication().get(springApplicationName).getSecret();
    HttpHeaders headers = new HttpHeaders();
    headers.add(Constant.HEADER_RMS_APPLICATION_NAME_CODE, springApplicationName);
    headers.add(Constant.HEADER_RMS_SIGN_CODE, Constant.sign(springApplicationName, secret));
    headers.add(Constant.HEADER_SERVICE_CODE_CODE, serviceCode);
    return headers;
  }
  //客户端验证
  private void verification(String serviceCode) {
    ApplicationMeta applicationMeta = rmsProperties.getApplication().get(springApplicationName);
    if (!applicationMeta.getAll()) {
      if (applicationMeta.getDisabled()) {
        throw new PermissionException(springApplicationName + " is disabled");
      }
      if (applicationMeta.getPurview().indexOf(serviceCode) == -1) {
        throw new PermissionException("no access to this servoceCode : " + serviceCode);
      }
    }
  }
  //获得请求方法
  private String getRmsMethod(String serviceCode) {
    return rmsProperties.getService().get(serviceCode).getMethod();
  }
  //构造url
  private String getRmsUrl(String serviceCode) {
    //获取服务元数据
    ServiceMeta serviceMeta = rmsProperties.getService().get(serviceCode);
    //构建请求路径
    StringBuilder url =
        new StringBuilder(serviceMeta.getIsHttps() ? Constant.HTTPS : Constant.HTTP);
    url.append(rmsProperties.getApplication().get(serviceMeta.getOwner()).getServiceId());
    url.append(serviceMeta.getUri());
    return url.toString();
  }
  //计算sign
  public static String sign(String rmsApplicationName, String secret) {
    final String split = "_";
    StringBuilder sb = new StringBuilder();
    sb.append(rmsApplicationName).append(split).append(secret).append(split)
        .append(new SimpleDateFormat(DATA_FORMAT).format(new Date()));
    return DigestUtils.md5Hex(sb.toString());
  }

###4.客户端调用

//获得文件信息
ResponseEntity<RestResponse<FileInfo>> fileInfo = rms.call("FILE_4", fileParam, null,
              new ParameterizedTypeReference<RestResponse<FileInfo>>() {
              }, null);

##结束

这样,规范了远程服务的调用,只关心接口编号和接口的入参和出参,能够增加沟通效率,并且也有了轻量级的服务治理机制,服务间的调用更可控,到最后,配置文件一拉出来一清二楚.


想获得最快更新,请关注公众号

想获得最快更新,请关注公众号

Spring Boot 入门 - 进阶篇(4)- REST访问(RestTemplate)

Spring Boot 入门 - 进阶篇(4)- REST访问(RestTemplate)

经常需要发送一个GET/POST请求到其他系统(REST API),通过JDK自带的HttpURLConnection、Apache HttpClient、Netty 4、OkHTTP 2/3都可以实现。 

HttpClient的使用:http://rensanning.iteye.com/blog/1550436 

Spring的RestTemplate封装了这些库的实现,使用起来更简洁。 

RestTemplate包含以下几个部分: 

  • HttpMessageConverter 对象转换器
  • ClientHttpRequestFactory 默认是JDK的HttpURLConnection
  • ResponseErrorHandler 异常处理
  • ClientHttpRequestInterceptor 请求拦截器



Java代码 

 收藏代码

  1. @Service  
  2. public class AccountService {  
  3.   
  4.     @Autowired  
  5.     private RestTemplate restTemplate;  
  6.   
  7.     // ...  
  8.   
  9. }  



(1)发送GET请求(getForObject()、getForEntity()、exchange()) 

Java代码 

 收藏代码

  1. // 1-getForObject()  
  2. User user1 = this.restTemplate.getForObject(uri, User.class);  
  3.   
  4. // 2-getForEntity()  
  5. ResponseEntity<User> responseEntity1 = this.restTemplate.getForEntity(uri, User.class);  
  6. HttpStatus statusCode = responseEntity1.getStatusCode();  
  7. HttpHeaders header = responseEntity1.getHeaders();  
  8. User user2 = responseEntity1.getBody();  
  9.   
  10. // 3-exchange()  
  11. RequestEntity requestEntity = RequestEntity.get(new URI(uri)).build();  
  12. ResponseEntity<User> responseEntity2 = this.restTemplate.exchange(requestEntity, User.class);  
  13. User user3 = responseEntity2.getBody();  



(2)发送POST请求(postForObject()、postForEntity()、exchange()) 

Java代码 

 收藏代码

  1. // 1-postForObject()  
  2. User user1 = this.restTemplate.postForObject(uri, user, User.class);  
  3.   
  4. // 2-postForEntity()  
  5. ResponseEntity<User> responseEntity1 = this.restTemplate.postForEntity(uri, user, User.class);  
  6.   
  7. // 3-exchange()  
  8. RequestEntity<User> requestEntity = RequestEntity.post(new URI(uri)).body(user);  
  9. ResponseEntity<User> responseEntity2 = this.restTemplate.exchange(requestEntity, User.class);  



(3)设置HTTP Header信息 

Java代码 

 收藏代码

  1. // 1-Content-Type  
  2. RequestEntity<User> requestEntity = RequestEntity  
  3.         .post(new URI(uri))  
  4.         .contentType(MediaType.APPLICATION_JSON)  
  5.         .body(user);  
  6.   
  7. // 2-Accept  
  8. RequestEntity<User> requestEntity = RequestEntity  
  9.         .post(new URI(uri))  
  10.         .accept(MediaType.APPLICATION_JSON)  
  11.         .body(user);  
  12.   
  13. // 3-Other  
  14. RequestEntity<User> requestEntity = RequestEntity  
  15.         .post(new URI(uri))  
  16.         .header("Authorization", "Basic " + base64Credentials)  
  17.         .body(user);  



(4)异常处理 

捕获HttpServerErrorException 

Java代码 

 收藏代码

  1. int retryCount = 0;  
  2. while (true) {  
  3.     try {  
  4.         responseEntity = restTemplate.exchange(requestEntity, String.class);  
  5.         break;  
  6.     } catch (HttpServerErrorException e) {  
  7.         if (retryCount == 3) {  
  8.             throw e;  
  9.         }  
  10.         retryCount++;  
  11.     }  
  12. }  



自定义异常处理 

Java代码 

 收藏代码

  1. public class CustomErrorHandler extends DefaultResponseErrorHandler {  
  2.   
  3.     @Override  
  4.     public void handleError(ClientHttpResponse response) throws IOException {  
  5.   
  6.     }  
  7.   
  8. }  


Java代码 

 收藏代码

  1. @Configuration  
  2. public class RestClientConfig {  
  3.   
  4.     @Bean  
  5.     public RestTemplate restTemplate() {  
  6.         RestTemplate restTemplate = new RestTemplate();  
  7.         restTemplate.setErrorHandler(new CustomErrorHandler());  
  8.         return restTemplate;  
  9.     }  
  10.   
  11. }  



(5)设置超时时间 

Java代码 

 收藏代码

  1. @Configuration  
  2. public class RestClientConfig {  
  3.   
  4.     @Bean  
  5.     public RestTemplate restTemplate() {  
  6.         return new RestTemplate(clientHttpRequestFactory());  
  7.     }  
  8.   
  9.     private ClientHttpRequestFactory clientHttpRequestFactory() {  
  10.         HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();  
  11.         factory.setReadTimeout(2000);  
  12.         factory.setConnectTimeout(2000);  
  13.         return factory;  
  14.     }  
  15. }  



(6)设置连接池 

Java代码 

 收藏代码

  1. @Configuration  
  2. public class RestClientConfig {  
  3.   
  4.   @Bean  
  5.   public ClientHttpRequestFactory httpRequestFactory() {  
  6.     return new HttpComponentsClientHttpRequestFactory(httpClient());  
  7.   }  
  8.   
  9.   @Bean  
  10.   public RestTemplate restTemplate() {  
  11.     return new RestTemplate(httpRequestFactory());  
  12.   }  
  13.   
  14.   @Bean  
  15.   public HttpClient httpClient() {  
  16.     Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory> create()  
  17.         .register("http", PlainConnectionSocketFactory.getSocketFactory())  
  18.         .register("https", SSLConnectionSocketFactory.getSocketFactory())  
  19.         .build();  
  20.     PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(registry);  
  21.     connectionManager.setMaxTotal(5);  
  22.     connectionManager.setDefaultMaxPerRoute(5);  
  23.   
  24.     RequestConfig requestConfig = RequestConfig.custom()  
  25.         .setSocketTimeout(8000)  
  26.         .setConnectTimeout(8000)  
  27.         .setConnectionRequestTimeout(8000)  
  28.         .build();  
  29.   
  30.     return HttpClientBuilder.create()  
  31.         .setDefaultRequestConfig(requestConfig)  
  32.         .setConnectionManager(connectionManager)  
  33.         .build();  
  34.   }  
  35.   
  36. }  



(7)发送文件 

Java代码 

 收藏代码

  1. MultiValueMap<String, Object> multiPartBody = new LinkedMultiValueMap<>();  
  2. multiPartBody.add("file", new ClassPathResource("/tmp/user.txt"));  
  3. RequestEntity<MultiValueMap<String, Object>> requestEntity = RequestEntity  
  4.         .post(uri)  
  5.         .contentType(MediaType.MULTIPART_FORM_DATA)  
  6.         .body(multiPartBody);  



(8)下载文件 

Java代码 

 收藏代码

  1. // 小文件  
  2. RequestEntity requestEntity = RequestEntity.get(uri).build();  
  3. ResponseEntity<byte[]> responseEntity = restTemplate.exchange(requestEntity, byte[].class);  
  4. byte[] downloadContent = responseEntity.getBody();  
  5.   
  6. // 大文件  
  7. ResponseExtractor<ResponseEntity<File>> responseExtractor = new ResponseExtractor<ResponseEntity<File>>() {  
  8.     @Override  
  9.     public ResponseEntity<File> extractData(ClientHttpResponse response) throws IOException {  
  10.         File rcvFile = File.createTempFile("rcvFile", "zip");  
  11.         FileCopyUtils.copy(response.getBody(), new FileOutputStream(rcvFile));  
  12.         return ResponseEntity.status(response.getStatusCode()).headers(response.getHeaders()).body(rcvFile);  
  13.     }  
  14. };  
  15. File getFile = this.restTemplate.execute(targetUri, HttpMethod.GET, null, responseExtractor);  



(9)Spring Boot的RestTemplateBuilder 

全局设置 
Java代码 

 收藏代码

  1. @Component  
  2. public class CustomRestTemplateCustomizer implements RestTemplateCustomizer {  
  3.     @Override  
  4.     public void customize(RestTemplate restTemplate) {  
  5.         new RestTemplateBuilder()  
  6.                 .detectRequestFactory(false)  
  7.                 .basicAuthorization("username", "password")  
  8.                 .uriTemplateHandler(new OkHttp3ClientHttpRequestFactory())  
  9.                 .errorHandler(new CustomResponseErrorHandler())  
  10.                 .configure(restTemplate);  
  11.     }  
  12. }  



单独设置 
Java代码 

 收藏代码

  1. @Service  
  2. public class MyRestClientService {  
  3.   
  4.     private RestTemplate restTemplate;  
  5.   
  6.     public MyRestClientService(RestTemplateBuilder restTemplateBuilder) {  
  7.         this.restTemplate = restTemplateBuilder  
  8.             .basicAuthorization("username", "password")  
  9.             .setConnectTimeout(3000)  
  10.             .setReadTimeout(5000)  
  11.             .rootUri("http://api.example.com/")  
  12.             .errorHandler(new CustomResponseErrorHandler())  
  13.             .additionalMessageConverters(new CustomHttpMessageConverter())  
  14.             .uriTemplateHandler(new OkHttp3ClientHttpRequestFactory())  
  15.             .build();  
  16.     }  
  17.   
  18.     public String site() {  
  19.         return this.restTemplate.getForObject("http://rensanning.iteye.com/", String.class);  
  20.     }  
  21.   
  22. }  



参考: 
http://qiita.com/kazuki43zoo/items/7cf3c8ca4f6f2283cefb 
http://terasolunaorg.github.io/guideline/5.1.0.RELEASE/ja/ArchitectureInDetail/RestClient.html

  • 查看图片附件

程序猿必关注这个技术,风头正劲,仅4个月薪酬35K+

大数据技术与运用的成熟,应用集中于互联网、金融、医疗、新能源、通信和房地产等行业。整理平均薪资情况和大数据学习大纲供查看

关于Spring MVC中使用RestTemplate访问REST服务详解spring mvc restful的介绍已经告一段落,感谢您的耐心阅读,如果想了解更多关于java – SpringBoot @WebMvcTest,自动装配RestTemplateBuilder、RestTemplate相关组件:ClientHttpRequestInterceptor【享学Spring MVC】、spring boot / cloud (八) 使用RestTemplate来构建远程调用服务、Spring Boot 入门 - 进阶篇(4)- REST访问(RestTemplate)的相关信息,请在本站寻找。

本文标签: