zoukankan      html  css  js  c++  java
  • 002-04-RestTemplate 使用常见问题

    一、使用

      同前三节:ClientGetGoodsByGoodsIdResponse response = restTemplate.postForObject(svcUrl, request, Response.class);

    二、问题汇总

    1、no suitable HttpMessageConverter found for request type异常

    这个问题通常会出现在postForObject中传入对象进行调用的时候。

    分析RestTemplate源码,在HttpEntityRequestCallback类的doWithRequest方法中,如果messageConverters(这个字段后面会继续提及)列表字段循环处理的过程中没有满足return跳出的逻辑(也就是没有匹配的HttpMessageConverter),则抛出上述异常:

    @Override
            @SuppressWarnings("unchecked")
            public void doWithRequest(ClientHttpRequest httpRequest) throws IOException {
                super.doWithRequest(httpRequest);
                Object requestBody = this.requestEntity.getBody();
                if (requestBody == null) {
                    HttpHeaders httpHeaders = httpRequest.getHeaders();
                    HttpHeaders requestHeaders = this.requestEntity.getHeaders();
                    if (!requestHeaders.isEmpty()) {
                        for (Map.Entry<String, List<String>> entry : requestHeaders.entrySet()) {
                            httpHeaders.put(entry.getKey(), new LinkedList<>(entry.getValue()));
                        }
                    }
                    if (httpHeaders.getContentLength() < 0) {
                        httpHeaders.setContentLength(0L);
                    }
                }
                else {
                    Class<?> requestBodyClass = requestBody.getClass();
                    Type requestBodyType = (this.requestEntity instanceof RequestEntity ?
                            ((RequestEntity<?>)this.requestEntity).getType() : requestBodyClass);
                    HttpHeaders httpHeaders = httpRequest.getHeaders();
                    HttpHeaders requestHeaders = this.requestEntity.getHeaders();
                    MediaType requestContentType = requestHeaders.getContentType();
                    for (HttpMessageConverter<?> messageConverter : getMessageConverters()) {
                        if (messageConverter instanceof GenericHttpMessageConverter) {
                            GenericHttpMessageConverter<Object> genericConverter =
                                    (GenericHttpMessageConverter<Object>) messageConverter;
                            if (genericConverter.canWrite(requestBodyType, requestBodyClass, requestContentType)) {
                                if (!requestHeaders.isEmpty()) {
                                    for (Map.Entry<String, List<String>> entry : requestHeaders.entrySet()) {
                                        httpHeaders.put(entry.getKey(), new LinkedList<>(entry.getValue()));
                                    }
                                }
                                if (logger.isDebugEnabled()) {
                                    if (requestContentType != null) {
                                        logger.debug("Writing [" + requestBody + "] as "" + requestContentType +
                                                "" using [" + messageConverter + "]");
                                    }
                                    else {
                                        logger.debug("Writing [" + requestBody + "] using [" + messageConverter + "]");
                                    }
    
                                }
                                genericConverter.write(requestBody, requestBodyType, requestContentType, httpRequest);
                                return;
                            }
                        }
                        else if (messageConverter.canWrite(requestBodyClass, requestContentType)) {
                            if (!requestHeaders.isEmpty()) {
                                for (Map.Entry<String, List<String>> entry : requestHeaders.entrySet()) {
                                    httpHeaders.put(entry.getKey(), new LinkedList<>(entry.getValue()));
                                }
                            }
                            if (logger.isDebugEnabled()) {
                                if (requestContentType != null) {
                                    logger.debug("Writing [" + requestBody + "] as "" + requestContentType +
                                            "" using [" + messageConverter + "]");
                                }
                                else {
                                    logger.debug("Writing [" + requestBody + "] using [" + messageConverter + "]");
                                }
    
                            }
                            ((HttpMessageConverter<Object>) messageConverter).write(
                                    requestBody, requestContentType, httpRequest);
                            return;
                        }
                    }
                    String message = "Could not write request: no suitable HttpMessageConverter found for request type [" +
                            requestBodyClass.getName() + "]";
                    if (requestContentType != null) {
                        message += " and content type [" + requestContentType + "]";
                    }
                    throw new RestClientException(message);
                }
            }
    
    HttpEntityRequestCallback.doWithRequest
    View Code

    最简单的解决方案是,可以通过包装http请求头,并将请求对象序列化成字符串的形式传参,参考示例代码如下:

        /*
         * Post请求调用
         * */
        public static String postForObject(RestTemplate restTemplate, String url, Object params) {
            HttpHeaders headers = new HttpHeaders();
            MediaType type = MediaType.parseMediaType("application/json; charset=UTF-8");
            headers.setContentType(type);
            headers.add("Accept", MediaType.APPLICATION_JSON.toString());
    
            String json = JSON.toJSONString(params);
    
            HttpEntity<String> formEntity = new HttpEntity<String>(json, headers);
    
            String result = restTemplate.postForObject(url, formEntity, String.class);
    
            return result;
        }

    如果我们还想直接返回对象,直接反序列化返回的字符串即可:

        /*
         * Post请求调用
         * */
        public static <T> T postForObject(RestTemplate restTemplate, String url, Object params, Class<T> clazz) {
            T response = null;
    
            String respStr = postForObject(restTemplate, url, params);
    
            response = JSON.parseObject(respStr, clazz);
    
            return response;
        }

    其中,序列化和反序列化工具比较多,常用的比如fastjson、jackson和gson。

    2、no suitable HttpMessageConverter found for response type异常

    和发起请求发生异常一样,处理应答的时候也会有问题。

    StackOverflow上有人问过相同的问题,根本原因是HTTP消息转换器HttpMessageConverter缺少MIME Type,也就是说HTTP在把输出结果传送到客户端的时候,客户端必须启动适当的应用程序来处理这个输出文档,这可以通过多种MIME(多功能网际邮件扩充协议)Type来完成。

    对于服务端应答,很多HttpMessageConverter默认支持的媒体类型(MIMEType)都不同。StringHttpMessageConverter默认支持的则是MediaType.TEXT_PLAIN,SourceHttpMessageConverter默认支持的则是MediaType.TEXT_XML,FormHttpMessageConverter默认支持的是MediaType.APPLICATION_FORM_URLENCODED和MediaType.MULTIPART_FORM_DATA,在REST服务中,我们用到的最多的还是MappingJackson2HttpMessageConverter,这是一个比较通用的转化器(继承自GenericHttpMessageConverter接口),根据分析,它默认支持的MIMEType为MediaType.APPLICATION_JSON:

        /**
         * Construct a new {@link MappingJackson2HttpMessageConverter} with a custom {@link ObjectMapper}.
         * You can use {@link Jackson2ObjectMapperBuilder} to build it easily.
         * @see Jackson2ObjectMapperBuilder#json()
         */
        public MappingJackson2HttpMessageConverter(ObjectMapper objectMapper) {
            super(objectMapper, MediaType.APPLICATION_JSON, new MediaType("application", "*+json"));
        }

    但是有些应用接口默认的应答MIMEType不是application/json,比如我们调用一个外部天气预报接口,如果使用RestTemplate的默认配置,直接返回一个字符串应答是没有问题的:

    String url = "http://wthrcdn.etouch.cn/weather_mini?city=上海";
    String result = restTemplate.getForObject(url, String.class);
    ClientWeatherResultVO vo = SerializeUtil.DeSerialize(result, ClientWeatherResultVO.class);

    但是,如果我们想直接返回一个实体对象:

    String url = "http://wthrcdn.etouch.cn/weather_mini?city=上海";
    
    ClientWeatherResultVO weatherResultVO = restTemplate.getForObject(url, ClientWeatherResultVO.class);

    则直接报异常:
    Could not extract response: no suitable HttpMessageConverter found for response type [class ]
    and content type [application/octet-stream]

    很多人碰到过这个问题,首次碰到估计大多都比较懵吧,很多接口都是json或者xml或者plain text格式返回的,什么是application/octet-stream?

    查看RestTemplate源代码,一路跟踪下去会发现HttpMessageConverterExtractor类的extractData方法有个解析应答及反序列化逻辑,如果不成功,抛出的异常信息和上述一致:

    @Override
        @SuppressWarnings({"unchecked", "rawtypes", "resource"})
        public T extractData(ClientHttpResponse response) throws IOException {
            MessageBodyClientHttpResponseWrapper responseWrapper = new MessageBodyClientHttpResponseWrapper(response);
            if (!responseWrapper.hasMessageBody() || responseWrapper.hasEmptyMessageBody()) {
                return null;
            }
            MediaType contentType = getContentType(responseWrapper);
    
            try {
                for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
                    if (messageConverter instanceof GenericHttpMessageConverter) {
                        GenericHttpMessageConverter<?> genericMessageConverter =
                                (GenericHttpMessageConverter<?>) messageConverter;
                        if (genericMessageConverter.canRead(this.responseType, null, contentType)) {
                            if (logger.isDebugEnabled()) {
                                logger.debug("Reading [" + this.responseType + "] as "" +
                                        contentType + "" using [" + messageConverter + "]");
                            }
                            return (T) genericMessageConverter.read(this.responseType, null, responseWrapper);
                        }
                    }
                    if (this.responseClass != null) {
                        if (messageConverter.canRead(this.responseClass, contentType)) {
                            if (logger.isDebugEnabled()) {
                                logger.debug("Reading [" + this.responseClass.getName() + "] as "" +
                                        contentType + "" using [" + messageConverter + "]");
                            }
                            return (T) messageConverter.read((Class) this.responseClass, responseWrapper);
                        }
                    }
                }
            }
            catch (IOException | HttpMessageNotReadableException ex) {
                throw new RestClientException("Error while extracting response for type [" +
                        this.responseType + "] and content type [" + contentType + "]", ex);
            }
    
            throw new RestClientException("Could not extract response: no suitable HttpMessageConverter found " +
                    "for response type [" + this.responseType + "] and content type [" + contentType + "]");
        }
    
    HttpMessageConverterExtractor.extractData
    View Code

    StackOverflow上的解决的示例代码可以接受,但是并不准确,常见的MIMEType都应该加进去,贴一下我认为正确的代码:

    package com.power.demo.restclient.config;
    
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.google.common.collect.Lists;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.web.client.RestTemplateBuilder;
    import org.springframework.context.annotation.Bean;
    import org.springframework.http.MediaType;
    import org.springframework.http.converter.*;
    import org.springframework.http.converter.cbor.MappingJackson2CborHttpMessageConverter;
    import org.springframework.http.converter.feed.AtomFeedHttpMessageConverter;
    import org.springframework.http.converter.feed.RssChannelHttpMessageConverter;
    import org.springframework.http.converter.json.GsonHttpMessageConverter;
    import org.springframework.http.converter.json.JsonbHttpMessageConverter;
    import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
    import org.springframework.http.converter.smile.MappingJackson2SmileHttpMessageConverter;
    import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter;
    import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter;
    import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter;
    import org.springframework.http.converter.xml.SourceHttpMessageConverter;
    import org.springframework.stereotype.Component;
    import org.springframework.util.ClassUtils;
    import org.springframework.web.client.RestTemplate;
    
    import java.util.Arrays;
    import java.util.List;
    
    @Component
    public class RestTemplateConfig {
    
        private static final boolean romePresent = ClassUtils.isPresent("com.rometools.rome.feed.WireFeed", RestTemplate
                .class.getClassLoader());
        private static final boolean jaxb2Present = ClassUtils.isPresent("javax.xml.bind.Binder", RestTemplate.class.getClassLoader());
        private static final boolean jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", RestTemplate.class.getClassLoader()) && ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", RestTemplate.class.getClassLoader());
        private static final boolean jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", RestTemplate.class.getClassLoader());
        private static final boolean jackson2SmilePresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.smile.SmileFactory", RestTemplate.class.getClassLoader());
        private static final boolean jackson2CborPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.cbor.CBORFactory", RestTemplate.class.getClassLoader());
        private static final boolean gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", RestTemplate.class.getClassLoader());
        private static final boolean jsonbPresent = ClassUtils.isPresent("javax.json.bind.Jsonb", RestTemplate.class.getClassLoader());
    
        // 启动的时候要注意,由于我们在服务中注入了RestTemplate,所以启动的时候需要实例化该类的一个实例
        @Autowired
        private RestTemplateBuilder builder;
    
        @Autowired
        private ObjectMapper objectMapper;
    
        // 使用RestTemplateBuilder来实例化RestTemplate对象,spring默认已经注入了RestTemplateBuilder实例
        @Bean
        public RestTemplate restTemplate() {
    
            RestTemplate restTemplate = builder.build();
    
            List<HttpMessageConverter<?>> messageConverters = Lists.newArrayList();
            MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
            converter.setObjectMapper(objectMapper);
    
            //不加会出现异常
            //Could not extract response: no suitable HttpMessageConverter found for response type [class ]
    
            MediaType[] mediaTypes = new MediaType[]{
                    MediaType.APPLICATION_JSON,
                    MediaType.APPLICATION_OCTET_STREAM,
    
                    MediaType.APPLICATION_JSON_UTF8,
                    MediaType.TEXT_HTML,
                    MediaType.TEXT_PLAIN,
                    MediaType.TEXT_XML,
                    MediaType.APPLICATION_STREAM_JSON,
                    MediaType.APPLICATION_ATOM_XML,
                    MediaType.APPLICATION_FORM_URLENCODED,
                    MediaType.APPLICATION_PDF,
            };
    
            converter.setSupportedMediaTypes(Arrays.asList(mediaTypes));
    
            //messageConverters.add(converter);
            if (jackson2Present) {
                messageConverters.add(converter);
            } else if (gsonPresent) {
                messageConverters.add(new GsonHttpMessageConverter());
            } else if (jsonbPresent) {
                messageConverters.add(new JsonbHttpMessageConverter());
            }
    
            messageConverters.add(new FormHttpMessageConverter());
    
            messageConverters.add(new ByteArrayHttpMessageConverter());
            messageConverters.add(new StringHttpMessageConverter());
            messageConverters.add(new ResourceHttpMessageConverter(false));
            messageConverters.add(new SourceHttpMessageConverter());
            messageConverters.add(new AllEncompassingFormHttpMessageConverter());
            if (romePresent) {
                messageConverters.add(new AtomFeedHttpMessageConverter());
                messageConverters.add(new RssChannelHttpMessageConverter());
            }
    
            if (jackson2XmlPresent) {
                messageConverters.add(new MappingJackson2XmlHttpMessageConverter());
            } else if (jaxb2Present) {
                messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
            }
    
    
            if (jackson2SmilePresent) {
                messageConverters.add(new MappingJackson2SmileHttpMessageConverter());
            }
    
            if (jackson2CborPresent) {
                messageConverters.add(new MappingJackson2CborHttpMessageConverter());
            }
    
            restTemplate.setMessageConverters(messageConverters);
    
            return restTemplate;
        }
    
    }
    
    RestTemplateConfig
    View Code

    看到上面的代码,再对比一下RestTemplate内部实现,就知道我参考了RestTemplate的源码,有洁癖的人可能会说这一坨代码有点啰嗦,上面那一堆static final的变量和messageConverters填充数据方法,暴露了RestTemplate的实现,如果RestTemplate修改了,这里也要改,非常不友好,而且看上去一点也不OO。

    经过分析,RestTemplateBuilder.build()构造了RestTemplate对象,只要将内部MappingJackson2HttpMessageConverter修改一下支持的MediaType即可,RestTemplate的messageConverters字段虽然是private final的,我们依然可以通过反射修改之,改进后的代码如下:

    package com.power.demo.restclient.config;
    
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.google.common.collect.Lists;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.web.client.RestTemplateBuilder;
    import org.springframework.context.annotation.Bean;
    import org.springframework.http.MediaType;
    import org.springframework.http.converter.HttpMessageConverter;
    import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
    import org.springframework.stereotype.Component;
    import org.springframework.web.client.RestTemplate;
    
    import java.lang.reflect.Field;
    import java.util.Arrays;
    import java.util.List;
    import java.util.Optional;
    import java.util.stream.Collectors;
    
    @Component
    public class RestTemplateConfig {
    
        // 启动的时候要注意,由于我们在服务中注入了RestTemplate,所以启动的时候需要实例化该类的一个实例
        @Autowired
        private RestTemplateBuilder builder;
    
        @Autowired
        private ObjectMapper objectMapper;
    
        // 使用RestTemplateBuilder来实例化RestTemplate对象,spring默认已经注入了RestTemplateBuilder实例
        @Bean
        public RestTemplate restTemplate() {
    
            RestTemplate restTemplate = builder.build();
    
            List<HttpMessageConverter<?>> messageConverters = Lists.newArrayList();
            MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
            converter.setObjectMapper(objectMapper);
    
            //不加可能会出现异常
            //Could not extract response: no suitable HttpMessageConverter found for response type [class ]
    
            MediaType[] mediaTypes = new MediaType[]{
                    MediaType.APPLICATION_JSON,
                    MediaType.APPLICATION_OCTET_STREAM,
    
                    MediaType.TEXT_HTML,
                    MediaType.TEXT_PLAIN,
                    MediaType.TEXT_XML,
                    MediaType.APPLICATION_STREAM_JSON,
                    MediaType.APPLICATION_ATOM_XML,
                    MediaType.APPLICATION_FORM_URLENCODED,
                    MediaType.APPLICATION_JSON_UTF8,
                    MediaType.APPLICATION_PDF,
            };
    
            converter.setSupportedMediaTypes(Arrays.asList(mediaTypes));
    
            try {
                //通过反射设置MessageConverters
                Field field = restTemplate.getClass().getDeclaredField("messageConverters");
    
                field.setAccessible(true);
    
                List<HttpMessageConverter<?>> orgConverterList = (List<HttpMessageConverter<?>>) field.get(restTemplate);
    
                Optional<HttpMessageConverter<?>> opConverter = orgConverterList.stream()
                        .filter(x -> x.getClass().getName().equalsIgnoreCase(MappingJackson2HttpMessageConverter.class
                                .getName()))
                        .findFirst();
    
                if (opConverter.isPresent() == false) {
                    return restTemplate;
                }
    
                messageConverters.add(converter);//添加MappingJackson2HttpMessageConverter
    
                //添加原有的剩余的HttpMessageConverter
                List<HttpMessageConverter<?>> leftConverters = orgConverterList.stream()
                        .filter(x -> x.getClass().getName().equalsIgnoreCase(MappingJackson2HttpMessageConverter.class
                                .getName()) == false)
                        .collect(Collectors.toList());
    
                messageConverters.addAll(leftConverters);
    
                System.out.println(String.format("【HttpMessageConverter】原有数量:%s,重新构造后数量:%s"
                        , orgConverterList.size(), messageConverters.size()));
    
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            restTemplate.setMessageConverters(messageConverters);
    
            return restTemplate;
        }
    
    }
    
    RestTemplateConfig
    View Code

    除了一个messageConverters字段,看上去我们不再关心RestTemplate那些外部依赖包和内部构造过程,果然干净简洁好维护了很多。

    3、乱码问题

    这个也是一个非常经典的问题。解决方案非常简单,找到HttpMessageConverter,看看默认支持的Charset。AbstractJackson2HttpMessageConverter是很多HttpMessageConverter的基类,默认编码为UTF-8:

    public abstract class AbstractJackson2HttpMessageConverter extends AbstractGenericHttpMessageConverter<Object> {
    
        public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
    
    }

    而StringHttpMessageConverter比较特殊,有人反馈过发生乱码问题由它默认支持的编码ISO-8859-1引起:

    /**
     * Implementation of {@link HttpMessageConverter} that can read and write strings.
     *
     * <p>By default, this converter supports all media types ({@code &#42;&#47;&#42;}),
     * and writes with a {@code Content-Type} of {@code text/plain}. This can be overridden
     * by setting the {@link #setSupportedMediaTypes supportedMediaTypes} property.
     *
     * @author Arjen Poutsma
     * @author Juergen Hoeller
     * @since 3.0
     */
    public class StringHttpMessageConverter extends AbstractHttpMessageConverter<String> {
    
        public static final Charset DEFAULT_CHARSET = StandardCharsets.ISO_8859_1;
    
        /**
         * A default constructor that uses {@code "ISO-8859-1"} as the default charset.
         * @see #StringHttpMessageConverter(Charset)
         */
        public StringHttpMessageConverter() {
            this(DEFAULT_CHARSET);
        }
    
    }
    View Code

    如果在使用过程中发生乱码,我们可以通过方法设置HttpMessageConverter支持的编码,常用的有UTF-8、GBK等。

    4、反序列化异常

    这是开发过程中容易碰到的又一个问题。因为Java的开源框架和工具类非常之多,而且版本更迭频繁,所以经常发生一些意想不到的坑。

    以joda time为例,joda time是流行的java时间和日期框架,但是如果你的接口对外暴露joda time的类型,比如DateTime,那么接口调用方(同构和异构系统)可能会碰到序列化难题,反序列化时甚至直接抛出如下异常:

    org.springframework.http.converter.HttpMessageConversionException: Type definition error: [simple type, class org.joda.time.Chronology]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `org.joda.time.Chronology` (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
     at [Source: (PushbackInputStream);

    我在前厂就碰到过,可以参考这里,后来为了调用方便,改回直接暴露Java的Date类型。

    当然解决的方案不止这一种,可以使用jackson支持自定义类的序列化和反序列化的方式。在精度要求不是很高的系统里,实现简单的DateTime自定义序列化:

    package com.power.demo.util;
    
    
    import com.fasterxml.jackson.core.JsonGenerator;
    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.databind.JsonSerializer;
    import com.fasterxml.jackson.databind.SerializerProvider;
    import org.joda.time.DateTime;
    import org.joda.time.format.DateTimeFormat;
    import org.joda.time.format.DateTimeFormatter;
    
    import java.io.IOException;
    
    /**
     * 在默认情况下,jackson会将joda time序列化为较为复杂的形式,不利于阅读,并且对象较大。
     * <p>
     * JodaTime 序列化的时候可以将datetime序列化为字符串,更容易读
     **/
    public class DateTimeSerializer extends JsonSerializer<DateTime> {
    
        private static DateTimeFormatter dateFormatter = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss");
    
        @Override
        public void serialize(DateTime value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
            jgen.writeString(value.toString(dateFormatter));
        }
    }
    View Code

    以及DateTime反序列化:

    package com.power.demo.util;
    
    import com.fasterxml.jackson.core.JsonParser;
    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.databind.DeserializationContext;
    import com.fasterxml.jackson.databind.JsonDeserializer;
    import com.fasterxml.jackson.databind.JsonNode;
    import org.joda.time.DateTime;
    import org.joda.time.format.DateTimeFormat;
    import org.joda.time.format.DateTimeFormatter;
    
    import java.io.IOException;
    
    /**
     * JodaTime 反序列化将字符串转化为datetime
     **/
    public class DatetimeDeserializer extends JsonDeserializer<DateTime> {
    
        private static DateTimeFormatter dateFormatter = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss");
    
        @Override
        public DateTime deserialize(JsonParser jp, DeserializationContext context) throws IOException, JsonProcessingException {
            JsonNode node = jp.getCodec().readTree(jp);
            String s = node.asText();
            DateTime parse = DateTime.parse(s, dateFormatter);
            return parse;
        }
    }
    View Code

    最后可以在RestTemplateConfig类中对常见调用问题进行汇总处理,可以参考如下:

    package com.power.demo.restclient.config;
    
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.databind.module.SimpleModule;
    import com.google.common.collect.Lists;
    import com.power.demo.util.DateTimeSerializer;
    import com.power.demo.util.DatetimeDeserializer;
    import org.joda.time.DateTime;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.web.client.RestTemplateBuilder;
    import org.springframework.context.annotation.Bean;
    import org.springframework.http.MediaType;
    import org.springframework.http.converter.HttpMessageConverter;
    import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
    import org.springframework.stereotype.Component;
    import org.springframework.web.client.RestTemplate;
    
    import java.lang.reflect.Field;
    import java.util.Arrays;
    import java.util.List;
    import java.util.Optional;
    import java.util.stream.Collectors;
    
    @Component
    public class RestTemplateConfig {
    
        // 启动的时候要注意,由于我们在服务中注入了RestTemplate,所以启动的时候需要实例化该类的一个实例
        @Autowired
        private RestTemplateBuilder builder;
    
        @Autowired
        private ObjectMapper objectMapper;
    
        // 使用RestTemplateBuilder来实例化RestTemplate对象,spring默认已经注入了RestTemplateBuilder实例
        @Bean
        public RestTemplate restTemplate() {
    
            RestTemplate restTemplate = builder.build();
    
            //注册model,用于实现jackson joda time序列化和反序列化
            SimpleModule module = new SimpleModule();
            module.addSerializer(DateTime.class, new DateTimeSerializer());
            module.addDeserializer(DateTime.class, new DatetimeDeserializer());
            objectMapper.registerModule(module);
    
            List<HttpMessageConverter<?>> messageConverters = Lists.newArrayList();
            MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
            converter.setObjectMapper(objectMapper);
    
            //不加会出现异常
            //Could not extract response: no suitable HttpMessageConverter found for response type [class ]
            MediaType[] mediaTypes = new MediaType[]{
                    MediaType.APPLICATION_JSON,
                    MediaType.APPLICATION_OCTET_STREAM,
    
                    MediaType.TEXT_HTML,
                    MediaType.TEXT_PLAIN,
                    MediaType.TEXT_XML,
                    MediaType.APPLICATION_STREAM_JSON,
                    MediaType.APPLICATION_ATOM_XML,
                    MediaType.APPLICATION_FORM_URLENCODED,
                    MediaType.APPLICATION_JSON_UTF8,
                    MediaType.APPLICATION_PDF,
            };
    
            converter.setSupportedMediaTypes(Arrays.asList(mediaTypes));
    
            try {
                //通过反射设置MessageConverters
                Field field = restTemplate.getClass().getDeclaredField("messageConverters");
    
                field.setAccessible(true);
    
                List<HttpMessageConverter<?>> orgConverterList = (List<HttpMessageConverter<?>>) field.get(restTemplate);
    
                Optional<HttpMessageConverter<?>> opConverter = orgConverterList.stream()
                        .filter(x -> x.getClass().getName().equalsIgnoreCase(MappingJackson2HttpMessageConverter.class
                                .getName()))
                        .findFirst();
    
                if (opConverter.isPresent() == false) {
                    return restTemplate;
                }
    
                messageConverters.add(converter);//添加MappingJackson2HttpMessageConverter
    
                //添加原有的剩余的HttpMessageConverter
                List<HttpMessageConverter<?>> leftConverters = orgConverterList.stream()
                        .filter(x -> x.getClass().getName().equalsIgnoreCase(MappingJackson2HttpMessageConverter.class
                                .getName()) == false)
                        .collect(Collectors.toList());
    
                messageConverters.addAll(leftConverters);
    
                System.out.println(String.format("【HttpMessageConverter】原有数量:%s,重新构造后数量:%s"
                        , orgConverterList.size(), messageConverters.size()));
    
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            restTemplate.setMessageConverters(messageConverters);
    
            return restTemplate;
        }
    
    }
    View Code

    目前良好地解决了RestTemplate常见调用问题,而且不需要你写RestTemplate帮助工具类了。

    原文地址:https://www.cnblogs.com/jeffwongishandsome/archive/2018/05/17/8995562.html

  • 相关阅读:
    MyEclipse优化设置(最详细版本)
    报错:java.net.bindexception: address already in use: jvm_bind:8080
    java.net.BindException: Address already in use: JVM_Bind
    MyEclipse总是quick update解决办法
    【EJB学习笔记】——EJB开发环境搭建(Eclipse集成JBoss)
    对EJB2.1几种接口的认识
    免安装PostgreSQL启动服务及创建数据库
    git使用教程5-pycharm修改代码后提交到代码仓库
    git使用教程4-pycharm拉取git仓库项目代码
    git使用教程3-解决github网页打开慢的问题
  • 原文地址:https://www.cnblogs.com/bjlhx/p/10538434.html
Copyright © 2011-2022 走看看