转载

Spring Boot 发起 HTTP 请求

起步

新年目标 Spring Cloud 开始实施,打开慕课网。

Spring Boot 发起 HTTP 请求

刚学了一章,大体就是调用中国天气网的 api ,使用 Spring Boot 构建自己的天气预报系统,然后使用 Spring Cloud ,一步一步使用微服务的思想来演进架构。

小目标

昨天去百度抢了新年红包,感叹百度的高并发做的也是如此优秀。

阿里的双十一,百度的新年。(发现了二者的共同点,可能也是解决并发的一种思路,并发的时候只允许增加数据。)

感叹归感叹,期望着学习完 Spring Cloud 也能设计出优秀的架构,解决并发的一些问题。

遇到的问题

学习时也跟着课程进行编码,讲师讲的非常好,但是本课程的重点是后面的微服务架构,所以前面的功能有一些瑕疵,特此提出自己的实现,供大家学习交流。

功能描述

最初的功能很简单,因为后台是没有任何数据的,所以前台有请求,就直接去天气网要数据,然后再返回去。

Spring Boot 发起 HTTP 请求

数据序列化问题

这是天气网 api 返回来的数据格式,乍一看没啥毛病。

{
    "data": {
        "yesterday": {
            "date": "4日星期一", 
            "high": "高温 26℃", 
            "fx": "无持续风向", 
            "low": "低温 18℃", 
            "fl": "<![CDATA[<3级]]>", 
            "type": "多云"
        }, 
        "city": "深圳", 
        "forecast": [
            {
                "date": "5日星期二", 
                "high": "高温 25℃", 
                "fengli": "<![CDATA[<3级]]>", 
                "low": "低温 18℃", 
                "fengxiang": "无持续风向", 
                "type": "多云"
            }, 
            {
                "date": "6日星期三", 
                "high": "高温 26℃", 
                "fengli": "<![CDATA[<3级]]>", 
                "low": "低温 17℃", 
                "fengxiang": "无持续风向", 
                "type": "多云"
            }, 
            {
                "date": "7日星期四", 
                "high": "高温 27℃", 
                "fengli": "<![CDATA[<3级]]>", 
                "low": "低温 18℃", 
                "fengxiang": "无持续风向", 
                "type": "多云"
            }, 
            {
                "date": "8日星期五", 
                "high": "高温 26℃", 
                "fengli": "<![CDATA[<3级]]>", 
                "low": "低温 17℃", 
                "fengxiang": "无持续风向", 
                "type": "多云"
            }, 
            {
                "date": "9日星期六", 
                "high": "高温 24℃", 
                "fengli": "<![CDATA[<3级]]>", 
                "low": "低温 14℃", 
                "fengxiang": "无持续风向", 
                "type": "小雨"
            }
        ], 
        "ganmao": "相对今天出现了较大幅度降温,较易发生感冒,体质较弱的朋友请注意适当防护。", 
        "wendu": "23"
    }, 
    "status": 1000, 
    "desc": "OK"
}

缺点1:有拼音; ganmaowendu

缺点2:名称不一致;理论上来说 yesterdayforecast 应该是同一个实体,都表示一天的天气情况,只是名称不同。但是在 yesterday 中,风向和风力是 fxfl ,在 forecast 中,名称却是 fenglifengxiang

解决此问题,想到的思路就是使用 jackson 进行序列化与反序列化时进行配置的一些注解。

最初使用此种方法实现:

@JsonProperty("wendu")
private Float temperature;

一个对象中的名字,一个 json 数据中的名字。

可以实现,但是不好。

举个例子,天气 api 返回给我 wendu ,添加了 @JsonProperty ,然后 wendu 就绑定到了 temperature 上,但是如果我前台再返回该对象,序列化后生成的名称还是 wendu 。不好!

目标是实现,反序列化时:从 wendu 能绑定到我的 temperature ,序列化时直接使用我的字段名。

get、set尝试

猜测是不是和 getset 方法有关。

就把 @JsonProperty("wendu") 添加到 set 方法上,发现并没有用。

JsonAlias

后来经过查询,原来是注解用错了,此种情况应使用别名。

关于 JsonPropertyJsonAlias 的详细讲解,请参考 Jackson @JsonProperty and @JsonAlias Example 。

@JsonAlias("wendu")
private Float temperature;

同时,可以应用多个别名:

@JsonAlias({"fengli", "fl"})
private String windForce;

发起请求

发起请求的示例代码,供以后参考。

@Autowired
private RestTemplate restTemplate;

@Override
public Weather getWeatherByCityName(String cityName) {
    return this.getWeatherByUrl(BASE_URL + "?" + CITY_NAME + "=" + cityName)
            .getData();
}

private Response getWeatherByUrl(String url) {
    // 发起Get请求
    ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);
    // 如果状态码非200, 抛异常
    if (response.getStatusCodeValue() != 200) {
        throw new YunzhiNetworkException("数据请求失败");
    }
    // 实例化对象映射对象
    ObjectMapper mapper = new ObjectMapper();
    // 初始化响应数据
    Response data;
    // 从字符串转换为Response对象
    try {
        data = mapper.readValue(response.getBody(), Response.class);
    } catch (IOException e) {
        throw new YunzhiIOException("json数据转换失败");
    }
    // 返回
    return data;
}

RestTemplate配置

这里与正常的 RestTemplate 构建有些不同,通常的 RestTemplate 是使用 Spring 工具类构造的,此处使用 ApacheHttp 组件构造,以支持更多的数据格式。

implementation 'org.apache.httpcomponents:httpclient'

同时去除了默认的对 StringHttp 消息转换器,默认的转换器使用的不是 UTF-8 编码。

讲师原文章: Spring RestTemplate 调用天气预报接口乱码的解决

@Configuration
public class BeanConfiguration {

    @Bean
    public RestTemplate restTemplate() {
        // 使用Apache HttpClient构建RestTemplate, 支持的比Spring自带的更多
        RestTemplate restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory());
        // 去除默认的String转换器
        restTemplate.getMessageConverters().removeIf(converter -> converter instanceof StringHttpMessageConverter);
        // 添加自定义的String转换器, 支持UTF-8
        restTemplate.getMessageConverters()
                .add(new StringHttpMessageConverter(StandardCharsets.UTF_8));
        return restTemplate;
    }
}

更完善的单元测试

同时,在编写单元测试的时候,看了一篇关于 AssertJ 的文章。 Testing with AssertJ assertions - Tutorial

之前学 Junit5 的时候,觉得这个东西挺好使的啊?为什么被开源社区抛弃而使用 AssertJ 呢?

原来之前用的断言都太简单,其实 AssertJ 远比我们使用的更强大。

@Test
public void getWeatherByCityName() throws Exception {
    final String cityName = "深圳";
    MvcResult mvcResult = this.mockMvc
            .perform(MockMvcRequestBuilders.get(BASE_URL + "/cityName/" + cityName))
            .andExpect(MockMvcResultMatchers.status().isOk())
            .andReturn();
    String json = mvcResult.getResponse().getContentAsString();
    Assertions.assertThat(json)
            .contains("cityName", "cold", "temperature", "windDirection", "windForce")
            .doesNotContain("ganmao", "wendu", "fx", "fl", "fengxiang", "fengli");
}

总结

多看英文文章, Tutorial 写得都特别好。

原文  https://segmentfault.com/a/1190000018101800
正文到此结束
Loading...