天天看点

使用RestTemplate方式发送HTTP请求详解一、RestTemplate方式发送HTTP请求代码示例二、RestTemplate详解三、RestTemplate扩展四、SpringBoot中使用RestTemplate

目录

一、RestTemplate方式发送HTTP请求代码示例

(1)发送Get请求

(2)发送Post请求

二、RestTemplate详解

1、RestTemplate简述

2、RestTemplate解析

HttpMessageConverter

创建RestTemplate

RestTemplate构造方法

3、RestTemplate API使用

GET方法

POST方法

exchange方法

三、RestTemplate扩展

1、设置拦截器(ClientHttpRequestInterceptor)

2、RestTemplate配置

1、处理请求头和响应头

2、ClientHttpRequestFactory

3、自定义messageConverter

使用fastjson做json转换

四、SpringBoot中使用RestTemplate

一、RestTemplate方式发送HTTP请求代码示例

RestTemplate是Spring提供的用于访问Rest服务的客户端,RestTemplate提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率。

使用apache的HttpClient开发,代码复杂,还得操心资源回收等。代码很复杂,冗余代码多,稍微截个图,这是一个封装好的get请求工具:

public static String get(String url, String paramsStr) {
        //创建一个默认的HttpClients对象
        CloseableHttpClient createDefault = HttpClients.createDefault();
        //创建一个get请求并发送参数
        HttpGet httpGet = new HttpGet(url + paramsStr);
        //设置http头部信息
        httpGet.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1;"
                + " Win64; x64; rv:50.0) Gecko/20100101 Firefox/50.0");
        httpGet.setHeader("Accept", "application/json");
        httpGet.setHeader("Accept-Encoding", "gzip, deflate");
        httpGet.setHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
        //RequestConfig.Builder 配置器 ,我们可以通过custom获取一个新的Builder对象
        RequestConfig config = RequestConfig.custom()
                //设置链接超时的时间1秒钟
                .setConnectTimeout(90000)
                //设置读取超时1秒钟
                .setSocketTimeout(90000)
                //RequestConfig静态方法  setProxy  设置代理
                .build();
        //设置头部信息
        httpGet.setConfig(config);
        //实例话对象赋值
        CloseableHttpResponse execute = null;
        String jsonStr = "";
        try {
            //执行HttpClient
            execute = createDefault.execute(httpGet);
            //转化json格式 并防止乱码
            jsonStr = EntityUtils.toString(execute.getEntity(), "UTF-8");
        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                //关闭HttpClient
                createDefault.close();
                //关闭执行
                execute.close();
                //销毁GET请求
                httpGet.abort();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return jsonStr;
    }
           

下边贴下通过RestTemplate发送Http请求的代码:

(1)发送Get请求

代码示例:

/**
     * RestTemplate 发送  HTTP GET请求 --- 测试
     * @throws UnsupportedEncodingException
     */
    @Test
    public void doHttpGetTest() throws UnsupportedEncodingException {
        // -------------------------------> 获取Rest客户端实例
        RestTemplate restTemplate = new RestTemplate();

        // -------------------------------> 解决(响应数据可能)中文乱码 的问题
        List<HttpMessageConverter<?>> converterList = restTemplate.getMessageConverters();
        converterList.remove(1); // 移除原来的转换器
        // 设置字符编码为utf-8
        HttpMessageConverter<?> converter = new StringHttpMessageConverter(StandardCharsets.UTF_8);
        converterList.add(1, converter); // 添加新的转换器(注:convert顺序错误会导致失败)
        restTemplate.setMessageConverters(converterList);

        // -------------------------------> (选择性设置)请求头信息
        // HttpHeaders实现了MultiValueMap接口
        HttpHeaders httpHeaders = new HttpHeaders();
        // 给请求header中添加一些数据
        httpHeaders.add("JustryDeng", "这是一个大帅哥!");

        // -------------------------------> 注:GET请求 创建HttpEntity时,请求体传入null即可
        // 请求体的类型任选即可;只要保证 请求体 的类型与HttpEntity类的泛型保持一致即可
        String httpBody = null;
        HttpEntity<String> httpEntity = new HttpEntity<String>(httpBody, httpHeaders);

        // -------------------------------> URI
        StringBuffer paramsURL = new StringBuffer("http://127.0.0.1:8080/http/doHttpTest");
        // 字符数据最好encoding一下;这样一来,某些特殊字符才能传过去(如:flag的参数值就是“&”,不encoding的话,传不过去)
        paramsURL.append("?flag=" + URLEncoder.encode("&&", "utf-8"));
        URI uri = URI.create(paramsURL.toString());

        //  -------------------------------> 执行请求并返回结果
        // 此处的泛型  对应 响应体数据   类型;即:这里指定响应体的数据装配为String
        ResponseEntity<String> response =
                restTemplate.exchange(uri, HttpMethod.GET, httpEntity, String.class);

        // -------------------------------> 响应信息
        //响应码,如:401、302、404、500、200等
        System.err.println(response.getStatusCodeValue());
        // 响应头
        System.err.println(JSON.toJSON(response.getHeaders()));
        // 响应体
        if(response.hasBody()) {
            System.err.println(response.getBody());
        }
    }
           

(2)发送Post请求

代码示例:

