HttpClient使用Annotation和Proxy
1. Object与Proxy
在数据库领域,通常使用ORM方式映射数据模型和数据库表,在XML领域也有OXM方式,将对象与数据模型映射。此外还有另外一种思路,就是通过代理的方式生成映射,例如mybatis等。
我们期望有一种映射模型,能够映射一套完整的Http请求,用户通过配置就可以完成Http请求的编写,分离业务逻辑和Http请求系统逻辑间的耦合。
使用Object模式,用户需要对每一个Http请求创建一个Model,使用Proxy方式,可以将同一类的Http请求在一个接口中定义,接口中的不同方法表示不同的Http请求。
对于Http请求,返回值主要有两种,一种是String(JSON、XML),一种是byte流(文件等),为了简化设计,只提供了String和byte[]两种类型的返回值。
2. 层次设计和内部逻辑
前面分别介绍了HttpClient、RestTemplate和Annotation+Proxy的用法,结合先前的介绍,可以得到如下关于HttpClient的设计模型:
如上图所示,当需要一个新的Http请求时,用户通过编写接口和在接口上标记相应的信息即可,不需要关心底层具体使用何种方式实现,屏蔽了业务逻辑和系统逻辑的细节。在运行时,MapperProxy创建代理,并实现了InvocationHandler接口,当外部调用具体接口时,会触发MapperProxy中实现的invoke方法,在invoke方法内部会创建MapperMethod,之后会调用其execute方法。MapperMethod负责分析Annotation的配置和值,组织相应的参数,并通过template将信息继续传递,template封装了HttpClient的请求,实现了MapperMethod层与HttpClient层间的隔离,如果最终HttpClient的实现方式被替换掉,也不会影响到代理层和业务逻辑层的代码实现。最终template调用RestClient,完成Http请求。RestClient中封装了一个多线程的HttpClient池,用于最终完成请求。
3. 关于Http请求的Interface and annotation设计
对于一个Http请求的参数主要是url、query、head、body。请求方式主要是Post、Get、Put、Head、Delete等。
我们通过举例子的方式,描述相应的主要功能。
例如,我们定义请求接口如下:
@URL("http://api.m.renren.com/api")
@ParamDefaults({
@ParamDefault(paramname="v",value="1.0",type=ParamType.BODY),
@ParamDefault(paramname="format",type=ParamType.BODY,value="JSON")})
publicinterface RenRenHttpDao {
@Post("/photos/uploadbin")
@ParamDefaults({
@ParamDefault(paramname="voice_rate",value="44100",type=ParamType.BODY),
@ParamDefault(paramname="default_album_switch",value="2",type=ParamType.BODY),
@ParamDefault(paramname="from",value="2080686",type=ParamType.BODY)
})
public String share(
@ParamBody("access_token") String accessToken,
@ParamBody("call_id") String callId,
@ParamBody("caption") String caption,
@ParamFile("data") HttpFile data,
@ParamFile("voicedata") HttpFile voiceData,
@ParamBody("voice_length") long voiceLength,
@ParamBody("voice_size") long voiceSize,
@ParamBody("sig") String sig
);
}
首先,@URL指明了这个接口内部所有方法要访问的web服务的地址公共部分,类上的@ParamDefaults定义接口内部所有方法都会使用到的默认参数。@Post、@Get、@Put、@Delete、@Header分别表示不同的请求类型,并且可以带有请求url的子路径。标记在方法上的@ParamDefaults表示这个方法调用过程中使用的默认参数。最后@ParamBody表明Http消息体内部需要承载的消息(@ParamFile是消息体内的文件消息)。
又比如:
@URL("https://api.weibo.com/2")
publicinterface WeiBoHttpDao {
@Get("/short_url/shorten.json")
public String getshortUrl(@ParamQuery("access_token") String accessToken,
@ParamQuery("source") long source,
@ParamQuery("url_long") String urlLong);
}
getshortUrl是访问微博的一个接口,根据接口的描述,最终访问的url应当是https://api.weibo.com/2/short_url/shorten.json @ParamQuery表示Url后面带的query参数,因此最终的访问应当是:
https://api.weibo.com/2/short_url/shorten.json?access_token=**& source=**&url_long=**
最后给出一个访问天气的接口:
@URL("http://m.weather.com.cn/data/{city}.html")
publicinterface WeatherDao {
@Get
String getWeather(@ParamUrl("city") int cityCode);
}
从接口上能够看出访问的url,@ParamUrl指明了Url中需要替换的信息。
我们应当如何使用接口能,答案很简单,如下是一个获取北京天气的例子:
WeatherDao weather = MapperProxy.newMapperProxy(WeatherDao.class, new DefaultHttpTemplate());//获取proxy
System.out.println(weather.getWeather(101010100));//调用方法
4. 与spring的结合
如果需要与spring结合可以实现Spring的FactoryBean接口,之后就可以通过xml方式配置Proxy。进一步工作是使用Scan扫描Proxy。