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.
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.
Sanyou's Java Diary
Passionate about technology, though not great at solving problems; eager to share, never tire of learning!
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.