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.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
How Spring Cloud Feign Works: Enabling, Bean Registration, and Proxy Injection

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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaMicroservicesBackend Developmentfeigndependency-injectionSpring CloudDynamic Proxy
Spring Full-Stack Practical Cases
Written by

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.

0 followers
Reader feedback

How this landed with the community

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.