开放平台 SDK 设计实践!
SDK,大概的设计会分为三个模块,分别为:
- 数据传输模块:主要用于传输请求数据,本文使用http协议传输数据
- 序列化模块:用户序列化和反序列化数据,SDK是给客户使用应该尽可能的去适配客户的意愿,如客户首先的序列化方式为JSON那,作为使用方肯定是会希望SDK能支持JSON序列化和反序列化的
- 应用模块:主要是协调数据传输、序列化之间的工作,同时对数据做一些校验签名操作
基本功能
数据传输
数据传输协议使用较为通用的http协议
,一开始是想是否做一个通用的设计来支持其他的传输方式,如支持socket
保持长链接,后来想想没必要过度设计,如果要做到支持不同的传输协议,那么就得做很大一部分适配的编码,事实上可能支持http后以后都会保持不变,没有这个必要!
目前有很多开源的http
工具,如httpclient
、okhttp
还有URLConnection
的方式等,选择哪一种呢?好的方法是自己抽象出来一个接口,然后提供一个默认的实现类就好,这样就能保持一定的灵活性。
/** * http协议接口 */ public interface HttpClient { /** * 执行http post请求 * @param url * @param data * @param headers * @return */ String post(String url, byte[] data, Map> headers) throws HttpClientException; }
HttpClient
只定义了一个post
方法,满足需求即可(偷懒需求~),大部分开放平台的接口都是post
请求方式的,没必要把所有的http特性都给定义出来,一个post
就是干!
接下来就用HttpURLConnection
发起http请求吧:
package com.javaobj.sdk.http.impl; public class DefaultHttpClient implements HttpClient { public String post(String url, byte[] data, Map> headers) throws HttpClientException { URL targetUrl = null; try { targetUrl = new URL(url); } catch (MalformedURLException e) { throw new HttpClientException("访问的url链接不正确"); } try { HttpURLConnection connection = (HttpURLConnection) targetUrl.openConnection(); connection.setDoInput(true); connection.setDoOutput(true); connection.setRequestMethod("POST"); //设置讲求头 if(headers != null){ Map > tmp = new HashMap >(headers); for(Map.Entry > entry : tmp.entrySet()){ for(String value: entry.getValue()){ connection.setRequestProperty(entry.getKey(), value); } } } connection.connect(); //发送请求数据 OutputStream out = connection.getOutputStream(); out.write(data); out.close(); //读取请求响应 InputStream in = connection.getInputStream(); ByteOutputStream body = new ByteOutputStream(); int ch; while ((ch = in.read()) != -1){ body.write(ch); } in.close(); String responseBody = new String(body.getBytes()); int statusCode = connection.getResponseCode(); boolean is2xxSuccessful = statusCode >= 200 && statusCode <= 299; boolean is3xxSuccessful = statusCode >= 300 && statusCode <= 399; //判断请求是否成功 if (!(is2xxSuccessful || is3xxSuccessful)) { throw new HttpStatusClientException(statusCode, connection.getResponseMessage(), responseBody); } return responseBody; } catch (IOException e) { throw new HttpClientException(e); } } }
以上就实现了一个简单的http客户端了, 基本满足需求。
序列化和反序列化
序列化和反序列化就是将数据在Java实体之间转换,为了设计的灵活一点,此处面向接口编程,分别为序列化和反序列化定义一个接口:
- Serializer
- Deserializer
同时使用jackson
库做了一个简单的实现:
JacksonSerializer
public class JacksonSerializer implements Serializer { private final ObjectMapper objectMapper = new ObjectMapper(); public JacksonSerializer() { objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY); } public byte[] serialize(Object obj) { try { return objectMapper.writeValueAsBytes(obj); } catch (JsonProcessingException e) { throw new IllegalStateException(e); } } }
JacksonDeserializer
public class JacksonDeserializer implements Deserializer { private final ObjectMapper objectMapper = new ObjectMapper(); public JacksonDeserializer() { objectMapper.configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false); } publicT deserialize(byte[] data, Class clz) { try { return objectMapper.readValue(data, clz); } catch (IOException e) { throw new IllegalStateException(e); } } }
这样一个简单的序列化和反序列化工具就做好了,这样基本能满足需求,但是一般的开放平台都有format
参数,它的值可能json
、xml
等等,为了正好的整合序列化和反序列化, 我又创建了一个SerializationAdapter
接口,继承Serializer
和Deserializer
:
public interface SerializationAdapter extends Serializer, Deserializer{ /** * 判断是否支持数据格式 * @return */ boolean support(String format); }
添加SerializationAdapter.support
方法用来判断它是否支持指定的数据格式,如适配json
数据格式,创建一个实现类JSONSerializationAdapter
:
JSONSerializationAdapter
public class JSONSerializationAdapter implements SerializationAdapter { private final Serializer serializer; private final Deserializer deserializer; public JSONSerializationAdapter(Serializer serializer, Deserializer deserializer) { this.serializer = serializer; this.deserializer = deserializer; } public JSONSerializationAdapter() { this(new JacksonSerializer(), new JacksonDeserializer()); } @Override public boolean support(String format) { return format != null && Objects.equals(format.toLowerCase(), "json"); } @Override publicT deserialize(byte[] data, Class clz) { return deserializer.deserialize(data, clz); } @Override public byte[] serialize(Object obj) { return serializer.serialize(obj); } }
这样就比较灵活了, 假如说老板在后面偷偷的来一句,这个要是能支持xml
就完美了,这个需求很简单!假如我们使用JAXB
来实现xml
的序列化,那么先写好序列化和反序列化器:
JAXBSerializer
public class JAXBSerializer implements Serializer { @Override public byte[] serialize(Object obj) { ByteOutputStream outputStream = new ByteOutputStream(); JAXB.marshal(obj, outputStream); return outputStream.getBytes(); } }
JAXBDeserializer
public class JAXBDeserializer implements Deserializer { @Override publicT deserialize(byte[] data, Class clz) { //trim去掉不需要的换行 return JAXB.unmarshal(new StringReader(new String(data).trim()), clz); } }
XMLSerializationAdapter
public class XMLSerializationAdapter implements SerializationAdapter { private final Serializer serializer; private final Deserializer deserializer; public XMLSerializationAdapter(Serializer serializer, Deserializer deserializer) { this.serializer = serializer; this.deserializer = deserializer; } public XMLSerializationAdapter() { this(new JAXBSerializer(), new JAXBDeserializer()); } @Override public boolean support(String format) { return format != null && Objects.equals(format.toLowerCase(), "xml"); } @Override publicT deserialize(byte[] data, Class clz) { return deserializer.deserialize(data, clz); } @Override public byte[] serialize(Object obj) { return serializer.serialize(obj); } }
同理其他数据格式也可以自定定义,就不多做阐述了。
如何使用?
使用也是非常简单的, 简单的写个测试方法测试下:
public class SerializationTest { @Test public void testSerialization(){ String format = "xml"; Listadapters = new ArrayList<>(); adapters.add(new JSONSerializationAdapter()); adapters.add(new XMLSerializationAdapter()); Person person = new Person(); person.setName("霸戈"); person.setAge(18); for(SerializationAdapter adapter : adapters){ if (adapter.support(format)) { byte[] data = adapter.serialize(person); System.out.println(format + "序列化后的数据:" + new String(data)); Person person2 = adapter.deserialize(data, Person.class); System.out.println(format + "反序列化后的数据:" + person2); } } } }
以上测试代码,只需要改变format
变量的值就能在json
和xml
格式之间转换。
JSON格式输出
json序列化后的数据:{"name":"霸戈","age":18} json反序列化后的数据:Person{name='霸戈', age=18}
XML格式输出
xml序列化后的数据:<?xml version="1.0" encoding="UTF-8" standalone="yes"?>xml反序列化后的数据:Person{name='霸戈', age=18} 18 霸戈
API客户端
剩下的工具就是编写一个简单的API客户端,发起http请求、处理响应数据。
public interface SdkClient { /** * 执行API请求 * @param req * @param* @return */ T execute(AbstractRequest req); }
因为SDK都是给他人使用,尽可能的方便,入参(AbstractRequest),和出参(AbstractResponse)都用实体类表示。
AbstractRequest
public abstract class AbstractRequest{ /** * 请求方法名称 * @return */ public abstract String getMethod(); /** * 接口请求参数 * @return */ public abstract Map getApiParams(); /** * 请求响应实体 * @return */ public abstract Class getResponseClass(); }
AbstractResponse
public class AbstractResponse { private String code; private String msg; public boolean isSuccess(){ return Objects.equals(code, "0"); } }
DefaultClient
一个简单的API客户端
public class DefaultClient implements SdkClient { private SerializationAdapter serializationAdapter = new JSONSerializationAdapter(); private HttpClient httpClient = new DefaultHttpClient(); /** * 请求地址 */ private final String endpoint; /** * 应用id */ private final String appid; /** * 应用密钥 */ private final String appsecret; public DefaultClient(String endpoint, String appid, String appsecret) { this.endpoint = endpoint; this.appid = appid; this.appsecret = appsecret; } @Override publicT execute(AbstractRequest req) { Map params = req.getApiParams(); String timestamp = System.currentTimeMillis() + ""; //参数排序 StringBuilder paramsStr = new StringBuilder(); Map treeMap = new TreeMap<>(params); for(Map.Entry entry : treeMap.entrySet()){ paramsStr.append(entry.getKey()).append(entry.getValue()); } //计算签名 //应用密钥 + 应用id + 请求方法名称 + api版本号 + 请求参数 + 应用密钥 String sign = Md5Util.md5(appsecret + appid + req.getMethod() + req.getVersion() + paramsStr.toString() + appsecret ); String url = endpoint + "?" + "method=" + req.getMethod() + "&version=" + req.getVersion() + "×tamp" + timestamp + "&sign" + sign + "&appid" + appid; Map > headers = new HashMap<>(); headers.put("Content-type", Collections.singletonList("application/json")); byte[] body = serializationAdapter.serialize(req.getApiParams()); String response = httpClient.post(url, body, headers); return serializationAdapter.deserialize(response.getBytes(), req.getResponseClass()); } }
总结
至此一个简单的开放平台SDK就基本成型了,为了适配大多数客户的开放环境,应当注意以下几点:
- 尽量使用低版本jdk开发,如1.5、1.6,覆盖绝大多数Java运行/开发环境
- 减少第三方依赖,避免版本冲突
- 出参入参都使用java实体类,避免客户使用时参数名称、类型不正确