Spring Boot 發起 HTTP 請求
起步
新年目標 Spring Cloud
開始實施,開啟慕課網。
剛學了一章,大體就是呼叫中國天氣網的 api
,使用 Spring Boot
構建自己的天氣預報系統,然後使用 Spring Cloud
,一步一步使用微服務的思想來演進架構。
小目標
昨天去百度搶了新年紅包,感嘆百度的高併發做的也是如此優秀。
阿里的雙十一,百度的新年。(發現了二者的共同點,可能也是解決併發的一種思路,併發的時候只允許增加資料。)
感嘆歸感嘆,期望著學習完 Spring Cloud
也能設計出優秀的架構,解決併發的一些問題。
遇到的問題
學習時也跟著課程進行編碼,講師講的非常好,但是本課程的重點是後面的微服務架構,所以前面的功能有一些瑕疵,特此提出自己的實現,供大家學習交流。
功能描述
最初的功能很簡單,因為後臺是沒有任何資料的,所以前臺有請求,就直接去天氣網要資料,然後再返回去。
資料序列化問題
這是天氣網 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:有拼音; ganmao
、 wendu
。
缺點2:名稱不一致;理論上來說 yesterday
與 forecast
應該是同一個實體,都表示一天的天氣情況,只是名稱不同。但是在 yesterday
中,風向和風力是 fx
和 fl
,在 forecast
中,名稱卻是 fengli
、 fengxiang
。
解決此問題,想到的思路就是使用 jackson
進行序列化與反序列化時進行配置的一些註解。
最初使用此種方法實現:
@JsonProperty("wendu") private Float temperature;
一個物件中的名字,一個 json
資料中的名字。
可以實現,但是不好。
舉個例子,天氣 api
返回給我 wendu
,添加了 @JsonProperty
,然後 wendu
就繫結到了 temperature
上,但是如果我前臺再返回該物件,序列化後生成的名稱還是 wendu
。不好!
目標是實現,反序列化時:從 wendu
能繫結到我的 temperature
,序列化時直接使用我的欄位名。
get、set嘗試
猜測是不是和 get
、 set
方法有關。
就把 @JsonProperty("wendu")
新增到 set
方法上,發現並沒有用。
JsonAlias
後來經過查詢,原來是註解用錯了,此種情況應使用別名。
關於 JsonProperty
和 JsonAlias
的詳細講解,請參考 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
工具類構造的,此處使用 Apache
的 Http
元件構造,以支援更多的資料格式。
implementation 'org.apache.httpcomponents:httpclient'
同時去除了預設的對 String
的 Http
訊息轉換器,預設的轉換器使用的不是 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
寫得都特別好。