Implementation Principles and Sample Code of RPC (Remote Procedure Call) in Java

This article explains the fundamentals of RPC, describes the components involved in a basic remote call, and provides a complete Java implementation using Spring, custom XML configuration, Netty for network communication, and Zookeeper for service registration and discovery, complete with server and client code examples.

Selected Java Interview Questions
Selected Java Interview Questions
Selected Java Interview Questions
Implementation Principles and Sample Code of RPC (Remote Procedure Call) in Java

Introduction

Remote Procedure Call (RPC) is a communication protocol that allows a program running on one computer to invoke a sub‑program on another computer as if it were a local method call, without the developer needing to write networking code.

What a Basic RPC Call Involves

Modern RPC frameworks such as Dubbo use interface‑based remote method invocation. The client only needs the interface definition and a dynamically generated proxy object (via Proxy and InvocationHandler) to call the remote service. The call traverses the network, requiring serialization/deserialization and service discovery through a registration center.

Basic Implementation

Server (Provider)

Service Interface

The shared service API is defined as:

/**
 * @author Sun Hao
 * @description Service interface
 */
public interface HelloService {
    String sayHello(String somebody);
}

Service Implementation

The concrete implementation:

/**
 * @author Sun Hao
 * @description Service implementation
 */
public class HelloServiceImpl implements HelloService {
    @Override
    public String sayHello(String somebody) {
        return "hello " + somebody + "!";
    }
}

Service Registration

Using a custom Spring XML namespace, the service is registered to a Zookeeper‑based registration center. The XSD definition looks like:

<xsd:element name="service">
    <xsd:complexType>
        <xsd:complexContent>
            <xsd:extension base="beans:identifiedType">
                <xsd:attribute name="interface" type="xsd:string" use="required"/>
                <xsd:attribute name="timeout" type="xsd:int" use="required"/>
                <xsd:attribute name="serverPort" type="xsd:int" use="required"/>
                <xsd:attribute name="ref" type="xsd:string" use="required"/>
                <xsd:attribute name="weight" type="xsd:int" use="optional"/>
                <xsd:attribute name="workerThreads" type="xsd:int" use="optional"/>
                <xsd:attribute name="appKey" type="xsd:string" use="required"/>
                <xsd:attribute name="groupName" type="xsd:string" use="optional"/>
            </xsd:extension>
        </xsd:complexContent>
    </xsd:complexType>
</xsd:element>

The XSD and its handler are placed under classpath:META-INF. In the Spring configuration the service is published:

<!-- Publish remote service -->
<bean id="helloService" class="com.hsunfkqm.storm.framework.test.HelloServiceImpl"/>
<storm:service id="helloServiceRegister"
    interface="com.hsunfkqm.storm.framework.test.HelloService"
    ref="helloService"
    groupName="default"
    weight="2"
    appKey="ares"
    workerThreads="100"
    serverPort="8081"
    timeout="600"/>

The handler StormServiceNamespaceHandler registers a ProviderFactoryBeanDefinitionParser which creates a ProviderFactoryBean. The bean starts a Netty server, registers metadata to Zookeeper, and holds configuration such as service interface, implementation object, port, timeout, weight, and thread pool size.

public class ProviderFactoryBean implements FactoryBean, InitializingBean {
    private Class<?> serviceItf;
    private Object serviceObject;
    private String serverPort;
    private long timeout;
    private Object serviceProxyObject;
    private String appKey;
    private String groupName = "default";
    private int weight = 1;
    private int workerThreads = 10;
    @Override
    public void afterPropertiesSet() throws Exception {
        // start Netty server
        NettyServer.singleton().start(Integer.parseInt(serverPort));
        // register to ZK
        List<ProviderService> providerServiceList = buildProviderServiceInfos();
        RegisterCenter.singleton().registerProvider(providerServiceList);
    }
    // getters omitted
}

Netty server bootstrap adds a decoder, encoder, and the business handler NettyServerInvokeHandler which extracts the InvocationHandler#invoke method name, performs service discovery, applies a semaphore for rate limiting, invokes the target method via reflection, and writes back a StormResponse.

public class NettyServerInvokeHandler extends SimpleChannelInboundHandler<StormRequest> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, StormRequest request) throws Exception {
        ProviderService metaData = request.getProviderService();
        String methodName = request.getInvokedMethodName();
        // service discovery, semaphore acquire, reflection invoke
        Object result = method.invoke(serviceObject, request.getArgs());
        StormResponse response = new StormResponse();
        response.setResult(result);
        ctx.writeAndFlush(response);
    }
}

Client (Consumer)

Generate Proxy via JDK Dynamic Proxy

public Object getProxy() {
    return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
        new Class<?>[]{targetInterface}, this);
}

Service Discovery from Zookeeper and load‑balancing

String serviceKey = targetInterface.getName();
List<ProviderService> providerServices = RegisterCenter.singleton()
    .getServiceMetaDataMap4Consume().get(serviceKey);
ClusterStrategy strategy = ClusterEngine.queryClusterStrategy(clusterStrategy);
ProviderService provider = strategy.select(providerServices);

Remote Call via Netty

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    StormRequest request = new StormRequest();
    request.setUniqueKey(UUID.randomUUID().toString() + "-" + Thread.currentThread().getId());
    request.setProviderService(provider.copy());
    request.setInvokedMethodName(method.getName());
    request.setArgs(args);
    InetSocketAddress address = new InetSocketAddress(request.getProviderService().getServerIp(),
        request.getProviderService().getServerPort());
    Future<StormResponse> future = fixedThreadPool.submit(RevokerServiceCallable.of(address, request));
    StormResponse response = future.get(request.getInvokeTimeout(), TimeUnit.MILLISECONDS);
    return response != null ? response.getResult() : null;
}

The client stores the asynchronous Netty response in a blocking queue ( RevokerResponseHolder) so that the calling thread can synchronously obtain the result.

@Override
protected void channelRead0(ChannelHandlerContext ctx, StormResponse response) throws Exception {
    RevokerResponseHolder.putResultValue(response);
}

Testing

Server main class publishes the service:

public class MainServer {
    public static void main(String[] args) throws Exception {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("storm-server.xml");
        System.out.println("Service published");
    }
}

Client main class obtains the proxy and invokes the method:

public class Client {
    public static void main(String[] args) throws Exception {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("storm-client.xml");
        HelloService helloService = (HelloService) context.getBean("helloService");
        String result = helloService.sayHello("World");
        System.out.println(result);
        for (;;) {}
    }
}

Running the server shows the registration center entries (Zookeeper nodes) and the client receives the expected "hello World!" response.

Conclusion

The article presented a complete end‑to‑end RPC workflow: the provider loads and registers the service interface, starts a Netty server, and handles requests via reflection; the consumer creates a dynamic proxy, discovers services from Zookeeper, selects a provider using a load‑balancing strategy, and performs remote invocation over Netty. This example helps deepen understanding of RPC mechanisms.

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.

RPCspringZooKeeperNetty
Selected Java Interview Questions
Written by

Selected Java Interview Questions

A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!

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.