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.
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.
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.
Selected Java Interview Questions
A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!
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.
