In-Depth Analysis of XXL‑RPC Framework: Design, Implementation, and Source Code Walkthrough
This article provides a comprehensive overview of the lightweight XXL‑RPC framework, covering fundamental RPC concepts, the framework's architecture built on Spring and Netty, detailed provider and consumer implementations, various call types, and the service registry‑discovery mechanism, concluding with practical insights for developers.
1. RPC Basics
Remote Procedure Call (RPC) enables a program to invoke methods on a remote service as if they were local, using stub (client‑side proxy) and skeleton (server‑side handler) components to hide network details.
2. XXL‑RPC Design
XXL‑RPC is a lightweight, Spring‑integrated RPC framework that relies on Netty NIO for transport. It defines two key annotations: @XxlRpcService for providers and @XxlRpcReference for consumers.
Provider Side
The XxlRpcSpringProviderFactory scans beans annotated with @XxlRpcService, registers them in a map, and starts a Netty server in afterPropertiesSet(). The server uses NettyServerHandler to dispatch requests to the appropriate service via reflection.
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
Map<String, Object> serviceBeanMap = applicationContext.getBeansWithAnnotation(XxlRpcService.class);
if (serviceBeanMap != null && serviceBeanMap.size() > 0) {
for (Object serviceBean : serviceBeanMap.values()) {
if (serviceBean.getClass().getInterfaces().length == 0) {
throw new XxlRpcException("XXL-RPC, service(XxlRpcService) must inherit interface.");
}
XxlRpcService xxlRpcService = serviceBean.getClass().getAnnotation(XxlRpcService.class);
String iface = serviceBean.getClass().getInterfaces()[0].getName();
String version = xxlRpcService.version();
super.addService(iface, version, serviceBean);
}
}
}The Netty server is started in NettyServer.start(), creating boss and worker groups, configuring channel pipelines with idle state handling, encoders/decoders, and the core NettyServerHandler.
public void start(final XxlRpcProviderFactory xxlRpcProviderFactory) throws Exception {
thread = new Thread(new Runnable() {
@Override
public void run() {
ThreadPoolExecutor serverHandlerPool = ThreadPoolUtil.makeServerThreadPool(
NettyServer.class.getSimpleName(),
xxlRpcProviderFactory.getCorePoolSize(),
xxlRpcProviderFactory.getMaxPoolSize());
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel channel) throws Exception {
channel.pipeline()
.addLast(new IdleStateHandler(0, 0, Beat.BEAT_INTERVAL * 3, TimeUnit.SECONDS))
.addLast(new NettyDecoder(XxlRpcRequest.class, xxlRpcProviderFactory.getSerializerInstance()))
.addLast(new NettyEncoder(XxlRpcResponse.class, xxlRpcProviderFactory.getSerializerInstance()))
.addLast(new NettyServerHandler(xxlRpcProviderFactory, serverHandlerPool));
}
})
.childOption(ChannelOption.TCP_NODELAY, true)
.childOption(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture future = bootstrap.bind(xxlRpcProviderFactory.getPort()).sync();
onStarted();
future.channel().closeFuture().sync();
} finally {
// shutdown logic omitted for brevity
}
}
});
thread.start();
}Consumer Side
During Spring initialization, XxlRpcSpringInvokerFactory processes fields annotated with @XxlRpcReference, creates a XxlRpcReferenceBean, and generates a dynamic proxy that forwards method calls to the remote provider.
public Object getObject() throws Exception {
return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class[]{ iface },
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// build XxlRpcRequest
if (CallType.SYNC == callType) {
XxlRpcFutureResponse futureResponse = new XxlRpcFutureResponse(invokerFactory, xxlRpcRequest, null);
clientInstance.asyncSend(finalAddress, xxlRpcRequest);
XxlRpcResponse response = futureResponse.get(timeout, TimeUnit.MILLISECONDS);
if (response.getErrorMsg() != null) {
throw new XxlRpcException(response.getErrorMsg());
}
return response.getResult();
} else if (CallType.FUTURE == callType) {
// future handling omitted for brevity
} else if (CallType.CALLBACK == callType) {
// callback handling omitted for brevity
} else if (CallType.ONEWAY == callType) {
clientInstance.asyncSend(finalAddress, xxlRpcRequest);
return null;
}
return null;
}
});
}The proxy supports four call types—SYNC, FUTURE, CALLBACK, and ONEWAY—implementing a "sync‑over‑async" pattern using wait() / notifyAll() to bridge asynchronous Netty communication with synchronous method semantics.
3. Service Registry & Discovery
When the Netty server starts, a callback registers all provider services to a central registry via HTTP. A background thread periodically refreshes registration data every 10 seconds. Consumers poll the registry (long‑polling) to obtain available service addresses and apply load‑balancing to select an endpoint.
4. Summary
XXL‑RPC is a compact, easy‑to‑understand RPC framework built on Spring and Netty. Its clear source code, lightweight design, and support for multiple invocation modes make it an excellent learning tool before tackling more complex systems such as Dubbo.
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.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
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.
