CoApi Internal Architecture: How AbstractCoApiRegistrar and CoApiFactoryBean Build Spring HTTP Clients
CoApi uses a four-component Spring registration pipeline where AbstractCoApiRegistrar detects client modes and delegates to CoApiRegistrar, which registers CoApiFactoryBean instances that ultimately generate JDK proxies for your annotated interfaces.
The ahoo-wang/coapi library transforms plain Kotlin or Java interfaces into fully managed Spring HTTP clients through a lightweight, annotation-driven registration pipeline. Understanding the CoApi internal architecture—specifically how AbstractCoApiRegistrar coordinates with CoApiFactoryBean—is essential for debugging configuration issues or extending the framework with custom adapters.
The Four Core Components of CoApi
The framework's spring module implements a chain-of-responsibility pattern across four specialized classes. Each component handles a distinct phase of the bean lifecycle, from classpath scanning to proxy instantiation.
AbstractCoApiRegistrar
AbstractCoApiRegistrar serves as the entry point by implementing Spring’s ImportBeanDefinitionRegistrar interface. Located in spring/src/main/kotlin/me/ahoo/coapi/spring/AbstractCoApiRegistrar.kt, this abstract class orchestrates the initial setup phase.
When registerBeanDefinitions() is invoked, the registrar performs three critical tasks:
- Infers the ClientMode by calling
ClientMode.inferClientMode(environment), which checks properties likecoapi.client-modeto determine whether to use synchronous (sync) or reactive (reactive) HTTP clients. - Registers the appropriate HttpExchangeAdapterFactory (
SyncHttpExchangeAdapterFactoryorReactiveHttpExchangeAdapterFactory) as a Spring bean. - Delegates definition processing by calling the abstract
getCoApiDefinitions()method, which concrete subclasses implement to scan for@CoApiannotated interfaces.
The registrar then passes the resulting set of CoApiDefinition objects to CoApiRegistrar for actual bean registration.
CoApiRegistrar
CoApiRegistrar (defined in spring/src/main/kotlin/me/ahoo/coapi/spring/CoApiRegistrar.kt) acts as the construction coordinator. It accepts a BeanDefinitionRegistry and the resolved ClientMode, then iterates over each CoApiDefinition to register two beans per definition:
- The HTTP client bean: Either
RestClientFactoryBean(sync) orWebClientFactoryBean(reactive) - The API client bean: A
CoApiFactoryBeanthat manufactures the final proxy
Using Spring’s BeanDefinitionBuilder, CoApiRegistrar programmatically adds these definitions to the container, logs registration steps, and skips duplicates to prevent bean overriding conflicts.
CoApiFactoryBean
CoApiFactoryBean implements Spring’s FactoryBean interface to produce the actual client instance. When Spring requests the bean via getObject(), the factory (located in spring/src/main/kotlin/me/ahoo/coapi/spring/CoApiFactoryBean.kt) executes a three-step construction process:
- Retrieves the HttpExchangeAdapterFactory from the bean factory based on the current
ClientMode. - Creates an HttpExchangeAdapter and builds a
HttpServiceProxyFactoryusing Spring’s HTTP interface infrastructure. - Generates the proxy by calling
HttpServiceProxyFactory.createClient(apiType), returning a concrete implementation of the annotated interface that routes method calls through the underlying HTTP client.
This approach allows the resulting client to behave like a standard Spring bean, supporting dependency injection, scoping, and AOP proxies.
CoApiDefinition
CoApiDefinition (found in spring/src/main/kotlin/me/ahoo/coapi/spring/CoApiDefinition.kt) is an immutable data class that encapsulates the metadata required to construct a client. It stores the API name, interface type (apiType), base URL, load-balanced flag, and lazily computed bean names (httpClientBeanName, coApiBeanName).
The class provides a static extension function Class<*>.toCoApiDefinition(Environment) that extracts @CoApi and optional @LoadBalanced annotations, resolves property placeholders (e.g., ${github.url}), and produces a fully resolved definition ready for registration.
Step-by-Step Registration Flow
The CoApi internal architecture follows a strict lifecycle from annotation detection to proxy creation:
-
Interface Discovery: A concrete subclass of
AbstractCoApiRegistrar(such asEnableCoApiRegistrar) scans the configured base packages and converts each@CoApiinterface into aCoApiDefinitionusingtoCoApiDefinition(env). -
Mode Detection:
AbstractCoApiRegistrarexamines the SpringEnvironmentto determineClientMode, registering the correspondingHttpExchangeAdapterFactorybean. -
Bean Registration:
CoApiRegistrarreceives the definitions and registers both the low-level HTTP client factory and the high-levelCoApiFactoryBeanfor each API interface. -
Proxy Instantiation: When application code requests the client bean, Spring invokes
CoApiFactoryBean.getObject(), which assembles theHttpServiceProxyFactoryand generates the JDK proxy that implements your interface.
All components participate in Spring’s standard bean-definition lifecycle, ensuring compatibility with @Autowired, @Qualifier, and custom bean post-processors.
Configuration Examples
Defining a CoApi Interface
Mark an interface with @CoApi to trigger processing. The serviceId attribute enables Spring Cloud LoadBalancer integration when combined with @LoadBalanced.
package me.ahoo.coapi.example
import me.ahoo.coapi.api.CoApi
import me.ahoo.coapi.api.LoadBalanced
@CoApi(serviceId = "github-service")
@LoadBalanced
interface GitHubSyncClient {
fun getIssue(owner: String, repo: String, number: Long): Issue
}
Internally, CoApiDefinition.toCoApiDefinition(env) converts this to a definition with baseUrl = "lb://github-service" and loadBalanced = true.
Enabling CoApi in Spring
Apply @EnableCoApi to a configuration class. This annotation imports EnableCoApiRegistrar, which extends AbstractCoApiRegistrar to scan the specified packages.
import me.ahoo.coapi.spring.EnableCoApi
import org.springframework.context.annotation.Configuration
@Configuration
@EnableCoApi(
scanBasePackages = ["me.ahoo.coapi.example"]
)
class CoApiConfig
Injecting the Generated Client
Inject the interface directly. Spring resolves the bean from CoApiFactoryBean and routes calls through the appropriate HTTP client.
import org.springframework.stereotype.Service
@Service
class IssueService(private val gitHubSyncClient: GitHubSyncClient) {
fun fetchIssue(owner: String, repo: String, number: Long): Issue =
gitHubSyncClient.getIssue(owner, repo, number)
}
Configuring Client Mode
Control the underlying HTTP client implementation via properties. AbstractCoApiRegistrar reads this during registerBeanDefinitions().
coapi.client-mode=reactive
Valid values are sync (uses RestClient) or reactive (uses WebClient).
Summary
- AbstractCoApiRegistrar implements
ImportBeanDefinitionRegistrarto detectClientModefrom the environment and trigger the registration pipeline. - CoApiRegistrar registers two beans per API definition: the HTTP client factory (
RestClientFactoryBeanorWebClientFactoryBean) and the proxy factory (CoApiFactoryBean). - CoApiFactoryBean creates the final proxy by assembling
HttpServiceProxyFactoryand invokingcreateClient(apiType). - CoApiDefinition captures metadata from
@CoApiannotations and resolves property placeholders usingtoCoApiDefinition(Environment). - The entire architecture leverages standard Spring bean lifecycle hooks, allowing CoApi clients to participate in dependency injection and AOP infrastructure.
Frequently Asked Questions
What is the role of AbstractCoApiRegistrar in CoApi?
AbstractCoApiRegistrar acts as the bootstrap component that integrates CoApi with Spring's bean registration phase. It implements ImportBeanDefinitionRegistrar to hook into the @Configuration processing lifecycle, determines whether to use synchronous or reactive HTTP clients by inspecting ClientMode.inferClientMode(environment), and delegates the actual bean registration work to CoApiRegistrar. It also registers the appropriate HttpExchangeAdapterFactory bean before processing individual API definitions.
How does CoApiFactoryBean create the HTTP client proxy?
CoApiFactoryBean implements Spring's FactoryBean interface to produce the client instance lazily. When Spring calls getObject(), the factory retrieves the previously registered HttpExchangeAdapterFactory, creates an HttpExchangeAdapter, and uses it to build a HttpServiceProxyFactory. It then calls createClient(apiType) on the proxy factory to generate a JDK dynamic proxy that implements the annotated interface, routing all method invocations through the configured HTTP client.
How does CoApi determine whether to use sync or reactive mode?
During bean registration, AbstractCoApiRegistrar invokes ClientMode.inferClientMode(environment) to read the coapi.client-mode property from the Spring Environment. If set to reactive, the registrar registers ReactiveHttpExchangeAdapterFactory and WebClientFactoryBean; if set to sync, it registers SyncHttpExchangeAdapterFactory and RestClientFactoryBean. This decision propagates through CoApiRegistrar to CoApiFactoryBean, ensuring consistent client behavior.
What is CoApiDefinition and how is it created?
CoApiDefinition is an immutable data class defined in spring/src/main/kotlin/me/ahoo/coapi/spring/CoApiDefinition.kt that stores metadata about a CoApi interface, including the API type, base URL, service ID, and load-balanced status. It is created through the extension function Class<*>.toCoApiDefinition(Environment), which scans the class for @CoApi and @LoadBalanced annotations, resolves placeholder expressions like ${service.url}, and constructs the definition object passed to CoApiRegistrar for bean creation.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →