Build a Flexible Java Open Platform SDK: HTTP, Serialization, API Client
This article walks through the design and implementation of a Java open‑platform SDK, covering a lightweight HTTP client, flexible serialization adapters for JSON and XML, and a simple API client, while sharing practical code examples and best‑practice tips for building maintainable backend integrations.
Hello everyone, I'm Bage! After the previous article on the design ideas of the Taobao Open Platform API, many readers asked for the next piece, so this article presents the design ideas for an open platform SDK and demonstrates step‑by‑step implementation.
Data Transmission Module: handles request data transmission using the HTTP protocol.
Serialization Module: provides serialization and deserialization, supporting formats such as JSON and XML.
Application Module: coordinates data transmission and serialization, and performs request signing.
Data Transmission
The transmission protocol uses the widely supported http protocol. Although a generic design could support other protocols like socket, the added complexity is unnecessary for most cases.
/**
* http protocol interface
*/
public interface HttpClient {
/**
* Execute an HTTP POST request
* @param url
* @param data
* @param headers
* @return response body
*/
String post(String url, byte[] data, Map<String, List<String>> headers) throws HttpClientException;
}A simple implementation using HttpURLConnection follows:
package com.javaobj.sdk.http.impl;
public class DefaultHttpClient implements HttpClient {
public String post(String url, byte[] data, Map<String, List<String>> headers) throws HttpClientException {
URL targetUrl = null;
try {
targetUrl = new URL(url);
} catch (MalformedURLException e) {
throw new HttpClientException("Invalid URL");
}
try {
HttpURLConnection connection = (HttpURLConnection) targetUrl.openConnection();
connection.setDoInput(true);
connection.setDoOutput(true);
connection.setRequestMethod("POST");
if (headers != null) {
for (Map.Entry<String, List<String>> entry : headers.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);
}
}
}Serialization and Deserialization
Serialization converts Java objects to byte streams, and deserialization does the reverse. Two simple interfaces are defined:
Serializer
Deserializer
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);
}
public <T> T deserialize(byte[] data, Class<T> clz) {
try {
return objectMapper.readValue(data, clz);
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
}To support multiple formats, a SerializationAdapter interface extends both Serializer and Deserializer and adds a support(String format) method.
public interface SerializationAdapter extends Serializer, Deserializer {
/**
* Determine whether the adapter supports the given format
*/
boolean support(String format);
}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
public <T> T deserialize(byte[] data, Class<T> clz) {
return deserializer.deserialize(data, clz);
}
@Override
public byte[] serialize(Object obj) {
return serializer.serialize(obj);
}
}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
public <T> T deserialize(byte[] data, Class<T> clz) {
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
public <T> T deserialize(byte[] data, Class<T> clz) {
return deserializer.deserialize(data, clz);
}
@Override
public byte[] serialize(Object obj) {
return serializer.serialize(obj);
}
}How to Use?
public class SerializationTest {
@Test
public void testSerialization() {
String format = "xml";
List<SerializationAdapter> adapters = new ArrayList<>();
adapters.add(new JSONSerializationAdapter());
adapters.add(new XMLSerializationAdapter());
Person person = new Person();
person.setName("Bage");
person.setAge(18);
for (SerializationAdapter adapter : adapters) {
if (adapter.support(format)) {
byte[] data = adapter.serialize(person);
System.out.println(format + " serialized data: " + new String(data));
Person person2 = adapter.deserialize(data, Person.class);
System.out.println(format + " deserialized data: " + person2);
}
}
}
}API Client
A simple SDK client interface abstracts request execution and response handling.
public interface SdkClient {
/**
* Execute an API request
*/
<T extends AbstractResponse> T execute(AbstractRequest<T> req);
}AbstractRequest
public abstract class AbstractRequest<T extends AbstractResponse> {
/** Request method name */
public abstract String getMethod();
/** API parameters */
public abstract Map<String, Object> getApiParams();
/** Response class */
public abstract Class<T> getResponseClass();
}AbstractResponse
public class AbstractResponse {
private String code;
private String msg;
public boolean isSuccess() {
return Objects.equals(code, "0");
}
}DefaultClient
public class DefaultClient implements SdkClient {
private SerializationAdapter serializationAdapter = new JSONSerializationAdapter();
private HttpClient httpClient = new DefaultHttpClient();
private final String endpoint;
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
public <T extends AbstractResponse> T execute(AbstractRequest<T> req) {
Map<String, Object> params = req.getApiParams();
String timestamp = System.currentTimeMillis() + "";
StringBuilder paramsStr = new StringBuilder();
Map<String, Object> treeMap = new TreeMap<>(params);
for (Map.Entry<String, Object> entry : treeMap.entrySet()) {
paramsStr.append(entry.getKey()).append(entry.getValue());
}
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<String, List<String>> 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());
}
}Summary
Prefer low‑version JDK (1.5/1.6) to maximize runtime compatibility.
Minimize third‑party dependencies to avoid version conflicts.
Use Java POJOs for request and response parameters to ensure type safety for SDK users.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java High-Performance Architecture
Sharing Java development articles and resources, including SSM architecture and the Spring ecosystem (Spring Boot, Spring Cloud, MyBatis, Dubbo, Docker), Zookeeper, Redis, architecture design, microservices, message queues, Git, etc.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
