spring组件 RestTemplate + @ResponseBody 使用心得

刚刚给公司两个系统写了一个中间件,用于同步一些数据,用了 RestTemplate 这个家伙,确实相当友好,也简化了 http 请求,但是我也走了很多弯路,也被网上的那些各种不完整的博客坑惨了,下面说重点:

一. RestTemplate 简单配置

@Configuration
public class RestTemplateConfig {
    @Bean
    @ConditionalOnMissingBean({ RestOperations.class, RestTemplate.class })
    public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
        RestTemplate restTemplate = new RestTemplate(factory);
        <!--使用 utf-8 编码集的 conver 替换默认的 conver(默认的 string conver 的编码集为"ISO-8859-1")-->
        List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters();
        Iterator<HttpMessageConverter<?>> iterator = messageConverters.iterator();
        <!--!!!!!!!!!!严重警告,一定不要这么配置,这样就改变的convert的顺序,当接口采用@requestbody接收的时候,会报 parse error-->
        <!--while (iterator.hasNext()) {-->
        <!--    HttpMessageConverter<?> converter = iterator.next();-->
        <!--    if (converter instanceof StringHttpMessageConverter) {-->
        <!--        iterator.remove();-->
        <!--    }-->
        <!--}-->
        <!--messageConverters.add(new StringHttpMessageConverter(Charset.forName("UTF-8")));-->
        
        <!--正确姿势-->
        while (iterator.hasNext()) {
            HttpMessageConverter<?> converter = iterator.next();
            if (converter instanceof StringHttpMessageConverter) {
                ((StringHttpMessageConverter) converter).setDefaultCharset(Charset.forName("utf-8"));
            }
        }
        return restTemplate;
    }

    @Bean
    @ConditionalOnMissingBean({ClientHttpRequestFactory.class})
    public ClientHttpRequestFactory simpleClientHttpRequestFactory() {
        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
        factory.setReadTimeout(15000);
        factory.setConnectTimeout(15000);
        return factory;
    }
}

二. 依赖注入

@Autowired
private RestTemplate restTemplate;

有了上面这些,我想需要写工具类,直接 new 一个来使用,或者是写 xml 配置文件来依赖注入都不成问题了

三. 使用 1.第一步请先看看官方文档,别再网上搜,网上的东西都是针对使用者的环境能用,到你那里不一定就能用的

官方文档:>https://docs.spring.io/spring/docs/5.0.2.RELEASE/spring-framework-reference/integration.html#rest-client-access

2.@ResponseBody 返回的是 json 字符串,但是 RestTemplate 在 postForObject 的时候,如果返回参数里面包含 List 等组数格式字符串,同时这个数组你用了 object 对象来接收,就会出现结果只有 list 的最后一个值,前面的值都丢失了。如果用了 List 的格式接收就没有这个问题。 解决方法:

import com.alibaba.fastjson.JSON;
import springfox.documentation.spring.web.json.Json;

@RestController
@RequestMapping("/customer")
public class CustomerController {

    @Autowired
    private RestTemplate restTemplate;
    
    @PostMapping("/push")
    //接收参数请设置为 json 接收:即给接收参数加上 @RequestBody  注解,可以简化 RestTemplate 传递的参数封装
    public Object push(@RequestBody List<Customer> customers) {
        //将返回对象包装为 Json 对象返回,而不是 json 字符串
        return new Json(JSON.toJSONString(Object object));
    }

3.完整的 RestTemplate 请求:

//这里用的是 postForObject ,其他请求方式大同小异
//因为前面接收使用了 @RequestBody  注解方式,以 json 格式接收,这里的请求参数 object 不需要任何封装,直接使用就行了
Object object = new Object();
ResponseResult entity = 
    restTemplate.postForObject("http://192.168.82.15:9900/pool/customer/list", object , ResponseResult.class);

//此时 entity.getData() 实际上是一个 json 字符串
//转化对象内的 Object 对象
Page<Customer> ids = com.alibaba.fastjson.JSONObject.parseObject(entity.getData().toString(), Page.class);
//转化对象内的 Array 对象
List<Long> ids = com.alibaba.fastjson.JSONArray.parseArray(entity.getData().toString(), Long.class);

System.out.println(ids);

return ids;
我的 ResponseResult 对象:
@ApiModel("响应结果对象")
public class ResponseResult implements Serializable {
    @ApiModelProperty("响应码")
    private Integer code = ResponseCode.SUCCESS;
    @ApiModelProperty("响应消息")
    private String msg;
    @ApiModelProperty("响应数据")
    private Object data;
    @ApiModelProperty("响应时间")
    private Date time = new Date();
    ...
}

四. 双重序列化问题:

当使 RestTemplate 返回的参数,如:我上面包装的对象 ResponseResult 的 Data ,有可能是比较复杂的对象,Data 接收到的参数就会直接以 json 串的形式保留下来,如果此时不对该 json 串做正确的反序列化,直接将该 json 串通过接口再发送给其他调用者。问题来了,第三方接到的 Data 会以带转义符的 json 串呈现,正确的做法是:

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sword.ttttttttttt.entity.Student;

import java.io.IOException;
import java.util.ArrayList;

public class TestMain {

    public static void main(String[] args) {
        Student s = new Student();
        s.setName("abc");
        s.setId(1);
        s.setAge(null);
        s.setList(new ArrayList<String>());

        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setSerializationInclusion(JsonInclude.Include.ALWAYS);
        try {
            //第一次序列化
            String ss = objectMapper.writeValueAsString(s);
            //第二次序列化
            String string = objectMapper.writeValueAsString(ss);
            //反序列化第二次的序列化,而不是用replace或者substring之类的方法来解析
            String student = objectMapper.readValue(string, String.class);
            //反序列化第一次的序列化,得到正确的对象
            Student object = objectMapper.readValue(student, Student.class);

        } catch (JsonProcessingException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

ps:跟踪了一下源代码,没有找到最初的数据是从哪儿获取出来,又是从哪儿开始反序列化的,如果有知道的朋友,还请留步!!!

猜你喜欢

转载自my.oschina.net/ss6/blog/1625261