Backend Development 21 min read

How OpenFeign Builds Dynamic Proxies in Spring Cloud – A Deep Dive

This article dissects the inner workings of OpenFeign within Spring Cloud, explaining how @EnableFeignClients triggers bean registration, how FeignClientsRegistrar and related configuration classes create BeanDefinitions, and how the FeignBuilder ultimately generates dynamic proxy instances for Feign clients.

Sanyou's Java Diary
Sanyou's Java Diary
Sanyou's Java Diary
How OpenFeign Builds Dynamic Proxies in Spring Cloud – A Deep Dive

1. Role of @EnableFeignClients

Using @EnableFeignClients activates the Feign entry point. The annotation imports FeignClientsRegistrar via @Import, which implements ImportBeanDefinitionRegistrar . During Spring Boot startup, registerBeanDefinitions is called to inject beans dynamically.

<code>@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {}
</code>

The registrar parses the annotation attributes, scans for interfaces annotated with @FeignClient, and registers a FeignClientFactoryBean for each.

2. Scanning and Registering Feign Clients

<code>@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    // parse @EnableFeignClients attributes
    registerDefaultConfiguration(metadata, registry);
    registerFeignClients(metadata, registry);
}
</code>

During scanning, a ClassPathScanningCandidateComponentProvider is configured to find @FeignClient‑annotated interfaces, creating a BeanDefinition for each.

<code>public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    ClassPathScanningCandidateComponentProvider scanner = getScanner();
    scanner.setResourceLoader(this.resourceLoader);
    // determine base packages
    // include filter for @FeignClient
    // iterate candidate components and register them
}
</code>

Each discovered interface is validated to be an interface, its attributes are extracted, and two beans are registered:

FeignClientSpecification – holds client‑specific configuration.

FeignClientFactoryBean – creates the dynamic proxy.

3. Registering Client Configuration

<code>private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name, Object configuration) {
    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class);
    builder.addConstructorArgValue(name);
    builder.addConstructorArgValue(configuration);
    registry.registerBeanDefinition(name + "." + FeignClientSpecification.class.getSimpleName(), builder.getBeanDefinition());
}
</code>

This wraps the configuration class specified in @FeignClient into a FeignClientSpecification bean.

4. Building the Feign Client Factory Bean

<code>private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
    String className = annotationMetadata.getClassName();
    BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
    validate(attributes);
    definition.addPropertyValue("url", getUrl(attributes));
    definition.addPropertyValue("path", getPath(attributes));
    definition.addPropertyValue("name", getName(attributes));
    // other properties …
    definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
    BeanDefinitionHolder holder = new BeanDefinitionHolder(definition.getBeanDefinition(), className, new String[]{alias});
    BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}
</code>

The factory bean implements FactoryBean ; Spring calls its getObject() to obtain the proxy.

<code>@Override
public Object getObject() throws Exception {
    return getTarget();
}
</code>

5. Creating the Proxy Instance

<code>private <T> T getTarget() {
    FeignContext context = this.applicationContext.getBean(FeignContext.class);
    Feign.Builder builder = feign(context);
    // resolve URL, load‑balance, etc.
    Targeter targeter = get(context, Targeter.class);
    return targeter.target(this, builder, context, new HardCodedTarget<>(this.type, this.name, url));
}
</code>

The Feign.Builder is populated with components (encoder, decoder, contract, logger, etc.) from the client‑specific FeignContext , which isolates configuration per client.

<code>protected Feign.Builder feign(FeignContext context) {
    Logger logger = get(context, FeignLoggerFactory.class).create(this.type);
    return get(context, Feign.Builder.class)
        .logger(logger)
        .encoder(get(context, Encoder.class))
        .decoder(get(context, Decoder.class))
        .contract(get(context, Contract.class));
}
</code>

After configuration, builder.target() builds a ReflectiveFeign instance, which uses JDK dynamic proxies to implement the client interface.

<code>public <T> T target(Target<T> target) {
    return build().newInstance(target);
}

public Feign build() {
    // assemble handlers, logger, retryer, etc.
    return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}
</code>

The ReflectiveFeign creates a Proxy that delegates method calls to MethodHandler implementations generated from the Feign contract.

6. Configuration Isolation with NamedContextFactory

Each Feign client gets its own AnnotationConfigApplicationContext via NamedContextFactory . This isolates client‑specific beans while sharing a parent Spring Boot context.

<code>public class NamedContextFactory<C extends NamedContextFactory.Specification> implements DisposableBean, ApplicationContextAware {
    private final String propertySourceName;
    private final String propertyName;
    private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();
    private Map<String, C> configurations = new ConcurrentHashMap<>();
    // … methods to create and retrieve contexts …
}
</code>

The factory registers client‑specific configuration classes ( FeignClientSpecification ) and a default configuration ( FeignClientsConfiguration ).

7. Core Beans in FeignClientsConfiguration

@Bean public Contract feignContract(...) – parses Spring MVC annotations on Feign interfaces.

@Bean @Scope("prototype") public Feign.Builder feignBuilder(Retryer retryer) – builds the Feign client.

Conditional beans for Hystrix, Sentinel, etc., enable optional features when corresponding properties are set.

8. Summary

The article explains how @EnableFeignClients triggers scanning of @FeignClient interfaces, registers FeignClientFactoryBean and FeignClientSpecification beans, isolates each client’s configuration with FeignContext , and finally constructs a JDK dynamic proxy via Feign.Builder and ReflectiveFeign . This deep dive clarifies the end‑to‑end process of turning a simple interface into a fully‑featured HTTP client in Spring Cloud.

Backend DevelopmentSpring CloudDynamic ProxyOpenFeignFeignClient
Sanyou's Java Diary
Written by

Sanyou's Java Diary

Passionate about technology, though not great at solving problems; eager to share, never tire of learning!

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.