# CoApi Internal Architecture: How AbstractCoApiRegistrar and CoApiFactoryBean Build Spring HTTP Clients

> Explore CoApi's internal architecture. Learn how AbstractCoApiRegistrar and CoApiFactoryBean build Spring HTTP clients using JDK proxies for annotated interfaces.

- Repository: [Ahoo Wang/coapi](https://github.com/ahoo-wang/coapi)
- Tags: internals
- Published: 2026-02-23

---

**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`](https://github.com/ahoo-wang/coapi/blob/main/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:

1. **Infers the ClientMode** by calling `ClientMode.inferClientMode(environment)`, which checks properties like `coapi.client-mode` to determine whether to use synchronous (`sync`) or reactive (`reactive`) HTTP clients.
2. **Registers the appropriate HttpExchangeAdapterFactory** (`SyncHttpExchangeAdapterFactory` or `ReactiveHttpExchangeAdapterFactory`) as a Spring bean.
3. **Delegates definition processing** by calling the abstract `getCoApiDefinitions()` method, which concrete subclasses implement to scan for `@CoApi` annotated 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`](https://github.com/ahoo-wang/coapi/blob/main/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) or `WebClientFactoryBean` (reactive)
- **The API client bean**: A `CoApiFactoryBean` that 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`](https://github.com/ahoo-wang/coapi/blob/main/spring/src/main/kotlin/me/ahoo/coapi/spring/CoApiFactoryBean.kt)) executes a three-step construction process:

1. **Retrieves the HttpExchangeAdapterFactory** from the bean factory based on the current `ClientMode`.
2. **Creates an HttpExchangeAdapter** and builds a `HttpServiceProxyFactory` using Spring’s HTTP interface infrastructure.
3. **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`](https://github.com/ahoo-wang/coapi/blob/main/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:

1. **Interface Discovery**: A concrete subclass of `AbstractCoApiRegistrar` (such as `EnableCoApiRegistrar`) scans the configured base packages and converts each `@CoApi` interface into a `CoApiDefinition` using `toCoApiDefinition(env)`.

2. **Mode Detection**: `AbstractCoApiRegistrar` examines the Spring `Environment` to determine `ClientMode`, registering the corresponding `HttpExchangeAdapterFactory` bean.

3. **Bean Registration**: `CoApiRegistrar` receives the definitions and registers both the low-level HTTP client factory and the high-level `CoApiFactoryBean` for each API interface.

4. **Proxy Instantiation**: When application code requests the client bean, Spring invokes `CoApiFactoryBean.getObject()`, which assembles the `HttpServiceProxyFactory` and 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`.

```kotlin
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.

```kotlin
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.

```kotlin
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()`.

```properties
coapi.client-mode=reactive

```

Valid values are `sync` (uses `RestClient`) or `reactive` (uses `WebClient`).

## Summary

- **AbstractCoApiRegistrar** implements `ImportBeanDefinitionRegistrar` to detect `ClientMode` from the environment and trigger the registration pipeline.
- **CoApiRegistrar** registers two beans per API definition: the HTTP client factory (`RestClientFactoryBean` or `WebClientFactoryBean`) and the proxy factory (`CoApiFactoryBean`).
- **CoApiFactoryBean** creates the final proxy by assembling `HttpServiceProxyFactory` and invoking `createClient(apiType)`.
- **CoApiDefinition** captures metadata from `@CoApi` annotations and resolves property placeholders using `toCoApiDefinition(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`](https://github.com/ahoo-wang/coapi/blob/main/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.