How Spring Cloud Feign Works: Enabling, Bean Registration, and Proxy Injection
This article walks through the complete lifecycle of Spring Cloud Feign in a Spring Boot 2.2.13 application, covering how to enable Feign, scan and register @FeignClient beans, inject them into controllers, create dynamic proxy instances, and finally execute remote calls with load‑balancing and Hystrix support.
1. Enable Feign
Set the environment to springboot2.2.13.RELEASE + springcloud Hoxton.SR8 and annotate the main application class with @SpringCloudApplication, @ServletComponentScan, and @EnableFeignClients("com.pack.feign"). The class extends SpringBootServletInitializer and overrides configure to return the application sources.
@SpringCloudApplication
@ServletComponentScan
@EnableFeignClients("com.pack.feign")
public class BaseApplication extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(BaseApplication.class);
}
public static void main(String[] args) {
SpringApplication.run(BaseApplication.class, args);
}
}Inspecting @EnableFeignClients reveals it is meta‑annotated with @Import(FeignClientsRegistrar.class), which triggers the registration process.
2. Register FeignClient Beans
The FeignClientsRegistrar implements ImportBeanDefinitionRegistrar. Its registerBeanDefinitions method first calls registerDefaultConfiguration and then registerFeignClients to scan the specified packages for interfaces annotated with @FeignClient.
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
registerDefaultConfiguration(metadata, registry);
registerFeignClients(metadata, registry);
}The registerFeignClients method creates a ClassPathScanningCandidateComponentProvider, configures it to include only types annotated with @FeignClient, determines the base packages (either from the annotation attributes or from explicit client classes), and iterates over candidate components. For each candidate, it validates that the type is an interface, extracts annotation attributes, registers client configuration, and finally registers a FeignClientFactoryBean definition.
public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
ClassPathScanningCandidateComponentProvider scanner = getScanner();
scanner.setResourceLoader(this.resourceLoader);
// obtain attributes from @EnableFeignClients
Map<String, Object> attrs = metadata.getAnnotationAttributes(EnableFeignClients.class.getName());
AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(FeignClient.class);
// ...determine basePackages and filters...
for (String basePackage : basePackages) {
Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(basePackage);
for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface");
Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes(FeignClient.class.getCanonicalName());
String name = getClientName(attributes);
registerClientConfiguration(registry, name, attributes.get("configuration"));
registerFeignClient(registry, annotationMetadata, attributes);
}
}
}
}The core registration method creates a BeanDefinitionBuilder for FeignClientFactoryBean, sets properties such as url, path, name, contextId, type, fallback settings, and registers the bean definition with the registry.
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));
String name = getName(attributes);
definition.addPropertyValue("name", name);
String contextId = getContextId(attributes);
definition.addPropertyValue("contextId", contextId);
definition.addPropertyValue("type", className);
// ...other properties and autowire mode...
BeanDefinitionHolder holder = new BeanDefinitionHolder(
definition.getBeanDefinition(), className, new String[] { contextId + "FeignClient" });
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
}3. Feign Injection into Controllers
When a controller declares a field of the Feign client interface annotated with @Resource, Spring’s CommonAnnotationBeanPostProcessor processes the injection. The bean factory calls populateBean, which iterates over BeanPostProcessor implementations. The Feign post‑processor injects the proxy instance into the target field via reflection.
protected void populateBean(String beanName, RootBeanDefinition mbd, BeanWrapper bw) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof InstantiationAwareBeanPostProcessor) {
InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
// ...
}
}
}The actual injection logic resides in CommonAnnotationBeanPostProcessor.inject, which obtains the resource (the Feign proxy) and sets it on the target field.
public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
for (InjectedElement element : elementsToIterate) {
element.inject(target, beanName, pvs);
}
}4. Creating the Feign Proxy
The FeignClientFactoryBean implements FactoryBean. Its getObject method builds a Feign.Builder from the FeignContext, resolves the target URL, selects an appropriate Client (unwrapping load‑balancer wrappers if necessary), and finally delegates to a Targeter to create the proxy.
@Override
public Object getObject() throws Exception {
return getTarget();
}
<T> T getTarget() {
FeignContext context = applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);
// resolve URL and client
// ...
Targeter targeter = get(context, Targeter.class);
return (T) targeter.target(this, builder, context, new HardCodedTarget<>(type, name, url));
}The Targeter uses ReflectiveFeign to generate a JDK dynamic proxy. Method handlers are prepared for each interface method; default methods receive a DefaultMethodHandler, while others use the mapping from Feign.configKey to MethodHandler. The resulting proxy is returned and later injected.
public <T> T newInstance(Target<T> target) {
Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
Map<Method, MethodHandler> methodToHandler = new LinkedHashMap<>();
List<DefaultMethodHandler> defaultMethodHandlers = new LinkedList<>();
for (Method method : target.type().getMethods()) {
if (method.getDeclaringClass() == Object.class) continue;
if (Util.isDefault(method)) {
DefaultMethodHandler handler = new DefaultMethodHandler(method);
defaultMethodHandlers.add(handler);
methodToHandler.put(method, handler);
} else {
methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));
}
}
InvocationHandler handler = factory.create(target, methodToHandler);
T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[] {target.type()}, handler);
for (DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {
defaultMethodHandler.bindTo(proxy);
}
return proxy;
}5. Invoking a Feign Interface
All remote calls go through HystrixInvocationHandler.invoke. It obtains the appropriate MethodHandler (often a SynchronousMethodHandler) and executes it. The handler builds a RequestTemplate, applies retry logic, and finally calls executeAndDecode on the underlying Client. Load‑balancing is applied when a LoadBalancerFeignClient is present.
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
// ...handle equals, hashCode, toString...
HystrixCommand<Object> hystrixCommand = new HystrixCommand<>(setterMethodMap.get(method)) {
@Override
protected Object run() throws Exception {
return HystrixInvocationHandler.this.dispatch.get(method).invoke(args);
}
@Override
protected Object getFallback() {
// fallback handling
}
};
return hystrixCommand.execute();
}The SynchronousMethodHandler.invoke creates the request, logs it, executes it via the client, decodes the response (or handles it asynchronously), and returns the result. This completes the end‑to‑end flow from enabling Feign to executing a remote HTTP call.
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.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
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.
