美文网首页码农的世界MyBatis+SpringMVC+SpringBootJava
RestTemplate用更优雅的方式发送HTTP请求

RestTemplate用更优雅的方式发送HTTP请求

作者: 一觉睡到丶小时候 | 来源:发表于2019-10-08 16:31 被阅读0次

    RestTemplate是Spring提供的用于访问Rest服务的客户端,RestTemplate提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率。
    我之前的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

    Spring文档: https://docs.spring.io/spring/docs/4.3.9.RELEASE/spring-framework-reference/html/remoting.html#rest-client-access

    The RestTemplate is the core class for client-side access to RESTful services. It is conceptually similar to other template classes in Spring, such as JdbcTemplate andJmsTemplate and other template classes found in other Spring portfolio projects.RestTemplate’s behavior is customized by providing callback methods and configuring the HttpMessageConverter used to marshal objects into the HTTP request body and to unmarshal any response back into an object. As it is common to use XML as a message format, Spring provides a MarshallingHttpMessageConverter that uses the Object-to-XML framework that is part of the org.springframework.oxm package. This gives you a wide range of choices of XML to Object mapping technologies to choose from.
    This section describes how to use the RestTemplate and its associated HttpMessageConverters.

    • RestTemplate是Spring的通过客户端访问RESTful服务端的核心类,和JdbcTemplate、JmsTemplate概念相似,都是Spring提供的模板类
    • RestTemplate的行为可以通过callback回调方法和配置HttpMessageConverter来定制,用来把对象封装到HTTP请求体,将响应信息放到一个对象中

    Invoking RESTful services in Java is typically done using a helper class such as Apache HttpComponents HttpClient. For common REST operations this approach is too low level as shown below.

    • java中调用RESTful服务很典型的是使用HttpClient,对于常用的REST操作,这些方法属于低等级的操作
    String uri = "http://example.com/hotels/1/bookings";
    
    PostMethod post = new PostMethod(uri);
    String request = // create booking request content
    post.setRequestEntity(new StringRequestEntity(request));
    
    httpClient.executeMethod(post);
    
    if (HttpStatus.SC_CREATED == post.getStatusCode()) {
        Header location = post.getRequestHeader("Location");
        if (location != null) {
            System.out.println("Created new booking at :" + location.getValue());
        }
    }
    

    使用HttpClient我们需要自己封装Post请求,再根据响应的状态码判断从响应中获取header和body,有时候还需要自己做json转换

    RestTemplate provides higher level methods that correspond to each of the six main HTTP methods that make invoking many RESTful services a one-liner and enforce REST best practices.
    RestTemplate提供更高等级的符合HTTP的6中主要方法,可以很简单的调用RESTful服务

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


    restTemplate.png

    二、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);
    }
    

    三、RestTemplate API使用

    RestTemplate 4.3.9.RELEASE版本 API

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

    • getForObject() -- 发送GET请求,将HTTP response转换成一个指定的object对象

    • postForEntity() -- 发送POST请求,将给定的对象封装到HTTP请求体,返回类型是一个HttpEntity对象

    • 每个HTTP方法对应的RestTemplate方法都有3种。其中2种的url参数为字符串,URI参数变量分别是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");
    
    
    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扩展

    设置拦截器(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());
    

    五、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的实现类SimpleClientHttpRequestFactoryHttpComponentsClientHttpRequestFactory

    SimpleClientHttpRequestFactory

    如果什么都不设置,RestTemplate默认使用的是SimpleClientHttpRequestFactory,其内部使用的是jdk的java.net.HttpURLConnection创建底层连接,默认是没有连接池的,connectTimeoutreadTimeout都是 -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转换器

    1. 添加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);
        }
    
    }
    

    相关文章

      网友评论

        本文标题:RestTemplate用更优雅的方式发送HTTP请求

        本文链接:https://www.haomeiwen.com/subject/pqbbpctx.html