/**
     * RestTemplate 发送  HTTP POST请求 --- 测试
     * @throws UnsupportedEncodingException
     */
    @Test
    public void doHttpPostTest() throws UnsupportedEncodingException {
        // -------------------------------> 获取Rest客户端实例
        RestTemplate restTemplate = new RestTemplate();

        // -------------------------------> (选择性设置)请求头信息
        // HttpHeaders实现了MultiValueMap接口
        HttpHeaders httpHeaders = new HttpHeaders();
        // 设置contentType
        httpHeaders.setContentType(MediaType.APPLICATION_JSON_UTF8);
        // 给请求header中添加一些数据
        httpHeaders.add("JustryDeng", "这是一个大帅哥!");

        // ------------------------------->将请求头、请求体数据,放入HttpEntity中
        // 请求体的类型任选即可;只要保证 请求体 的类型与HttpEntity类的泛型保持一致即可
        // 这里手写了一个json串作为请求体 数据 (实际开发时,可使用fastjson、gson等工具将数据转化为json串)
        // String httpBody = "{\"motto\":\"唉呀妈呀!脑瓜疼!\"}";
        Map<String,Object> paramMap = new HashMap<>();
        paramMap.put("name","是一个中文名");
        paramMap.put("englishName","merry");
        HttpEntity<Map<String,Object>> httpEntity = new HttpEntity<>(paramMap, httpHeaders);

        // -------------------------------> URI
        StringBuffer paramsURL = new StringBuffer("http://127.0.0.1:8080/http/doHttpTest");
        // 字符数据最好encoding一下;这样一来,某些特殊字符才能传过去(如:flag的参数值就是“&”,不encoding的话,传不过去)
        paramsURL.append("?flag=" + URLEncoder.encode("&&", "utf-8"));
        URI uri = URI.create(paramsURL.toString());

        //  -------------------------------> 执行请求并返回结果
        // 此处的泛型  对应 响应体数据   类型;即:这里指定响应体的数据装配为String
        // 访问方式一:
//        ResponseEntity<String> response =
//                restTemplate.exchange(uri, HttpMethod.POST, httpEntity, String.class);
        // 访问方式二:
        ResponseEntity<String> response = restTemplate.postForEntity(uri,httpEntity,String.class);

        // -------------------------------> 响应信息
        //响应码,如:401、302、404、500、200等
        System.err.println(response.getStatusCodeValue());
        // 响应头
        System.err.println(JSON.toJSON(response.getHeaders()));
        // 响应体
        if(response.hasBody()) {
            System.err.println(response.getBody());
        }

    }
           

对应的响应端代码:

@RequestMapping("/doHttpTest")
    public String doHttpGetTest(HttpServletRequest request, @RequestBody Map<String, Object> paramMap) throws IOException {
        System.out.println(request.getCharacterEncoding());
        // 不是请求体中的中文数据都需要转码
        String JustryDeng = new String(request.getHeader("JustryDeng").getBytes("ISO-8859-1"), "utf-8");
        System.out.println("获取Header传参:" + JustryDeng);
        // 获取URL上的参数
        String flag = request.getParameter("flag");
        System.out.println("获取URL传参:" + flag);
        // 接收对应的Body参数
        Map<String, Object> map = paramMap;
        return "访问成功!!!";
    }
           

注:HttpServletRequest 请求中的 body 内容仅能调用 request.getInputStream(), request.getReader()和request.getParameter("key") 方法读取一次,重复读取会报 java.io.IOException: Stream closed 异常。

request.getInputStream()只能读取一次,因此如果不对HttpServletRequest进行封装,在代码中直接通过流读取body中的内容就会抛出java.io.IOException: Stream closed 异常。

HttpEntity是请求体,可放置跟请求接口对应类型的数据。在示例中添加了许多格外的操作和代码注释,所以看起来比较臃肿,使用RestTemplate简洁的代码其实是这样的:

/**
     * RestTemplate 发送  HTTP POST请求 --- 测试
     * @throws UnsupportedEncodingException
     */
    @Test
    public void doHttpPostTest() throws UnsupportedEncodingException {
        // 1-------------------------------> 获取Rest客户端实例
        RestTemplate restTemplate = new RestTemplate();
        // 2-------------------------------> (选择性设置)请求头信息
        // HttpHeaders实现了MultiValueMap接口
        HttpHeaders httpHeaders = new HttpHeaders();
        // 3-------------------------------> 将请求头、请求体数据,放入HttpEntity中
        Map<String,Object> paramMap = new HashMap<>();
        paramMap.put("name","是一个中文名");
        HttpEntity<Map<String,Object>> httpEntity = new HttpEntity<>(paramMap, httpHeaders);
        // 4-------------------------------> 执行请求并返回结果
        ResponseEntity<String> response = restTemplate.postForEntity(
                "http://127.0.0.1:8080/http/doHttpTest",httpEntity,String.class);
        // 5-------------------------------> 响应信息
        if(response.hasBody()) {
            System.err.println(response.getBody());
        }
    }
           

当然你还可以去除注释和进行简单封装,发现它其实就几行代码,对于Get请求来说,就更加简单了,这个没什么好写的。

二、RestTemplate详解

1、RestTemplate简述

RestTemplate

是Spring的通过客户端访问RESTful服务端的核心类,和

JdbcTemplate、JmsTemplate

概念相似,都是Spring提供的模板类。

RestTemplate

的行为可以通过callback回调方法和配置

HttpMessageConverter

来定制,用来把对象封装到HTTP请求体,将响应信息放到一个对象中。

RestTemplate能大幅简化提交表单数据的难度,并且附带了自动转换JSON数据的功能,但只有理解了HttpEntity的组成结构(header与body),且理解了与uriVariables之间的差异,才能真正掌握其用法。这一点在Post请求更加突出。

