Design and Refactoring of a Java SDK with Proxy Optimization and Integration Test Enhancements
The article details how a Java SDK for a CIM instant‑messaging system was redesigned using Java 8+ features and the Builder pattern, introduced a lightweight proxy resembling RPC frameworks with dynamic‑URL support, and added comprehensive integration tests that verify automatic client reconnection and reliable message delivery across clustered servers.
The author describes the design and refactoring of a Java SDK used for integration testing in a CIM (Instant Messaging) system. The SDK was created to address coupling issues in the original client code and has been restructured using modern Java 8+ features and the Builder pattern.
Key components of the refactored SDK include a Spring‑Boot bean that builds a Client instance with configurable parameters such as callback thread pool, event handling, authentication, route URL, retry count, and an OkHttp client. The Client encapsulates core functionalities like long‑connection management, heartbeat detection, timeout handling, and automatic reconnection.
@Bean
public Client buildClient(@Qualifier("callBackThreadPool") ThreadPoolExecutor callbackThreadPool,
Event event) {
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.connectTimeout(3, TimeUnit.SECONDS)
.readTimeout(3, TimeUnit.SECONDS)
.writeTimeout(3, TimeUnit.SECONDS)
.retryOnConnectionFailure(true)
.build();
return Client.builder()
.auth(ClientConfigurationData.Auth.builder()
.userName(appConfiguration.getUserName())
.userId(appConfiguration.getUserId())
.build())
.routeUrl(appConfiguration.getRouteUrl())
.loginRetryCount(appConfiguration.getReconnectCount())
.event(event)
.reconnectCheck(client -> !shutDownSign.checkStatus())
.okHttpClient(okHttpClient)
.messageListener(new MsgCallBackListener(msgLogger))
.callbackThreadPool(callbackThreadPool)
.build();
}The SDK also provides a simple API for sending and receiving messages, illustrated with an image (omitted here).
To reduce boilerplate in HTTP calls, a lightweight Proxy mechanism was introduced. The original ProxyManager implementation handled connection creation, parameter packaging, response parsing, and error handling, but only covered half of the needed features.
@Override
public List
onlineUsers() throws Exception {
RouteApi routeApi = new ProxyManager<>(RouteApi.class, routeUrl, okHttpClient).getInstance();
Response response = null;
OnlineUsersResVO onlineUsersResVO = null;
try {
response = (Response) routeApi.onlineUser();
String json = response.body().string();
onlineUsersResVO = JSON.parseObject(json, OnlineUsersResVO.class);
} catch (Exception e) {
log.error("exception", e);
} finally {
response.body().close();
}
return onlineUsersResVO.getDataBody();
}After refactoring, the proxy code becomes much more concise, resembling RPC frameworks such as Dubbo or gRPC.
// Declare interface
@Request(method = Request.GET)
BaseResponse
> onlineUser() throws Exception;
// Initialization
routeApi = RpcProxyManager.create(RouteApi.class, routeUrl, okHttpClient);
public Set
onlineUser() throws Exception {
BaseResponse
> onlineUsersResVO = routeApi.onlineUser();
return onlineUsersResVO.getDataBody();
}The refactored proxy also supports dynamic URLs via a custom @DynamicUrl annotation, allowing the URL to be supplied at call time while keeping the proxy object as a Spring singleton.
EchoResponse echoTarget(EchoRequest message, @DynamicUrl(useMethodEndpoint = false) String url);
Echo echo = RpcProxyManager.create(Echo.class, client);
String url = "http://echo.free.beeceptor.com/sample-request?author=beeceptor";
EchoResponse response = echo.echoTarget(request, url);Integration testing was improved to support clustered server setups. A comprehensive test case demonstrates starting two server instances, a route service, creating two clients, sending messages, stopping one server, and verifying automatic client reconnection and message delivery.
@Test
public void testReconnect() throws Exception {
super.startTwoServer();
super.startRoute();
String routeUrl = "http://localhost:8083";
// register accounts, build auth objects, create clients, send messages, etc.
// ... (omitted for brevity) ...
// stop one server and verify reconnection
super.stopServer(serverInfo.getCimServerPort());
TimeUnit.SECONDS.sleep(30);
Awaitility.await().atMost(15, TimeUnit.SECONDS)
.untilAsserted(() -> Assertions.assertEquals(ClientState.State.Ready, state));
// send another message and verify
client1.sendGroup(msg);
Awaitility.await()
.untilAsserted(() -> Assertions.assertEquals(String.format("cj:%s", msg), client2Receive.get()));
super.stopTwoServer();
}The test workflow includes starting two servers, launching a route, creating two clients, sending messages, stopping the server connected to the first client, waiting for automatic reconnection, and sending messages again to confirm reliability.
All source code is available at https://github.com/crossoverJie/cim . Future work includes adding message acknowledgments, offline messaging, and other high‑demand features.
Sohu Tech Products
A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.
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.