This guide walks through the key classes and call chains in the x-rpc source code. It is intended for contributors and developers who want to understand the internals.
When a consumer calls a remote service method, the following chain executes:
File: x-rpc-core/.../proxy/JDKProxy.java
The JDK dynamic proxy intercepts all method calls on the service interface. It builds an Invocation object containing the service name, method name, argument types, arguments, and timeout, then delegates to the cluster invoker.
JDKProxy.JDKInvocationHandler.invoke(proxy, method, args)
→ build Invocation
→ set timeoutMills from ReferenceConfig
→ invoker.invoke(invocation)
→ invocationResult.invokeResult() // unwrap result or throw RpcServerException
Object methods (toString, hashCode, equals) are handled locally without RPC.
File: x-rpc-core/.../cluster/AbstractClusterInvoker.java
The abstract base class handles instance retrieval and routing:
public InvocationResult invoke(Invocation invocation) {
List<ServiceInstance> instances = nameService.getInstances(invocation);
instances = routerChain.route(invocation, instances);
return doInvoke(invocation, instances);
}FailFastClusterInvoker (x-rpc-core/.../cluster/FailFastClusterInvoker.java):
- Selects one instance via LoadBalancer
- Invokes once, propagates any exception
FailoverClusterInvoker (x-rpc-core/.../cluster/FailoverClusterInvoker.java):
- Attempts
retries + 1times - On
RpcClientException(network/timeout), excludes the tried instance and retries - On business exceptions (e.g.,
RpcServerException), fails immediately without retry - Falls back to all instances if untried instances are exhausted
File: x-rpc-core/.../filter/FilterChainBuilder.java
Wraps the terminal invoker with filters in reverse order, creating a chain:
// For filters [A, B, C] and invoker I:
// Result: A → B → C → I
Invoker nextNode = invoker;
for (int i = filters.size() - 1; i >= 0; i--) {
invoker = new FilterChainNodeInvoker(filters.get(i), nextNode);
nextNode = invoker;
}Each FilterChainNodeInvoker calls filter.filter(nextNode, invocation) and handles onResult/onError callbacks via whenComplete.
File: x-rpc-core/.../invoker/ConsumerInvoker.java
Converts Invocation to RpcRequest, sends it asynchronously, and blocks for the response:
CompletableFuture<RpcResponse> future = clientTransport.sendAsync(
invocation.getTargetAddress(), request, timeoutMills, callbackExecutor);
// ... complete InvocationResult on response/error
return result.get(timeoutMills); // blocking waitTimeout throws RpcTimeoutException. Network errors throw RpcClientException.
File: x-rpc-transport-remoting/.../XRemotingClientTransport.java
Delegates to x-remoting library's RpcClient:
connect()- establishes a Netty connection to a providersendAsync()- writesRpcRequestand registers an async callbackdisconnect()/reconnect()- connection lifecycleaddTransportEventListener()- notifiesNameServiceon CONNECT/DISCONNECT events
File: x-rpc-transport-remoting/.../RpcRequestProcessor.java
Netty's thread pool delivers deserialized RpcRequest objects here:
public RpcResponse handRequest(RpcRequest request) {
Invoker invoker = invokerMap.get(request.getServiceName());
Method method = reflectCache.find(serviceName, methodName, argTypes);
// build Invocation
InvocationResult result = invoker.invoke(invocation);
return InvokeTypes.convertRpcResponse(result);
}Errors are caught and returned as RpcResponse(success=false, errorMsg=className: message).
Same FilterChainBuilder mechanism as consumer, but with provider-specific filters:
- GracefulShutdownFilter - If
shuttingDown, rejects withRpcException. Otherwise increments active count, invokes, decrements infinally. - TraceFilter - Reads or generates
traceId/spanIdin invocation attachments. - MetricFilter - Increments total/success/fail counters, measures latency via
System.nanoTime()in try/finally. Logs aggregated metrics every 1000 requests. - ProviderGenericFilter - For generic invocations, deserializes JSON string arguments to the method's actual parameter types.
File: x-rpc-core/.../invoker/ProviderInvoker.java
Performs reflective method invocation on the service implementation:
Object result = invocation.getMethod().invoke(exporterConfig.getServiceImpl(), args);InvocationTargetException is unwrapped; all errors are wrapped as RpcServerException.
File: x-rpc-core/.../bootstrap/ProviderBoostrap.java
ProviderBoostrap.export(exporterConfig)
│ create ProviderInvoker + FilterChain
│ register invoker with ServerTransport
│
│ Registry.initInstance(appName, protocol, address) // once
│ ServiceInstance.addService(exporterConfig) // add to metadata
│ ServiceInstance.isRevisionChanged() // compute MD5
│
├── first export → Registry.registerInstance() // create ZK node
└── subsequent → Registry.updateInstance() // update ZK node
File: x-rpc-core/.../bootstrap/ConsumerBootstrap.java
ConsumerBootstrap.refer(referenceConfig)
│ get/create ClientTransport (shared)
│ create Cluster (with NameService, filters, invoker)
│
│ Registry.addAppServiceInstancesWatcher(appName)
│ └── creates ServiceCache → ZK watcher
│ └── initial query → NameService.notify(instances)
│
│ Registry.subscribe(appName, nameService)
│ └── nameService receives future change notifications
│
│ create JDK Proxy with ClusterInvoker
└── return proxy to user
File: x-rpc-registry-zookeeper/.../ZookeeperRegistry.java
- Uses
CuratorFrameworkwithExponentialBackoffRetry(1000ms, 3 retries)for ZK connection resilience ServiceDiscovery<ZookeeperInstancePayload>manages ephemeral ZK nodes underbasePathServiceCacheprovides local caching withZookeeperServiceDiscoveryChangeWatcherfor real-time notifications- Instance changes trigger
AppServiceInstancesWatcher.change()→NameService.notify()→ connection management
File: x-rpc-core/.../bootstrap/ConsumerBootstrap.java
- Creates a shared
ThreadPoolExecutor(10, 100, 60s, queue=1024, CallerRunsPolicy)for async callbacks refer()creates proxy for each service referenceclose()shuts down executor (30s await), then closes registry and transport managers- Supports direct connect mode: skips cluster/registry, connects directly to a specified address
File: x-rpc-core/.../bootstrap/ProviderBoostrap.java
export()issynchronizedto prevent concurrent registration conflicts- Detects duplicate exports by checking
exportedExporterConfigs - Automatically exports
MetadataServicealongside the first business service register()batch-registers all exported services (called byXRpcApplicationListeneron Spring context refresh)unExport()removes service from metadata, updates ZK, and unregisters from transport- Registers a JVM shutdown hook via
GracefulShutdown.INSTANCE
File: x-rpc-core/.../cluster/naming/DefaultNameService.java
- All methods are
synchronizedfor thread safety notify()diffs old vs new instance sets: connects new, disconnects removedonEvent()handles transport CONNECT/DISCONNECT to move instances between health setsgetInstances()throwsNoAvailableProviderExceptionif no healthy instances exist
File: x-rpc-spring-boot-starter/.../XRpcAutoConfiguration.java
Bean creation order (managed by @ConditionalOnBean chains):
ApplicationConfig
→ ZookeeperConfig → ZookeeperRegistryConfig
→ XRemotingTransportServerConfig + XRemotingTransportClientConfig
→ XRemotingTransportConfig → ProtocolConfig
→ ProviderBoostrap (with provider filters)
→ ConsumerBootstrap (with consumer filters + routers)
→ XRpcApplicationListener
Provider and consumer filters are built as separate lists via buildProviderFilters() and buildConsumerFilters() private methods, preventing filter cross-contamination.
File: x-rpc-spring-boot-starter/.../bean/XRpcServiceAnnotationPostProcessor.java
During BeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry():
- Scans configured packages for
@XRpcServiceannotated classes - Resolves the service interface: uses
interfaceClassif specified, otherwise finds the first non-java.*interface - Creates
ExporterConfigandXRpcServiceBeanbean definitions XRpcServiceBean.afterPropertiesSet()callsproviderBoostrap.export()
File: x-rpc-spring-boot-starter/.../bean/XRpcReferenceAnnotationPostProcessor.java
During InstantiationAwareBeanPostProcessor.postProcessProperties():
- Scans all fields for
@XRpcReferenceannotation - Generates a unique bean name:
interfaceName@appName+XRpcReferenceFactoryBean - Registers
XRpcReferenceFactoryBeanif not already registered XRpcReferenceFactoryBean.getObject()callsconsumerBootstrap.refer()to create the proxy- Injects the proxy into the field
File: x-rpc-spring-boot-starter/.../context/XRpcApplicationListener.java
Listens for ContextRefreshedEvent and calls providerBoostrap.register() to batch-register all exported services. This ensures all services are exported before any ZK registration happens, preventing partial visibility.