2、RestTemplate解析

HttpMessageConverter

RestTemplate默认使用HttpMessageConverter实例将HTTP消息转换成POJO或者从POJO转换成HTTP消息。默认情况下会注册主mime类型的转换器,但也可以通过setMessageConverters注册其他的转换器。其实这点在使用的时候是察觉不到的,很多方法有一个responseType 参数,它让你传入一个响应体所映射成的对象,然后底层用HttpMessageConverter将其做映射

HttpMessageConverterExtractor<T> responseExtractor =
                new HttpMessageConverterExtractor<>(responseType, getMessageConverters(), logger);
           

HttpMessageConverter.java源码:

public interface HttpMessageConverter<T> {
    //指示此转换器是否可以读取给定的类。
    boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);
 
    //指示此转换器是否可以写给定的类。
    boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);
 
    //返回List<MediaType>
    List<MediaType> getSupportedMediaTypes();
 
    //读取一个inputMessage
    T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
            throws IOException, HttpMessageNotReadableException;
 
    //往output message写一个Object
    void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
            throws IOException, HttpMessageNotWritableException;
 
}
           

创建RestTemplate

创建RestTemplate很简单,只需要new RestTemplate(),如果使用Spring架构,将创建的RestTemplate实例通过XML或注解的方式注册到Spring容器中即可

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

RestTemplate构造方法

RestTemplate有3个构造方法,一个无参构造,两个有参构造

RestTemplate无参构造

/**
 * Create a new instance of the {@link RestTemplate} using default settings.
 * Default {@link HttpMessageConverter}s are initialized.
 * 使用默认配置创建一个RestTemplate实例
 * 默认的HttpMessageConverter集合被初始化
 */
public RestTemplate() {
    this.messageConverters.add(new ByteArrayHttpMessageConverter());
    this.messageConverters.add(new StringHttpMessageConverter());
    this.messageConverters.add(new ResourceHttpMessageConverter());
    this.messageConverters.add(new SourceHttpMessageConverter<Source>());
    this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());

    if (romePresent) {
        this.messageConverters.add(new AtomFeedHttpMessageConverter());
        this.messageConverters.add(new RssChannelHttpMessageConverter());
    }

    if (jackson2XmlPresent) {
        this.messageConverters.add(new MappingJackson2XmlHttpMessageConverter());
    }
    else if (jaxb2Present) {
        this.messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
    }

    /**
     * 如果类路径下包含com.fasterxml.jackson.databind.ObjectMapper 和 com.fasterxml.jackson.core.JsonGenerator
     * 使用jackson做http请求、响应的json转换
     */
    if (jackson2Present) {
        this.messageConverters.add(new MappingJackson2HttpMessageConverter());
    }
    else if (gsonPresent) {
        this.messageConverters.add(new GsonHttpMessageConverter());
    }
}
           

参数为ClientHttpRequestFactory的构造

/**
 * Create a new instance of the {@link RestTemplate} based on the given {@link ClientHttpRequestFactory}.
 * @param requestFactory HTTP request factory to use
 * @see org.springframework.http.client.SimpleClientHttpRequestFactory
 * @see org.springframework.http.client.HttpComponentsClientHttpRequestFactory
 * 使用指定的ClientHttpRequestFactory创建一个RestTemplate实例
 * requestFactory是用于创建HTTP请求的工厂,默认的实现有
 * SimpleClientHttpRequestFactory、HttpComponentsClientHttpRequestFactory
 * 如果没有设置默认是SimpleClientHttpRequestFactory
 */
public RestTemplate(ClientHttpRequestFactory requestFactory) {
    this();  //也会调用无参构造初始化默认的messageConverters
    setRequestFactory(requestFactory);
}
           

参数为messageConverters的构造

/**
 * Create a new instance of the {@link RestTemplate} using the given list of
 * {@link HttpMessageConverter} to use
 * @param messageConverters the list of {@link HttpMessageConverter} to use
 * @since 3.2.7
 * 传入自定义的HttpMessageConverter集合,并赋值给messageConverters,之后使用自定义的HttpMessageConverter
 */
public RestTemplate(List<HttpMessageConverter<?>> messageConverters) {
    Assert.notEmpty(messageConverters, "At least one HttpMessageConverter required");
    this.messageConverters.addAll(messageConverters);
}
           

3、RestTemplate API使用

RestTemplate的方法名遵循一定的命名规范,第一部分表示用哪种HTTP方法调用(get,post),第二部分表示返回类型。

  • getForObject() -- 发送GET请求,将HTTP response转换成一个指定的object对象
  • postForEntity() -- 发送POST请求,将给定的对象封装到HTTP请求体,返回类型是一个HttpEntity对象

每个HTTP方法对应的RestTemplate方法都有3种。其中2种的url参数为字符串,其他参数变量分别是Object数组和Map。第3种使用URI类型作为参数。

注意:使用字符串类型的url默认会对url进行转义,如

http://example.com/hotel list

在执行时会转义为

http://example.com/hotel%20list

,这样其实是没有问题的,但如果字符串类型的url本身已经转义过了,执行时就会再转义一次,变成

http://example.com/hotel%2520list

。如果不需要这种隐式的转义,可以使用

java.net.URI

参数的方法,这种方法不会在执行时存在隐式的url转义,可以在创建URI对象时自行决定是否转义,推荐使用

UriComponentsBuilder

创建URI

