讓http請求的呼叫更優雅
概述
當我們提到java呼叫http請求時,我們想到的是HttpClient或是內建的HttpUrlConnention。 然後會寫下如下一串的程式碼訪問http介面:
HttpClient client = new HttpClient();
client.getHostConfiguration().setProxy("127.0.0.1", 8888);
client.getHostConfiguration().setHost("bl.ocks.org", 80, "http");
GetMethod getMethod = new GetMethod("/mbostock/raw/4090846/us-congress-113.json");
client.executeMethod(getMethod);
//列印伺服器返回的狀態
System.out.println(getMethod.getStatusLine().getStatusCode());
if(getMethod.getStatusLine().getStatusCode() == 200){
//列印結果頁面
String response = new String(getMethod.getResponseBodyAsString().getBytes("8859_1"));
//列印返回的資訊
System.out.println(response);
}
getMethod.releaseConnection();
複製程式碼
可是我們是不是有一種更優雅的方式呢?類似於MyBatis,通過一定的配置,然後在需要請求http介面的時候只需要呼叫一個介面函式便可以完成上述程式碼的工作。
這就是HttpFetch的初衷,讓http請求的呼叫更優雅。
下載
git clone https://github.com/youzan/httpfetch.git
複製程式碼
QuickStart
https://github.com/youzan/httpfetch/wiki/QuickStart
物件
- ParameterResolver:api引數解析類,自帶的可以對陣列、bean、簡單型別等引數進行解析並封裝成Get、Post、Form等型別請求的引數。也可以通過Url註解靈活定義api介面的請求地址。
- Convertor:返回資料封裝類,自帶的僅支援簡單型別和JSON型別的資料進行封裝。通過擴充套件可以實現更多的轉換方式。
- Chain: 責任鏈模式,一層層對請求進行加工和處理。裡面比較重要的是ParameterResolverChain、GenerateResponseChain和ExecuteRequestChain。ParameterResolverChain負責對引數進行處理,GenerateResponseChain負責對返回結果進行處理,ExecuteRequestChain負責最後的請求傳送。
- ResourceReader: 配置資訊讀取類,負責對各元件單元的讀取並最終傳給HttpApiConfiguration類。
- HttpApiConfiguration: 負責對ResourceReader讀取後的配置資訊進行封裝,然後將配置資訊傳給HttpApiService類。
- HttpApiService: 負責最後的代理類生成和快取;
框架
-
初始化過程
初始化過程可以選擇spring和xml兩種。spring的方式直接將生成的代理類註冊到BeanDefinitionRegistry(可見HttpApiClassPathBeanDefinitionScanner原始碼),xml方式可以在沒有spring元件的情況下獨立執行(見單測MbostockApiUseXmlTest)。兩種方式都可以完成Chain、ParamterResolver和Convertor註冊。
-
請求處理流程 請求者發起請求時,會通過配置的各個Chain單元,一步一步的處理和封裝引數併傳送最終的Http請求,最後將返回的值進行封裝。
使用
Maven
<dependency>
<groupId>com.github.youzan</groupId>
<artifactId>http-fetch</artifactId>
<version>1.1.6</version>
</dependency>
複製程式碼
非spring呼叫
1.建立http-api.xml配置檔案:
<?xml version="1.0" encoding="UTF-8"?>
<setting>
<!-- 請求處理鏈 -->
<chains>
</chains>
<!-- 引數處理類 -->
<argumentResolvers>
</argumentResolvers>
<!-- 結果處理類 -->
<resultConvertors>
</resultConvertors>
<!-- api和url對映關係 -->
<aliases>
<alias key="mbostockApi.getUsCongress" value="https://bl.ocks.org/mbostock/raw/4090846/us-congress-113.json" />
</aliases>
</setting>
複製程式碼
2.編寫MbostockApi介面類:
package com.github.nezha.httpfetch.mbostock.api;
import com.github.nezha.httpfetch.HttpApi;
import com.github.nezha.httpfetch.Header;
import com.github.nezha.httpfetch.mbostock.vo.UsCongressResponseVo;
/**
* Created by daiqiang on 17/3/14.
*/
public interface MbostockApi {
@HttpApi(timeout = 1000, headers = {@Header(key="user-agent", value = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36")})
UsCongressResponseVo getUsCongress();
}
複製程式碼
3.編寫測試類:
SourceReader xmlReader = new XmlReader(Arrays.asList("httpapi.xml"));
HttpApiConfiguration configuration = new HttpApiConfiguration();
configuration.setSourceReaders(Arrays.asList(xmlReader));
configuration.init();
HttpApiService service = new HttpApiService(configuration);
service.init();
MbostockApi mbostockApi = service.getOrCreateService(MbostockApi.class);
UsCongressResponseVo responseVo = mbostockApi.getUsCongress();
System.out.println("type=="+responseVo.getType());
System.out.println("arcs->size=="+responseVo.getArcs().size());
System.out.println("objects->districts->bbox->size=="+responseVo.getObjects().getDistricts().getBbox().size());
System.out.println("objects->districts->type=="+responseVo.getObjects().getDistricts().getType());
System.out.println("objects->districts->geometries->size=="+responseVo.getObjects().getDistricts().getGeometries().size());
System.out.println("transform->scale=="+responseVo.getTransform().getScale());
System.out.println("transform->translate=="+responseVo.getTransform().getTranslate());
複製程式碼
以上就是非spring方式的呼叫
spring方式的呼叫
1.建立application-httpapi.xml檔案:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
">
<bean id="springReader" class="com.github.nezha.httpfetch.spring.SpringReader" >
<!-- 請求處理鏈 -->
<property name="chains" >
<list>
<bean class="com.github.nezha.httpfetch.bookworm.chains.BookWormTokenChain" />
</list>
</property>
<!-- 引數處理類 -->
<property name="parameterResolvers">
<list>
</list>
</property>
<!-- 結果處理類 -->
<property name="convertors">
<list>
</list>
</property>
<!-- api和url對映關係 -->
<property name="urlAlias">
<map>
<entry key="mbostockApi.getUsCongress" value="${mock.host}/mbostock/raw/4090846/us-congress-113.json" />
</map>
</property>
</bean>
<bean id="httpApiConfiguration" class="com.github.nezha.httpfetch.HttpApiConfiguration" init-method="init">
<property name="sourceReaders">
<list>
<ref bean="springReader" />
</list>
</property>
</bean>
<bean id="httpApiService" class="com.github.nezha.httpfetch.HttpApiService" init-method="init">
<constructor-arg index="0" ref="httpApiConfiguration" />
</bean>
<!-- http api代理註冊 -->
<bean class="com.github.nezha.httpfetch.spring.HttpApiScannerConfigurer">
<property name="basePackage" value="com.github.nezha.httpfetch.bookworm.api,com.github.nezha.httpfetch.mbostock.api,com.github.nezha.httpfetch.youzan.api" />
</bean>
</beans>
複製程式碼
2.編寫MbostockApi介面類:
package com.github.nezha.httpfetch.mbostock.api;
import com.github.nezha.httpfetch.HttpApi;
import com.github.nezha.httpfetch.Header;
import com.github.nezha.httpfetch.mbostock.vo.UsCongressResponseVo;
/**
* Created by daiqiang on 17/3/14.
*/
public interface MbostockApi {
@HttpApi(timeout = 1000, headers = {@Header(key="user-agent", value = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36")})
UsCongressResponseVo getUsCongress();
}
複製程式碼
3.編寫測試類:
public class MbostockApiTest extends BaseTest {
@Autowired
private MbostockApi mbostockApi;
@Test
public void test(){
UsCongressResponseVo responseVo = mbostockApi.getUsCongress();
System.out.println("type=="+responseVo.getType());
System.out.println("arcs->size=="+responseVo.getArcs().size());
System.out.println("objects->districts->bbox->size=="+responseVo.getObjects().getDistricts().getBbox().size());
System.out.println("objects->districts->type=="+responseVo.getObjects().getDistricts().getType());
System.out.println("objects->districts->geometries->size=="+responseVo.getObjects().getDistricts().getGeometries().size());
System.out.println("transform->scale=="+responseVo.getTransform().getScale());
System.out.println("transform->translate=="+responseVo.getTransform().getTranslate());
}
}
複製程式碼
URL對映
url的對映使用了三種方式:
1.使用xml進行配置:
<aliases>
<alias key="mbostockApi.getUsCongress" value="https://bl.ocks.org/mbostock/raw/4090846/us-congress-113.json" />
</aliases>
複製程式碼
2.使用註解方式:
package com.github.nezha.httpfetch.bookworm.api;
import com.github.nezha.httpfetch.Header;
import com.github.nezha.httpfetch.HttpApi;
import com.github.nezha.httpfetch.resolver.RequestBody;
import java.util.Map;
/**
* Created by daiqiang on 17/6/16.
*/
public interface AlarmJobApi {
@HttpApi(method = "POST", headers = @Header(url = "http://alert.s.qima-inc.com/api/v1/alert", key = "Content-type", value = "application/json"), timeout = 2000)
String alert(@RequestBody Map<String, Object> param);
}
複製程式碼
3.使用引數方式傳入:
@HttpApi(timeout = 1000, headers = {@Header(key="user-agent", value = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36")})
UsCongressResponseVo getUsCongress(@URL String url);
複製程式碼
測試類:
public void test_url_param(){
String url = "https://bl.ocks.org/mbostock/raw/4090846/us-congress-113.json";
UsCongressResponseVo responseVo = mbostockApi.getUsCongress(url);
System.out.println("type=="+responseVo.getType());
System.out.println("arcs->size=="+responseVo.getArcs().size());
System.out.println("objects->districts->bbox->size=="+responseVo.getObjects().getDistricts().getBbox().size());
System.out.println("objects->districts->type=="+responseVo.getObjects().getDistricts().getType());
System.out.println("objects->districts->geometries->size=="+responseVo.getObjects().getDistricts().getGeometries().size());
System.out.println("transform->scale=="+responseVo.getTransform().getScale());
System.out.println("transform->translate=="+responseVo.getTransform().getTranslate());
}
複製程式碼
引數封裝
1.Get請求引數: 使用QueryParam註解標記,並填寫引數的名稱
@HttpApi(timeout = 2000, url = "http://bookworm365.com/uploadImage")
@BookWormApi
UploadFileResponseVo uploadFile(@QueryParam("name") String name,
@QueryParam("n_value") String nValue);
複製程式碼
2.Post請求引數: 使用PostParam註解標記,並填寫引數的名稱
Map audit(@PostParam("advertisementId") Integer advertisementId);
複製程式碼
3.Form請求引數: 使用FormParam註解標記,並填寫引數的名稱
@HttpApi(timeout = 2000, url = "http://bookworm365.com/uploadImage")
@BookWormApi
UploadFileResponseVo uploadFile(@FormParam("file") File file,
@QueryParam("name") String name,
@QueryParam("n_value") String nValue);
複製程式碼
4.BeanParam註解使用: 當我們傳遞一個bean做為引數,但是希望對這個bean進行解析然後作為http請求引數時,我們可以使用BeanParam註解。
@HttpApi(timeout = 2000, url = "http://bookworm365.com/uploadImage")
@BookWormApi
UploadFileResponseVo uploadFile(@BeanParam @QueryParam UploadFileRequestVo requestVo);
複製程式碼
package com.github.nezha.httpfetch.bookworm.vo;
import com.alibaba.fastjson.annotation.JSONField;
import java.io.File;
public class UploadFileRequestVo {
@JSONField(name = "file")
private File file;
private String name;
@JSONField(name="n_value")
private String nValue;
public File getFile() {return file;}
public void setFile(File file) {this.file = file;}
public String getName() {return name;}
public void setName(String name) {this.name = name;}
public String getnValue() {return nValue;}
public void setnValue(String nValue) {this.nValue = nValue;}
}
複製程式碼
http的請求最終為:http://bookworm365.com/uploadImage?file=XXX&name=XXX&n_value=XXX
5.RequestBody註解使用: 當你需要傳遞訊息體給伺服器是,可以通過該註解。 例如我們想要傳遞一個application\json的請求:
@HttpApi(method = "POST",timeout = 2000,headers = {@Header(key = "Content-type", value = "application/json;charset=UTF-8")})
@WechatApi
WechatBaseResponseVo<AddCustomAudiencesResponseVo> add(@RequestBody AddCustomAudiencesRequestVo requestVo);
複製程式碼
結果封裝
結果封裝預設支援簡單型別和JSON兩種: 1.簡單型別,如果返回值是String、int、long等,api的返回物件可以直接指定對應類:
@HttpApi(timeout = 2000, url = "http://bookworm365.com/checkHeader")
@BookWormApi
String checkHeader();
複製程式碼
2.JSON,如果返回值是一個json字串,可以直接編寫對應的bean作為返回類,內部使用fastjson進行反序列化:
@HttpApi(timeout = 1000, headers = {@Header(key="user-agent", value = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36")})
UsCongressResponseVo getUsCongress();
複製程式碼
package com.github.nezha.httpfetch.mbostock.vo;
import com.alibaba.fastjson.annotation.JSONField;
import java.util.List;
public class UsCongressResponseVo {
@JSONField(name="type")
private String type;
@JSONField(name="objects")
private ObjectsVo objects;
@JSONField(name="arcs")
private List<List<List<Integer>>> arcs;
@JSONField(name="transform")
private TransformVo transform;
public String getType() {return type;}
public void setType(String type) {this.type = type;}
public ObjectsVo getObjects() {return objects;}
public void setObjects(ObjectsVo objects) {this.objects = objects;}
public List<List<List<Integer>>> getArcs() {return arcs;}
public void setArcs(List<List<List<Integer>>> arcs) {this.arcs = arcs;}
public TransformVo getTransform() {return transform;}
public void setTransform(TransformVo transform) {this.transform = transform;}
}
複製程式碼
package com.github.nezha.httpfetch.mbostock.vo;
import com.alibaba.fastjson.annotation.JSONField;
import com.github.nezha.httpfetch.BaseTest;
public class ObjectsVo {
@JSONField(name="districts")
private DistrictsVo districts;
public DistrictsVo getDistricts() {return districts;}
public void setDistricts(DistrictsVo districts) {this.districts = districts;}
}
複製程式碼
另外返回的類還支援泛型。
@HttpApi(timeout = 1000, headers = {@Header(key="user-agent", value = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36")})
UsCongressResponseVo<TransformVo> getUsCongress();
複製程式碼
重試策略
需要升級到1.2.0之後才可以使用。
HttpApi註解中增加了retry和retryPolicy兩個變數:
retry:重試次數;
retryPolicy:重試策略,預設為ConnectFailureRetryPolicy,超時和連線異常會進行重試;
自定義重試策略
類圖:
所有的重試策略需要繼承RetryPolicy介面,並實現needRetry函式。
/**
* 重試校驗介面
*/
public interface RetryPolicy {
/**
*
* @param result http請求結果
* @param retryTimes 重試次數
* @param remainRetryTimes 剩餘重試次數
* @return
*/
boolean needRetry(HttpResult result, int retryTimes, int remainRetryTimes);
}
複製程式碼
ConnectFailureRetryPolicy:
public class ConnectFailureRetryPolicy implements RetryPolicy {
private static final Logger LOGGER = LoggerFactory.getLogger(ConnectFailureRetryPolicy.class);
/**
* 如果是網路異常則重試
* @param result http請求結果
* @param retryTimes 重試次數
* @param remainRetryTimes 剩餘重試次數
* @return
*/
@Override
public boolean needRetry(HttpResult result, int retryTimes, int remainRetryTimes) {
Exception e = result.getException();
if(e instanceof SocketTimeoutException || e instanceof ConnectException){
LOGGER.info("超時重試: {}, 重試次數: {} 剩餘次數: {}", e, retryTimes, remainRetryTimes);
return true;
}
return false;
}
}
複製程式碼
實現完自己的重試策略後,只需要在HttpApi註解中設定retryPolicy的值就可以了。
更多示例可以在專案的test
目錄中檢視
開源協議
本專案基於 MIT協議,請自由地享受和參與開源。
貢獻
如果你有好的意見或建議,歡迎給我們提 [issue] 或 [PR],為優化 [http-fetch] 貢獻力量