OpenFeign Deep Dive: Dynamic Proxies, Request Flow, and Spring Cloud Integration
This article provides a comprehensive walkthrough of OpenFeign’s request processing pipeline, covering dynamic proxy generation, annotation parsing, method handler routing, request template building, interceptor execution, logging, client implementation, and Spring Cloud OpenFeign extensions such as Hystrix and Sentinel, illustrated with code snippets and diagrams.
0. Intro Demo
This code is an OpenFeign example that fetches all contributors of a GitHub repository and creates an issue; it is recommended to start debugging with DEBUG level to read the source code.
interface GitHub {
@RequestLine("GET /repos/{owner}/{repo}/contributors")
List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repo);
@RequestLine("POST /repos/{owner}/{repo}/issues")
void createIssue(Issue issue, @Param("owner") String owner, @Param("repo") String repo);
}
public static class Contributor {
String login;
int contributions;
}
public static class Issue {
String title;
String body;
List<String> assignees;
int milestone;
List<String> labels;
}
public class MyApp {
public static void main(String... args) {
GitHub github = Feign.builder()
.decoder(new GsonDecoder())
.target(GitHub.class, "https://api.github.com");
List<Contributor> contributors = github.contributors("OpenFeign", "feign");
for (Contributor contributor : contributors) {
System.out.println(contributor.login + " (" + contributor.contributions + ")");
}
}
}Feign.build Dependency Injection
public Feign build() {
SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
logLevel, decode404, closeAfterDecode, propagationPolicy);
ParseHandlersByName handlersByName =
new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
errorDecoder, synchronousMethodHandlerFactory);
return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}Using JDK Dynamic Proxy to Generate Interface Proxy
Dynamic Proxy Generates Interface Object
public class ReflectiveFeign extends Feign {
@Override
public <T> T newInstance(Target<T> target) {
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
InvocationHandler handler = factory.create(target, methodToHandler);
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),
new Class<?>[]{target.type()}, handler);
return proxy;
}
}Parsing Interface Method Annotation Information
To parse the annotations of GitHub.contributors, Feign provides a Contract implementation. The default contract processes @RequestLine, @Body, @Headers, @Param, @QueryMap, and @HeaderMap annotations.
Default Parsing Logic
class Default extends Contract.BaseContract {
protected void processAnnotationOnMethod(MethodMetadata data, Annotation methodAnnotation, Method method) {
Class<? extends Annotation> annotationType = methodAnnotation.annotationType();
if (annotationType == RequestLine.class) {
// @RequestLine handling
} else if (annotationType == Body.class) {
// @Body handling
} else if (annotationType == Headers.class) {
// @Headers handling
}
}
protected boolean processAnnotationsOnParameter(MethodMetadata data, Annotation[] annotations, int paramIndex) {
boolean isHttpAnnotation = false;
for (Annotation annotation : annotations) {
Class<? extends Annotation> annotationType = annotation.annotationType();
if (annotationType == Param.class) {
// @Param handling
} else if (annotationType == QueryMap.class) {
// @QueryMap handling
} else if (annotationType == HeaderMap.class) {
// @HeaderMap handling
}
}
return isHttpAnnotation;
}
}Spring MVC Extension Annotations
SpringMvcContract adds support for Spring MVC annotations (e.g., @RequestMapping, @GetMapping, @PostMapping) to spring-cloud-open-feign.
public class SpringMvcContract {
@Override
protected void processAnnotationOnClass(MethodMetadata data, Class<?> clz) {
// process @RequestMapping on class
}
@Override
protected void processAnnotationOnMethod(MethodMetadata data, Annotation methodAnnotation, Method method) {
// process @RequestMapping, @GetMapping, @PostMapping, etc.
}
@Override
protected boolean processAnnotationsOnParameter(MethodMetadata data, Annotation[] annotations, int paramIndex) {
// handle parameter annotations
return isHttpAnnotation;
}
}MethodHandler Request Processing Logic
MethodHandler Routing
According to the request method, the call is routed to different MethodHandler implementations.
final class SynchronousMethodHandler implements MethodHandler {
@Override
public Object invoke(Object[] argv) throws Throwable {
RequestTemplate template = buildTemplateFromArgs.create(argv);
Options options = findOptions(argv);
Retryer retryer = this.retryer.clone();
while (true) {
try {
Request request = targetRequest(template);
if (logLevel != Logger.Level.NONE) {
logger.logRequest(metadata.configKey(), logLevel, request);
}
Response response = client.execute(request, options);
// decode response based on status
return response;
} catch (RetryableException e) {
// retry logic
}
}
}
}Building Request Template Based on Parameters
Form submission vs. direct body submission.
Executing Request Interceptors to Build Final Request
Request targetRequest(RequestTemplate template) {
for (RequestInterceptor interceptor : requestInterceptors) {
interceptor.apply(template);
}
return target.apply(template);
}Request Logging Handling
Log level configuration.
public enum Level {
NONE, // no output
BASIC, // method, URL, status, time
HEADERS,// headers + basic info
FULL // headers, body + basic info
}Client Executes Final Request
The default client uses java.net, but can be replaced with HttpClient or OKHttp for higher performance.
class Default implements Client {
private final SSLSocketFactory sslContextFactory;
private final HostnameVerifier hostnameVerifier;
@Override
public Response execute(Request request, Request.Options options) throws IOException {
HttpURLConnection connection = convertAndSend(request, options);
return convertResponse(connection, request);
}
}Spring Cloud Load Balancer Client
public class FeignBlockingLoadBalancerClient {
@Override
public Response execute(Request request, Request.Options options) throws IOException {
URI originalUri = URI.create(request.url());
String serviceId = originalUri.getHost();
ServiceInstance instance = loadBalancerClient.choose(serviceId);
String reconstructedUrl = loadBalancerClient.reconstructURI(instance, originalUri).toString();
Request newRequest = Request.create(request.httpMethod(), reconstructedUrl,
request.headers(), request.requestBody());
return delegate.execute(newRequest, options);
}
}Response Decoder Handling
Default encoder implementation.
class Default implements Encoder {
@Override
public void encode(Object object, Type bodyType, RequestTemplate template) {
if (bodyType == String.class) {
template.body(object.toString());
} else if (bodyType == byte[].class) {
template.body((byte[]) object, null);
} else if (object != null) {
throw new EncodeException(String.format("%s is not a type supported by this encoder.", object.getClass()));
}
}
}Default error decoder implementation.
public static class Default implements ErrorDecoder {
private final RetryAfterDecoder retryAfterDecoder = new RetryAfterDecoder();
@Override
public Exception decode(String methodKey, Response response) {
FeignException exception = errorStatus(methodKey, response);
Date retryAfter = retryAfterDecoder.apply(firstOrNull(response.headers(), RETRY_AFTER));
if (retryAfter != null) {
return new RetryableException(response.status(), exception.getMessage(),
response.request().httpMethod(), exception, retryAfter, response.request());
}
return exception;
}
private <T> T firstOrNull(Map<String, Collection<T>> map, String key) {
if (map.containsKey(key) && !map.get(key).isEmpty()) {
return map.get(key).iterator().next();
}
return null;
}
}Injecting a custom ErrorDecoder is common practice.
The above describes OpenFeign’s request processing flow; the following explains how spring-cloud-open-feign initializes and runs.
Extension: Spring Cloud OpenFeign
EnableFeignClients Parsing
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
}Adding @EnableFeignClients on the main class activates spring-cloud-open-feign features.
The Import(FeignClientsRegistrar.class) imports FeignClientsRegistrar, which scans for @FeignClient and registers them in the container.
FeignClientsRegistrar
class FeignClientsRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
registerFeignClients(metadata, registry);
}
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// scan base packages for @FeignClient and register them
}
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata,
Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
// set properties like url, path, name, contextId, type, decode404, fallback, fallbackFactory
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
// register bean definition
}
}Default Configuration
public class FeignAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass("feign.hystrix.HystrixFeign")
protected static class DefaultFeignTargeterConfiguration {
@Bean
@ConditionalOnMissingBean
public Targeter feignTargeter() {
return new DefaultTargeter();
}
}
}If the feign-hystrix module is not included, the flow remains the same as the original; calling a Feign client method triggers the dynamic proxy and MethodHandler.
HystrixFeign
public final class HystrixFeign {
public static Builder builder() {
return new Builder();
}
public static final class Builder extends Feign.Builder {
@Override
public Feign build(FallbackFactory<?> nullableFallbackFactory) {
super.invocationHandlerFactory(new InvocationHandlerFactory() {
@Override
public InvocationHandler create(Target target, Map<Method, MethodHandler> dispatch) {
return new HystrixInvocationHandler(target, dispatch, setterFactory, nullableFallbackFactory);
}
});
super.contract(new HystrixDelegatingContract(contract));
return super.build();
}
}
}HystrixInvocationHandler wraps method execution with a HystrixCommand.
final class HystrixInvocationHandler implements InvocationHandler {
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
HystrixCommand<Object> hystrixCommand = new HystrixCommand<Object>(setterMethodMap.get(method)) {
@Override
protected Object run() throws Exception {
return HystrixInvocationHandler.this.dispatch.get(method).invoke(args);
}
};
return hystrixCommand.execute();
}
}SentinelFeign
/** {@link Feign.Builder} like {@link HystrixFeign.Builder}. */
public final class SentinelFeign {
}SentinelInvocationHandler wraps requests with Sentinel for flow control and fallback.
public class SentinelInvocationHandler implements InvocationHandler {
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
try {
ContextUtil.enter(resourceName);
entry = SphU.entry(resourceName, EntryType.OUT, 1, args);
result = methodHandler.invoke(args);
} catch (Throwable ex) {
// fallback logic
} finally {
ContextUtil.exit();
}
return result;
}
}Summary Sequence Diagram
Future Plans
Upcoming articles will cover Ribbon, Hystrix, Sentinel, Nacos and other components with source code analysis.
Image resources are available from the public account “JAVA架构日记”.
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.
Java Architecture Diary
Committed to sharing original, high‑quality technical articles; no fluff or promotional content.
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.