UriComponents uriComponents = UriComponentsBuilder.fromUriString(
        "http://example.com/hotels/{hotel}/bookings/{booking}")
        .build() //build(true)就不会对url转义,但如果包含http://example.com/hotel list这种需要转义的url,会报错
        .expand("42", "21")
        .encode();

URI uri = uriComponents.toUri();
           

GET方法

getForEntity()

发送GET请求,返回ResponseEntity

/**
 * 参数1: String类型 或 URI类型的请求地址
 * 参数2: 指定返回的实体类型,class对象
 * 参数3: uri参数,可以是变长数组或map
 * 返回值:ResponseEntity<T>是Spring对HTTP响应的封装,包括了几个重要的元素,如响应码、contentType、contentLength、response header信息,response body信息等
 */
@Override
public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables)
        throws RestClientException {
    RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
    ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
    return execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables);
}

@Override
public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String, ?> uriVariables)
        throws RestClientException {
    RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
    ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
    return execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables);
}

@Override
public <T> ResponseEntity<T> getForEntity(URI url, Class<T> responseType) throws RestClientException {
    RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
    ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
    return execute(url, HttpMethod.GET, requestCallback, responseExtractor);
}
           

举例

ResponseEntity<Book> responseEntity = 
          restTemplate.getForEntity("http://127.0.0.1:8080/getbook?bookname={1}", Book.class, "java");
Book book = responseEntity.getBody();  //响应体转换为Book类型
int statusCodeValue = responseEntity.getStatusCodeValue();  //响应状态码
HttpHeaders headers = responseEntity.getHeaders();  //响应头信息
           

getForObject()

发送GET请求,返回指定的Object类型

/**
 * 参数1: String类型 或 URI类型的请求地址
 * 参数2: 指定返回的实体类型,class对象
 * 参数3: uri参数,可以是变长数组或map
 * 返回值:responseType指定的Object类型
 */
@Override
public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException {
    RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
    HttpMessageConverterExtractor<T> responseExtractor =
            new HttpMessageConverterExtractor<T>(responseType, getMessageConverters(), logger);
    return execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables);
}

@Override
public <T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException {
    RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
    HttpMessageConverterExtractor<T> responseExtractor =
            new HttpMessageConverterExtractor<T>(responseType, getMessageConverters(), logger);
    return execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables);
}

@Override
public <T> T getForObject(URI url, Class<T> responseType) throws RestClientException {
    RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
    HttpMessageConverterExtractor<T> responseExtractor =
            new HttpMessageConverterExtractor<T>(responseType, getMessageConverters(), logger);
    return execute(url, HttpMethod.GET, requestCallback, responseExtractor);
}
           

举例:

Book book = restTemplate.getForObject("http://127.0.0.1:8080/getbook?bookname={1}", Book.class, "java");
           

注:使用URI方式,可以在URI中直接封装对应的参数内容

POST方法

postForEntity()

发送POST请求,返回ResponseEntity

/**
 * 参数1: String类型 或 URI类型的请求地址
 * 参数2: 请求body,可以是HttpEntity类型(可设置request header),或其它Object类型
 * 参数3: 指定返回的实体类型,class对象
 * 参数4: uri参数,可以是变长数组或map
 * 返回值:ResponseEntity<T>是Spring对HTTP响应的封装,包括了几个重要的元素,如响应码、contentType、contentLength、response header信息,response body信息等
 */
@Override
public <T> ResponseEntity<T> postForEntity(String url, Object request, Class<T> responseType, Object... uriVariables)  throws RestClientException {
    RequestCallback requestCallback = httpEntityCallback(request, responseType);
    ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
    return execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables);
}

@Override
public <T> ResponseEntity<T> postForEntity(String url, Object request, Class<T> responseType, Map<String, ?> uriVariables)  throws RestClientException {
    RequestCallback requestCallback = httpEntityCallback(request, responseType);
    ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
    return execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables);
}

@Override
public <T> ResponseEntity<T> postForEntity(URI url, Object request, Class<T> responseType) throws RestClientException {
    RequestCallback requestCallback = httpEntityCallback(request, responseType);
    ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
    return execute(url, HttpMethod.POST, requestCallback, responseExtractor);
}
           

举例

//参数是Book类型,返回值是ResponseEntity<Book>类型
ResponseEntity<Book> responseEntity = restTemplate.postForEntity("http://127.0.0.1:8080/updateBook", book, Book.class);

Book book = responseEntity.getBody();  //响应体转换为Book类型
int statusCodeValue = responseEntity.getStatusCodeValue();  //响应状态码
HttpHeaders headers = responseEntity.getHeaders();  //响应头信息
           

postForObject()

发送POST请求,返回指定的Object类型

/**
 * 参数1: String类型 或 URI类型的请求地址
 * 参数2: 请求body,可以是HttpEntity类型(可设置request header),或其它Object类型
 * 参数3: 指定返回的实体类型,class对象
 * 参数4: uri参数,可以是变长数组或map
 * 返回值:responseType指定的Object类型
 */
@Override
public <T> T postForObject(String url, Object request, Class<T> responseType, Object... uriVariables)
        throws RestClientException {
    RequestCallback requestCallback = httpEntityCallback(request, responseType);
    HttpMessageConverterExtractor<T> responseExtractor =
            new HttpMessageConverterExtractor<T>(responseType, getMessageConverters(), logger);
    return execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables);
}

@Override
public <T> T postForObject(String url, Object request, Class<T> responseType, Map<String, ?> uriVariables)
        throws RestClientException {
    RequestCallback requestCallback = httpEntityCallback(request, responseType);
    HttpMessageConverterExtractor<T> responseExtractor =
            new HttpMessageConverterExtractor<T>(responseType, getMessageConverters(), logger);
    return execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables);
}

