SpringBoot 如何使用 RestTemplate 優雅的進行 Restful 資源訪問?

一、在 pom.xml 裏引入依賴:web

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>

二、直接使用 new RestTemplate() 使用 RestTemplate:spring

示例代碼(注意這有坑,下面解釋):json

String baiduHtml = new RestTemplate().getForObject("https://www.baidu.com", String.class);
System.out.println(baiduHtml);

注:不能直接使用 @Autowired 自動注入 RestTemplate 使用。由於,官網文檔有寫:
Since RestTemplate instances often need to be customized before being used, Spring Boot does not provide any single auto-configured RestTemplate bean.
三、使用 RestTemplateBuilder,官網文檔也有寫,提供了自動配置的 RestTemplateBuilder,能夠用 RestTemplateBuilder 建立 RestTemplate:
示例代碼:app

@Autowired
private RestTemplateBuilder restTemplateBuilder;
@Autowired
private RestTemplate restTemplate;
@Bean
public RestTemplate getRestTemplate() {
    return restTemplateBuilder.build();
}

//調用方式:
String baiduHtml = restTemplate.getForObject("https://www.baidu.com", String.class);
System.out.println(baiduHtml);

這樣處理以後(在 SpringBootApplication 入口中定義),在任何地方就能夠直接使用自動配置的 RestTemplate 了。ide

四、編寫配置類使用:spring-boot

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RestTemplateConfig {
    @Autowired
    private RestTemplateBuilder restTemplateBuilder;
    @Bean
    public RestTemplate restTemplate(){
        return restTemplateBuilder.build();
    }
}

而後就能夠在其餘地方直接使用 @Autowired 注入使用了。
經過以上三種方式(二、三、4)均可以使用 RestTemplate 訪問第三方接口,可是上面說的有個坑,在這裏:
使用 new RestTemplate() 和 RestTemplateBuilder 建立的 RestTemplate 訪問的時候,編碼有問題,我使用 new RestTemplate() 訪問百度,獲得亂碼的HTML,可是使用 RestTemplateBuilder 建立的 RestTemplate 訪問的時候,確是正常的!!!
來看源碼,一探究竟:
經過集成 actuator 查看全部 beans 發現這麼一個 Bean:ui

"restTemplateBuilder": {
  aliases: [ ],
  scope: "singleton",
  type: "org.springframework.boot.web.client.RestTemplateBuilder",
  resource: "class path resource [org/springframework/boot/autoconfigure/web/client/RestTemplateAutoConfiguration.class]",
  dependencies: [ ]
}

根據 resource 找到這個 RestTemplateAutoConfiguration 類:this

@Configuration
@AutoConfigureAfter({HttpMessageConvertersAutoConfiguration.class})
@ConditionalOnClass({RestTemplate.class})
public class RestTemplateAutoConfiguration {
    private final ObjectProvider<HttpMessageConverters> messageConverters;
    private final ObjectProvider<RestTemplateCustomizer> restTemplateCustomizers;

    public RestTemplateAutoConfiguration(ObjectProvider<HttpMessageConverters> messageConverters, ObjectProvider<RestTemplateCustomizer> restTemplateCustomizers) {
        this.messageConverters = messageConverters;
        this.restTemplateCustomizers = restTemplateCustomizers;
    }

    @Bean
    @ConditionalOnMissingBean
    public RestTemplateBuilder restTemplateBuilder() {
        RestTemplateBuilder builder = new RestTemplateBuilder(new RestTemplateCustomizer[0]);
        HttpMessageConverters converters = (HttpMessageConverters)this.messageConverters.getIfUnique();
        if(converters != null) {
            builder = builder.messageConverters(converters.getConverters());
            //↑ ↑ ↑ 看這裏,猜想編碼轉換應該是這裏加入了轉換器
        }

        List customizers = (List)this.restTemplateCustomizers.orderedStream().collect(Collectors.toList());
        if(!CollectionUtils.isEmpty(customizers)) {
            builder = builder.customizers(customizers);
        }

        return builder;
    }
}

看這個類上面有註解:編碼

@AutoConfigureAfter({HttpMessageConvertersAutoConfiguration.class})

顧名思義,是在 HttpMessageConvertersAutoConfiguration 這個配置類以後配置此類。繼續進入 HttpMessageConvertersAutoConfiguration 類:url

