Backend Development 16 min read

Implementing Transparent RPC over Spring MVC: From REST to RPC Programming Model

The article explains how to replace a traditional Spring MVC‑based REST RPC framework with a transparent RPC model by extending DispatcherServlet, HandlerMapping, and HandlerAdapter, providing implicit service contracts, simplified client interfaces, and code examples for registration and request handling.

Selected Java Interview Questions
Selected Java Interview Questions
Selected Java Interview Questions
Implementing Transparent RPC over Spring MVC: From REST to RPC Programming Model

Our current RPC framework is built on a Java Rest approach, similar to Spring Cloud Feign. With the rise of micro‑services, REST style has become the norm, Spring MVC serves as the de‑facto standard, and its low entry barrier makes it attractive.

REST vs RPC Development Styles

A simple Feign‑like RPC client example:

@RpcClient(schemaId="hello")
public interface Hello {
    @GetMapping("/message")
    HelloMessage hello(@RequestParam String name);
}

The service provider can expose the service directly with Spring MVC:

@RestController
public class HelloController {
    @Autowired
    private HelloService helloService;

    @GetMapping("/message")
    public HelloMessage getMessage(@RequestParam(name="name") String name) {
        HelloMessage hello = helloService.gen(name);
        return hello;
    }
}

Developing with REST brings many advantages: low learning curve, full reliance on Spring MVC annotations, and a decoupled, flexible interface that can serve both SDK and non‑SDK consumers. However, it also introduces problems such as mismatched client/server signatures, forgotten annotations, and the extra controller layer that complicates DDD‑style service decomposition.

To address these issues we propose a transparent RPC style that simplifies interface definitions and eliminates unnecessary annotations.

First, a more concise service interface definition:

@RpcClient(schemaId="hello")
public interface Hello {
    HelloMessage hello(String name);
}

Then the service implementation can be published with a simple annotation:

@RpcService(schemaId="hello")
public class HelloImpl implements Hello {
    @Override
    public HelloMessage hello(String name) {
        return new HelloMessage(name);
    }
}

Clients can now invoke hello() directly and receive a HelloMessage without the boilerplate of REST controllers.

Implicit Service Contract

In transparent RPC we generate the service contract automatically from the implementation class. The default contract includes the HTTP method (determined by parameter types) and a URL pattern of /ClassName/MethodType+MethodName . Methods without explicit annotations are registered using this convention.

Server‑Side REST Programming Model

The existing architecture relies on Spring MVC and the servlet‑based communication model. To switch to an RPC model we need to modify the communication layer while keeping the rest of the framework.

Understanding DispatcherServlet

DispatcherServlet processes a request through three main components: HandlerMapping, HandlerAdapter, and ViewResolver. It finds a suitable handler via HandlerMapping, adapts the request with HandlerAdapter, and finally resolves the view.

We choose to extend the existing DispatcherServlet by customizing only the HandlerMapping part, allowing us to keep most of the infrastructure unchanged.

HandlerMapping Initialization

The core registration method is registerHandlerMethod(Object handler, Method method, T mapping) . By scanning for @RpcClient‑annotated interfaces and their implementations, we can invoke this method to register RPC endpoints.

protected void registerHandlerMethod(Object handler, Method method, T mapping) {
    this.mappingRegistry.register(mapping, handler, method);
}

During registration the framework creates a HandlerMethod object, which holds an array of MethodParameter instances representing the method arguments.

HandlerAdapter Request Processing

When handling a request, RequestMappingHandlerAdapter calls getMethodArgumentValues , which uses argumentResolvers to resolve each parameter. For example, RequestResponseBodyMethodProcessor supports parameters annotated with @RequestBody :

@Override
public boolean supportsParameter(MethodParameter parameter) {
    return parameter.hasParameterAnnotation(RequestBody.class);
}

Our goal is to make the RPC framework appear as if the necessary annotations are present, even when they are omitted.

RPC Method Registration

We create a custom RpcRequestMappingHandlerMapping that scans for @RpcService classes and registers each method with an implicit contract:

public class RpcRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
    public void registerRpcToMvc(final String prefix) {
        AdvancedApiToMvcScanner scanner = new AdvancedApiToMvcScanner(RpcService.class);
        scanner.setBasePackage(basePackage);
        Map
, Set
> mvcMap = scanner.scan();
        for (Class
clazz : mvcMap.keySet()) {
            for (MethodTemplate methodTemplate : mvcMap.get(clazz)) {
                Method method = methodTemplate.getMethod();
                HttpMethod httpMethod = MvcFuncUtil.judgeMethodType(method);
                String uriTemplate = MvcFuncUtil.genMvcFuncName(clazz, httpMethod.name(), method);
                RequestMappingInfo requestMappingInfo = RequestMappingInfo
                    .paths(resolveEmbeddedValuesInPatterns(new String[]{uriTemplate}))
                    .methods(RequestMethod.valueOf(httpMethod.name()))
                    .build();
                this.registerHandlerMethod(handler, method, requestMappingInfo);
            }
        }
    }
}

RPC Request Handling

To make the argument resolvers treat parameters as if they were annotated, we override createHandlerMethod to return a custom RpcHandlerMethod that replaces each MethodParameter with a RpcMethodParameter :

@Override
protected HandlerMethod createHandlerMethod(Object handler, Method method) {
    HandlerMethod handlerMethod;
    if (handler instanceof String) {
        String beanName = (String) handler;
        handlerMethod = new HandlerMethod(beanName, getApplicationContext().getAutowireCapableBeanFactory(), method);
    } else {
        handlerMethod = new HandlerMethod(handler, method);
    }
    return new RpcHandlerMethod(handlerMethod);
}

The RpcMethodParameter injects synthetic annotations ( @RequestParam or @RequestBody ) based on the parameter type, so the existing resolvers will process them correctly.

public class RpcMethodParameter extends SynthesizingMethodParameter {
    private volatile Annotation[] annotations;
    protected RpcMethodParameter(SynthesizingMethodParameter original) {
        super(original);
        this.annotations = initParameterAnnotations();
    }
    private Annotation[] initParameterAnnotations() {
        List
list = new ArrayList<>();
        Class
parameterType = this.getParameterType();
        if (MvcFuncUtil.isRequestParamClass(parameterType)) {
            list.add(MvcFuncUtil.newRequestParam(MvcFuncUtil.genMvcParamName(this.getParameterIndex())));
        } else if (MvcFuncUtil.isRequestBodyClass(parameterType)) {
            list.add(MvcFuncUtil.newRequestBody());
        }
        return list.toArray(new Annotation[]{});
    }
    @Override
    public Annotation[] getParameterAnnotations() {
        return (annotations != null && annotations.length > 0) ? annotations : super.getParameterAnnotations();
    }
}

With these extensions the framework can automatically register RPC endpoints, generate implicit contracts, and handle requests without requiring developers to write explicit REST annotations, achieving a clean and consistent transparent RPC development experience.

Resulting RPC Programming Model

After the modifications, the overall architecture shifts from a pure REST‑based model to a transparent RPC model, as illustrated in the final diagram.

backendJavamicroservicesrpcSpring MVCHandlerMappingTransparent RPC
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

login 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.