@Override
public <T> T postForObject(URI url, Object request, Class<T> responseType) throws RestClientException {
    RequestCallback requestCallback = httpEntityCallback(request, responseType);
    HttpMessageConverterExtractor<T> responseExtractor =
            new HttpMessageConverterExtractor<T>(responseType, getMessageConverters());
    return execute(url, HttpMethod.POST, requestCallback, responseExtractor);
}
           

举例

//参数是Book类型,返回值也是Book类型
Book book = restTemplate.postForObject("http://127.0.0.1:8080/updatebook", book, Book.class);
           

exchange方法

  • 可以支持多种HTTP方法,在参数中指定
  • 可以在请求中增加header和body信息,返回类型是ResponseEntity,可以从中获取响应的状态码,header和body等信息

三、RestTemplate扩展

1、设置拦截器(ClientHttpRequestInterceptor)

有时候我们需要对请求做一些通用的拦截设置,这就可以使用拦截器进行处理。拦截器需要我们实现

org.springframework.http.client.ClientHttpRequestInterceptor

接口自己写。

举个简单的例子,写一个在header中根据请求内容和地址添加令牌的拦截器。

public class TokenInterceptor implements ClientHttpRequestInterceptor
{
    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException
    {
        //请求地址
        String checkTokenUrl = request.getURI().getPath();
        //token有效时间
        int ttTime = (int) (System.currentTimeMillis() / 1000 + 1800);
        //请求方法名 POST、GET等
        String methodName = request.getMethod().name();
        //请求内容
        String requestBody = new String(body);
        //生成令牌 此处调用一个自己写的方法,有兴趣的朋友可以自行google如何使用ak/sk生成token,此方法跟本教程无关,就不贴出来了
        String token = TokenHelper.generateToken(checkTokenUrl, ttTime, methodName, requestBody);
        //将令牌放入请求header中
        request.getHeaders().add("X-Auth-Token",token);

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

创建

RestTemplate

实例的时候可以这样向其中添加拦截器

RestTemplate restTemplate = new RestTemplate();
        //向restTemplate中添加自定义的拦截器
        restTemplate.getInterceptors().add(new TokenInterceptor());
           

2、RestTemplate配置

1、处理请求头和响应头

设置请求头信息

(1)如果是发送post、put请求,要设置请求头,可以在调用方法时的第二个参数传入HttpEntity对象,HttpEntity可以用于设置请求头信息,如

HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.set("MyRequestHeader", "MyValue");
HttpEntity requestEntity = new HttpEntity(requestHeaders);

Book book = restTemplate.postForObject("http://127.0.0.1:8080/getbook", requestEntity, Book.class);
           

以postForObject()方法举例,其第二个参数接收Object类型的数据,如传入的是HttpEntity,则使用它作为整个请求实体,如果传入的是其它Object类型,则将Object参数作为request body,新建一个HttpEntity作为请求实体

private HttpEntityRequestCallback(Object requestBody, Type responseType) {
    super(responseType);
    //如果是HttpEntity类型的,直接作为请求实体赋值给this.requestEntity
    if (requestBody instanceof HttpEntity) {
        this.requestEntity = (HttpEntity<?>) requestBody;
    }
    //如果requestBody不是HttpEntity类型,且不为空,以Object参数作为request body,并新建HttpEntity
    else if (requestBody != null) {
        this.requestEntity = new HttpEntity<Object>(requestBody);
    }
    else {
        this.requestEntity = HttpEntity.EMPTY;
    }
}
           

(2)如果是其它HTTP方法调用要设置请求头,可以使用exchange()方法,可以参考 官方示例

HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.set("MyRequestHeader", "MyValue");
HttpEntity requestEntity = new HttpEntity(requestHeaders);

HttpEntity<String> response = template.exchange(
        "http://example.com/hotels/{hotel}",
        HttpMethod.GET, requestEntity, String.class, "42");

String responseHeader = response.getHeaders().getFirst("MyResponseHeader");
String body = response.getBody();
           

处理响应头信息

使用RestTemplate中xxxForEntity()的方法,会返回ResponseEntity,可以从中获取到响应状态码,响应头和body等信息

HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.set("MyRequestHeader", "MyValue");
HttpEntity requestEntity = new HttpEntity(requestHeaders);

HttpEntity<String> response = template.exchange(
        "http://example.com/hotels/{hotel}",
        HttpMethod.GET, requestEntity, String.class, "42");

//response相关信息
String responseHeader = response.getHeaders().getFirst("MyResponseHeader");
String body = response.getBody();
           

2、ClientHttpRequestFactory

ClientHttpRequestFactory是Spring定义的一个接口,其用于生产org.springframework.http.client.ClientHttpRequest对象,RestTemplate只是模板类,抽象了很多调用方法,而底层真正使用何种框架发送HTTP请求是通过ClientHttpRequestFactory指定的

接口定义

/**
 * Factory for {@link ClientHttpRequest} objects.
 * Requests are created by the {@link #createRequest(URI, HttpMethod)} method.
 * ClientHttpRequest对象的工厂
 *
 * @author Arjen Poutsma
 * @since 3.0
 */
public interface ClientHttpRequestFactory {

    /**
     * Create a new {@link ClientHttpRequest} for the specified URI and HTTP method.
     * <p>The returned request can be written to, and then executed by calling
     * {@link ClientHttpRequest#execute()}.
     * 使用指定的URI和HTTP方法新建一个ClientHttpRequest对象
     * 可以修改返回的request,并通过ClientHttpRequest的execute()方法执行调用
     * 即调用的逻辑也被Spring封装到ClientHttpRequest中
     * 
     * @param uri the URI to create a request for
     * @param httpMethod the HTTP method to execute
     * @return the created request
     * @throws IOException in case of I/O errors
     */
    ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException;

}
           

RestTemplate可以在构造时设置ClientHttpRequestFactory,也可以通过setRequestFactory()方法设置

构造方法设置:
/**
 * Create a new instance of the {@link RestTemplate} based on the given {@link ClientHttpRequestFactory}.
 * @param requestFactory HTTP request factory to use
 * @see org.springframework.http.client.SimpleClientHttpRequestFactory
 * @see org.springframework.http.client.HttpComponentsClientHttpRequestFactory
 */
public RestTemplate(ClientHttpRequestFactory requestFactory) {
    this();
    setRequestFactory(requestFactory);
}
           

可以看到上面注释中已经给出了Spring的两种ClientHttpRequestFactory的实现类

SimpleClientHttpRequestFactory

HttpComponentsClientHttpRequestFactory

SimpleClientHttpRequestFactory——这个有点危险

如果什么都不设置,RestTemplate默认使用的是

SimpleClientHttpRequestFactory

,其内部使用的是jdk的

java.net.HttpURLConnection

创建底层连接,默认是没有连接池的,

connectTimeout

readTimeout

都是 -1,即没有超时时间

public class SimpleClientHttpRequestFactory implements ClientHttpRequestFactory, AsyncClientHttpRequestFactory {
    。。。。。。
        
    private int connectTimeout = -1;
    private int readTimeout = -1;
    
    //创建Request
    @Override
    public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
        HttpURLConnection connection = openConnection(uri.toURL(), this.proxy);
        prepareConnection(connection, httpMethod.name());

         //bufferRequestBody默认为true
        if (this.bufferRequestBody) {
            return new SimpleBufferingClientHttpRequest(connection, this.outputStreaming);
        }
        else {
            return new SimpleStreamingClientHttpRequest(connection, this.chunkSize, this.outputStreaming);
        }
    }
    
    
    /**
     * Opens and returns a connection to the given URL.
     * 打开并返回一个指定URL的连接
     * <p>The default implementation uses the given {@linkplain #setProxy(java.net.Proxy) proxy} -
     * if any - to open a connection.
     * @param url the URL to open a connection to
     * @param proxy the proxy to use, may be {@code null}
     * @return the opened connection  返回类型为 java.net.HttpURLConnection
     * @throws IOException in case of I/O errors
     */
    protected HttpURLConnection openConnection(URL url, Proxy proxy) throws IOException {
        URLConnection urlConnection = (proxy != null ? url.openConnection(proxy) : url.openConnection());
        if (!HttpURLConnection.class.isInstance(urlConnection)) {
            throw new IllegalStateException("HttpURLConnection required for [" + url + "] but got: " + urlConnection);
        }
        return (HttpURLConnection) urlConnection;
    }
    
    
    /**
     * Template method for preparing the given {@link HttpURLConnection}.
     * <p>The default implementation prepares the connection for input and output, and sets the HTTP method.
     * @param connection the connection to prepare
     * @param httpMethod the HTTP request method ({@code GET}, {@code POST}, etc.)
     * @throws IOException in case of I/O errors
     */
    protected void prepareConnection(HttpURLConnection connection, String httpMethod) throws IOException {
         //如果connectTimeout大于等于0,设置连接超时时间
        if (this.connectTimeout >= 0) {
            connection.setConnectTimeout(this.connectTimeout);
        }
         //如果readTimeout大于等于0,设置读超时时间
        if (this.readTimeout >= 0) {
            connection.setReadTimeout(this.readTimeout);
        }

        connection.setDoInput(true);

        if ("GET".equals(httpMethod)) {
            connection.setInstanceFollowRedirects(true);
        }
        else {
            connection.setInstanceFollowRedirects(false);
        }

        if ("POST".equals(httpMethod) || "PUT".equals(httpMethod) ||
                "PATCH".equals(httpMethod) || "DELETE".equals(httpMethod)) {
            connection.setDoOutput(true);
        }
        else {
            connection.setDoOutput(false);
        }

        connection.setRequestMethod(httpMethod);
    }
    
    。。。。。。
}
           

HttpComponentsClientHttpRequestFactory

HttpComponentsClientHttpRequestFactory底层使用Apache HttpClient创建请求,访问远程的Http服务,可以使用一个已经配置好的HttpClient实例创建HttpComponentsClientHttpRequestFactory请求工厂,HttpClient实例中可以配置连接池和证书等信息

添加HttpClient依赖

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>x.x.x</version>   <!-- springboot项目不用指定 -->
</dependency>
           

设置超时时间

设置超时时间,可以直接使用Spring的底层基于HttpClient的HttpComponentsClientHttpRequestFactory,此处设置的是ClientHttpRequestFactory级别的全局超时时间

@Configuration  
public class RestTemplateConfig {  
  
