SpringBoot 如何使用 RestTemplate 优雅的

作者: 8e744d4a988c | 来源:发表于2019-03-06 23:01 被阅读13次

    1、在 pom.xml 里引入依赖:

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

    2、直接使用 new RestTemplate() 使用 RestTemplate

    示例代码(注意这有坑,下面解释):

    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.

    3、使用 RestTemplateBuilder,官网文档也有写,提供了自动配置的 RestTemplateBuilder,可以用 RestTemplateBuilder 创建 RestTemplate

    示例代码:

    @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 了。

    4、编写配置类使用:

    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 注入使用了。

    通过以上三种方式(2、3、4)都可以使用 RestTemplate 访问第三方接口,但是上面说的有个坑,在这里:
    使用 new RestTemplate()RestTemplateBuilder 创建的 RestTemplate 访问的时候,编码有问题,我使用 new RestTemplate() 访问百度,得到乱码的HTML,但是使用 RestTemplateBuilder 创建的 RestTemplate 访问的时候,确是正常的!!!

    来看源码,一探究竟:
    通过集成 actuator 查看所有 beans 发现这么一个 Bean

    "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 类:

    @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 类:

    @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 的内部类,里面配置了名为 StringHttpMessageConverterBean,他内部使用了 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,截图如下:

    spring.http.encoding.charset

    那么我们来实验一下,修改 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 乱码问题已经得到解决。

    相关文章

      网友评论

        本文标题:SpringBoot 如何使用 RestTemplate 优雅的

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