Build a Custom Java RPC Framework with Netty, Zookeeper, and Spring

This article explains the fundamentals of RPC, walks through a complete Java implementation using Spring, Netty, and Zookeeper, and provides step‑by‑step code for both the service provider and consumer, including registration, dynamic proxy generation, network communication, and testing.

Java High-Performance Architecture
Java High-Performance Architecture
Java High-Performance Architecture
Build a Custom Java RPC Framework with Netty, Zookeeper, and Spring

Introduction

Remote Procedure Call (RPC) is a protocol that allows a program on one machine to invoke a method on a program running on another machine as if it were a local call, abstracting away the underlying network communication.

What a Basic RPC Call Involves

A typical RPC call includes interface‑based remote method invocation, dynamic proxy generation on the client side, serialization/deserialization of request and response objects, service registration in a registry, service discovery, and network communication handled by a high‑performance framework such as Netty.

RPC flow diagram
RPC flow diagram

Basic Implementation

Server (Provider)

Service Interface

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

Service Implementation

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

Service Registration

The service is registered to a Zookeeper‑based registry using custom Spring XML configuration and a namespace handler that maps the service element to a ProviderFactoryBean. The bean loads the implementation, creates a Netty server, and writes metadata (interface, host, port, weight, etc.) to Zookeeper.

public class ProviderFactoryBean implements FactoryBean, InitializingBean {
    private Class<?> serviceItf;
    private Object serviceObject;
    private String serverPort;
    private long timeout;
    private String appKey;
    private String groupName = "default";
    private int weight = 1;
    private int workerThreads = 10;

    @Override
    public Object getObject() throws Exception {
        return serviceProxyObject;
    }

    @Override
    public Class<?> getObjectType() {
        return serviceItf;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        NettyServer.singleton().start(Integer.parseInt(serverPort));
        RegisterCenter.singleton().registerProvider(buildProviderServiceInfos());
    }
}

Client (Consumer)

Dynamic Proxy Generation

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

Service Discovery

The client obtains the list of provider nodes from Zookeeper and selects one according to a load‑balancing strategy.

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

Remote Invocation 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);
    Future<StormResponse> future = threadPool.submit(
        RevokerServiceCallable.of(new InetSocketAddress(ip, port), request));
    StormResponse response = future.get(request.getInvokeTimeout(), TimeUnit.MILLISECONDS);
    return response != null ? response.getResult() : null;
}

Testing

Server startup:

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

Client usage:

public class Client {
    public static void main(String[] args) throws Exception {
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("storm-client.xml");
        HelloService hello = (HelloService) ctx.getBean("helloService");
        System.out.println(hello.sayHello("World"));
    }
}
Producer console output
Producer console output
Consumer console output
Consumer console output

Conclusion

The article demonstrates a complete RPC workflow: the provider loads and registers the service, starts a Netty server, and handles requests via reflection; the consumer creates a dynamic proxy, discovers services through Zookeeper, and performs remote calls with Netty, illustrating the core concepts of RPC in Java.

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.

JavaRPCZooKeeperNetty
Java High-Performance Architecture
Written by

Java High-Performance Architecture

Sharing Java development articles and resources, including SSM architecture and the Spring ecosystem (Spring Boot, Spring Cloud, MyBatis, Dubbo, Docker), Zookeeper, Redis, architecture design, microservices, message queues, Git, etc.

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.