    @Bean  
    public RestTemplate restTemplate() {  
        return new RestTemplate(clientHttpRequestFactory());  
    }  
  
    @Bean 
    private ClientHttpRequestFactory clientHttpRequestFactory() {  
        HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();  
        factory.setConnectTimeout(30 * 1000);  //连接超时时间
        factory.setReadTimeout(60 * 1000);  //读超时时间
        return factory;  
    }  
} 
           

注意:如果通过一个HttpClient实例创建HttpComponentsClientHttpRequestFactory,并通过HttpClient指定了DefaultRequestConfig,设置了connectTimeout、readTimeout等,在实际执行请求创建request时会与HttpComponentsClientHttpRequestFactory的配置合并,connectTimeout、socketTimeout、connectionRequestTimeout 以HttpComponentsClientHttpRequestFactory的配置为准

HttpComponentsClientHttpRequestFactory:
/**
 * Merge the given {@link HttpClient}-level {@link RequestConfig} with
 * the factory-level {@link RequestConfig}, if necessary.
 * @param clientConfig the config held by the current    httpClient级别的requestConfig配置
 * @return the merged request config
 * (may be {@code null} if the given client config is {@code null})
 * @since 4.2
 */
protected RequestConfig mergeRequestConfig(RequestConfig clientConfig) {
    if (this.requestConfig == null) {  // nothing to merge
        return clientConfig;
    }

    RequestConfig.Builder builder = RequestConfig.copy(clientConfig);
    int connectTimeout = this.requestConfig.getConnectTimeout();  //HttpComponentsClientHttpRequestFactory级别的配置
    if (connectTimeout >= 0) {
        builder.setConnectTimeout(connectTimeout);
    }
    int connectionRequestTimeout = this.requestConfig.getConnectionRequestTimeout();
    if (connectionRequestTimeout >= 0) {
        builder.setConnectionRequestTimeout(connectionRequestTimeout);
    }
    int socketTimeout = this.requestConfig.getSocketTimeout();
    if (socketTimeout >= 0) {
        builder.setSocketTimeout(socketTimeout);
    }
    return builder.build();
}
           

上例中虽然没有指定http连接池,但HttpComponentsClientHttpRequestFactory无参构造会创建一个HttpClient,并默认使用了连接池配置,MaxTotal=10,DefaultMaxPerRoute=5 ,具体如下:

HttpComponentsClientHttpRequestFactory:
/**
 * Create a new instance of the {@code HttpComponentsClientHttpRequestFactory}
 * with a default {@link HttpClient}.
 */
public HttpComponentsClientHttpRequestFactory() {
    this(HttpClients.createSystem());
}


HttpClients:
/**
 * Creates {@link CloseableHttpClient} instance with default
 * configuration based on system properties.
 * 创建CloseableHttpClient实例使用基于system properties的默认配置
 */
public static CloseableHttpClient createSystem() {
    return HttpClientBuilder.create().useSystemProperties().build();
}


HttpClientBuilder:
/**
 * Use system properties when creating and configuring default
 * implementations.
 */
public final HttpClientBuilder useSystemProperties() {
    this.systemProperties = true;  //设置systemProperties为true
    return this;
}

public CloseableHttpClient build() {
    HttpClientConnectionManager connManagerCopy = this.connManager; //没有设置,为null
    if (connManagerCopy == null) {
        。。。。。。
        //创建连接池管理器PoolingHttpClientConnectionManager
        @SuppressWarnings("resource")
        final PoolingHttpClientConnectionManager poolingmgr = new PoolingHttpClientConnectionManager(
                RegistryBuilder.<ConnectionSocketFactory>create()
                    .register("http", PlainConnectionSocketFactory.getSocketFactory())
                    .register("https", sslSocketFactoryCopy)
                    .build(),
                null,
                null,
                dnsResolver,
                connTimeToLive,
                connTimeToLiveTimeUnit != null ? connTimeToLiveTimeUnit : TimeUnit.MILLISECONDS);
        if (defaultSocketConfig != null) {
            poolingmgr.setDefaultSocketConfig(defaultSocketConfig);
        }
        if (defaultConnectionConfig != null) {
            poolingmgr.setDefaultConnectionConfig(defaultConnectionConfig);
        }
        //由于是HttpClientBuilder.create().useSystemProperties().build(),systemProperties为true
        if (systemProperties) {
            String s = System.getProperty("http.keepAlive", "true");  //http.keepAlive默认值为true
            if ("true".equalsIgnoreCase(s)) {
                s = System.getProperty("http.maxConnections", "5");  //默认值为5
                final int max = Integer.parseInt(s);
                poolingmgr.setDefaultMaxPerRoute(max);  //DefaultMaxPerRoute=5
                poolingmgr.setMaxTotal(2 * max);  //MaxTotal=10
            }
        }
        if (maxConnTotal > 0) {
            poolingmgr.setMaxTotal(maxConnTotal);
        }
        if (maxConnPerRoute > 0) {
            poolingmgr.setDefaultMaxPerRoute(maxConnPerRoute);
        }
        connManagerCopy = poolingmgr;
    }
}
           

配置连接池

@Configuration  
public class RestTemplateConfig {  
  
