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.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
In-Depth Analysis of XXL‑RPC Framework: Design, Implementation, and Source Code Walkthrough

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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Distributed SystemsjavaMicroservicesRPCspringNetty
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.