@Configuration
@ConditionalOnClass({HttpMessageConverter.class})
@AutoConfigureAfter({GsonAutoConfiguration.class, JacksonAutoConfiguration.class, JsonbAutoConfiguration.class})
@Import({JacksonHttpMessageConvertersConfiguration.class, GsonHttpMessageConvertersConfiguration.class, JsonbHttpMessageConvertersConfiguration.class})
public class HttpMessageConvertersAutoConfiguration {
    static final String PREFERRED_MAPPER_PROPERTY = "spring.http.converters.preferred-json-mapper";
    private final List<HttpMessageConverter<?>> converters;

    public HttpMessageConvertersAutoConfiguration(ObjectProvider<HttpMessageConverter<?>> convertersProvider) {
        this.converters = (List)convertersProvider.orderedStream().collect(Collectors.toList());
    }

    @Bean
    @ConditionalOnMissingBean
    public HttpMessageConverters messageConverters() {
        return new HttpMessageConverters(this.converters);
    }

    @Configuration
    @ConditionalOnClass({StringHttpMessageConverter.class})
    @EnableConfigurationProperties({HttpProperties.class})
    protected static class StringHttpMessageConverterConfiguration {
        private final Encoding properties;

        protected StringHttpMessageConverterConfiguration(HttpProperties httpProperties) {
            this.properties = httpProperties.getEncoding();
        }

        @Bean
        @ConditionalOnMissingBean
        public StringHttpMessageConverter stringHttpMessageConverter() {
            StringHttpMessageConverter converter = new StringHttpMessageConverter(this.properties.getCharset());
            converter.setWriteAcceptCharset(false);
            return converter;
        }
    }
}

能夠看到這個類中有一個 protected 的內部類,裏面配置了名爲 StringHttpMessageConverter 的 Bean,他內部使用了 HttpProperties.class 這個配置類裏面的配置,再深刻 HttpProperties 看看,代碼過多,因此省略了不少:

@ConfigurationProperties(
    prefix = "spring.http"
)
public class HttpProperties {
    private final HttpProperties.Encoding encoding = new HttpProperties.Encoding();

    //...省略其餘代碼

    public HttpProperties.Encoding getEncoding() {
        return this.encoding;
    }

    public static class Encoding {
        public static final Charset DEFAULT_CHARSET;
        private Charset charset;

        public Encoding() {
            this.charset = DEFAULT_CHARSET;
        }

        public Charset getCharset() {
            return this.charset;
        }

        //...省略其餘代碼

        static {
            DEFAULT_CHARSET = StandardCharsets.UTF_8;
        }
        //...省略其餘代碼
    }
}

到這裏彷佛就漸漸明白了,若是使用 RestTemplateBuilder 建立的 RestTemplate,它默認就是用了 spring.http.encoding.charset 這個配置的編碼。在 application.properties 中配置的時候,能夠看到這個編碼默認就是 UTF-8,截圖以下:
SpringBoot 如何使用 RestTemplate 優雅的進行 Restful 資源訪問?
那麼咱們來實驗一下,修改 application.properties 增長配置:

spring.http.encoding.charset=ISO-8859-1

若是不出意外,使用 RestTemplateBuilder 建立的 RestTemplate 訪問的時候應該是會是亂碼。

@Autowired
    private RestTemplate restTemplate;
    private String getRestResponse(String url) {
        try {
            return new RestTemplate().getForObject(url, String.class);
        } catch(Exception ex) {
            return ex.toString();
        }
    }
    private String getRestResponse2(String url) {
        try {
            return restTemplate.getForObject(url, String.class);
        } catch(Exception ex) {
            return ex.toString();
        }
    }
    @Override
    public void run(ApplicationArguments applicationArguments) throws Exception {
        System.out.println(getRestResponse("https://www.baidu.com"));
        System.out.println(getRestResponse2("https://www.baidu.com"));
    }

此時,第二種方式獲取到的百度源代碼和第一種方式獲取到的源代碼一致,都是亂碼,可見直接使用 new RestTemplate() 將使用默認編碼 ISO-8859-1。
那麼問題來了,如何在使用 new RestTemplate() 的時候一樣是 UTF-8 編碼呢?在建立 RestTemplate 以後作一點處理便可,代碼以下:

private String getRestResponse(String url) {
        try {
            RestTemplate rest = new RestTemplate();
            for (HttpMessageConverter<?> converter : rest.getMessageConverters()) {
                if (converter instanceof StringHttpMessageConverter) {
                    ((StringHttpMessageConverter)converter).setDefaultCharset(Charset.forName("UTF-8"));
                    break;
                }
            }
            return rest.getForObject(url, String.class);
        } catch(Exception ex) {
            return ex.toString();
        }
    }

此時,RestTemplate 亂碼問題已經獲得解決。

相關文章
相關標籤/搜索