    @Bean  
    public RestTemplate restTemplate() {  
        return new RestTemplate(clientHttpRequestFactory());  
    }  
  
    @Bean
    public HttpComponentsClientHttpRequestFactory clientHttpRequestFactory() {
        try {
            HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();

            //开始设置连接池
            PoolingHttpClientConnectionManager poolingHttpClientConnectionManager 
                                                    = new PoolingHttpClientConnectionManager();
            poolingHttpClientConnectionManager.setMaxTotal(100);  //最大连接数
            poolingHttpClientConnectionManager.setDefaultMaxPerRoute(20);  //同路由并发数
            httpClientBuilder.setConnectionManager(poolingHttpClientConnectionManager);

            HttpClient httpClient = httpClientBuilder.build();
            // httpClient连接配置
            HttpComponentsClientHttpRequestFactory clientHttpRequestFactory 
                                                    = new HttpComponentsClientHttpRequestFactory(httpClient);
            clientHttpRequestFactory.setConnectTimeout(30 * 1000);  //连接超时
            clientHttpRequestFactory.setReadTimeout(60 * 1000);     //数据读取超时时间
            clientHttpRequestFactory.setConnectionRequestTimeout(30 * 1000);  //连接不够用的等待时间
            return clientHttpRequestFactory;
        }
        catch (Exception e) {
            logger.error("初始化clientHttpRequestFactory出错", e);
        }
        return null;
    } 
} 
           

3、自定义messageConverter

RestTemplate的无参构造中默认会初始化很多messageConverters,用于请求/响应中的消息转换

/**
 * Create a new instance of the {@link RestTemplate} using default settings.
 * Default {@link HttpMessageConverter}s are initialized.
 * 使用默认配置创建一个RestTemplate实例
 * 默认的HttpMessageConverter集合被初始化
 */
public RestTemplate() {
    this.messageConverters.add(new ByteArrayHttpMessageConverter());
    this.messageConverters.add(new StringHttpMessageConverter());
    this.messageConverters.add(new ResourceHttpMessageConverter());
    this.messageConverters.add(new SourceHttpMessageConverter<Source>());
    this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());

    if (romePresent) {
        this.messageConverters.add(new AtomFeedHttpMessageConverter());
        this.messageConverters.add(new RssChannelHttpMessageConverter());
    }

    if (jackson2XmlPresent) {
        this.messageConverters.add(new MappingJackson2XmlHttpMessageConverter());
    }
    else if (jaxb2Present) {
        this.messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
    }

    /**
     * 如果类路径下包含com.fasterxml.jackson.databind.ObjectMapper 和 com.fasterxml.jackson.core.JsonGenerator
     * 使用jackson做http请求、响应的json转换
     */
    if (jackson2Present) {
        this.messageConverters.add(new MappingJackson2HttpMessageConverter());
    }
    else if (gsonPresent) {  //类路径下包含 com.google.gson.Gson
        this.messageConverters.add(new GsonHttpMessageConverter());
    }
}
           

使用fastjson做json转换

springboot项目默认使用jackson做json转换

  1. 引入fastjson依赖
  2. 排除jackson的HttpMessageConverter转换器
  3. 添加fastjson的转换器

排除jackson的HttpMessageConverter转换器有两种方式:

(1)类路径下去掉jackson的支持

从RestTemplate的无参构造可以看出,需要判断类路径下是否有jackson的相关类,有才会添加MappingJackson2HttpMessageConverter,故可以在pom.xml中排除jackson的支持,以springboot项目举例

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <artifactId>jackson-databind</artifactId> 
            <groupId>com.fasterxml.jackson.core</groupId>
        </exclusion>
    </exclusions>
</dependency>
           

(2)在初始化配置RestTemplate时,去掉其默认的MappingJackson2HttpMessageConverter

@Bean
public RestTemplate restTemplate() {
    RestTemplate restTemplate = new RestTemplate();
    restTemplate.setRequestFactory(clientHttpRequestFactory());

    //restTemplate默认的HttpMessageConverter
    List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters();
    List<HttpMessageConverter<?>> messageConvertersNew = new ArrayList<HttpMessageConverter<?>>();
    
    for(HttpMessageConverter httpMessageConverter : messageConverters){
        //跳过MappingJackson2HttpMessageConverter
        if (httpMessageConverter instanceof MappingJackson2HttpMessageConverter) continue;

        messageConvertersNew.add(httpMessageConverter);
    }

    //添加fastjson转换器
    messageConvertersNew.add(fastJsonHttpMessageConverter());

    return restTemplate;
}

@Bean
public HttpMessageConverter fastJsonHttpMessageConverter() {
    //MediaType
    List<MediaType> mediaTypes = new ArrayList<>();
    mediaTypes.add(MediaType.APPLICATION_JSON_UTF8);

    //FastJsonConfig
    FastJsonConfig fastJsonConfig = new FastJsonConfig();
    fastJsonConfig.setSerializerFeatures(SerializerFeature.WriteMapNullValue,
                                         SerializerFeature.QuoteFieldNames);

    //创建FastJsonHttpMessageConverter4    Spring 4.2后使用
    FastJsonHttpMessageConverter4 fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter4();
    fastJsonHttpMessageConverter.setSupportedMediaTypes(mediaTypes);
    fastJsonHttpMessageConverter.setFastJsonConfig(fastJsonConfig);

    return fastJsonHttpMessageConverter;
}
           

四、SpringBoot中使用RestTemplate

SpringBoot项目可以通过上面的方式,@Bean往Spring容器中注册一个配置好的RestTemplate实例,也可以参考 SpringBoot官方 的方式自定义RestTemplate

由于RestTemplate实例在使用前经常需要自定义,SpringBoot没有提供自动配置好的RestTemplate,但是自动配置好了可以用于创建RestTemplate的

RestTemplateBuilder

实例,可以按如下使用

@Service
public class MyBean {

    private final RestTemplate restTemplate;

    public MyBean(RestTemplateBuilder restTemplateBuilder) {
        this.restTemplate = restTemplateBuilder.build();
    }

    public Details someRestCall(String name) {
        return this.restTemplate.getForObject("/{name}/details", Details.class, name);
    }

}
           

继续阅读