Skip to content

Repository files navigation

spring-dynamic-beans-library

Companion code for the article "Mastering Dynamic Spring Beans: Building Plug-and-Play Enterprise Libraries" — a practical example of building a reusable, configuration-driven Spring library that registers beans dynamically from application.yml and integrates fully with the Spring lifecycle.

⚠️ Placeholder coordinates. This repository intentionally uses com.example:spring-dynamic-beans-library and the com.example.dynamicbeans package. Replace them with your organization's coordinates before publishing the library to a real repository.

What it demonstrates

  • Dynamic multi-instance bean registration: BeanDefinitionRegistryPostProcessor
    • registerBeanDefinition() turn a YAML map into N named Spring beans, injectable via @Qualifier("<name>") — the same pattern Spring Data uses.
  • Resource-safe lifecycle: native resources (the AWS SDK's connection pools and threads) are registered as separate beans with destroy-method: close, so Spring shuts them down cleanly — no leaked sockets on redeploy.
  • Non-bootable library packaging: no spring-boot-starter-parent, no spring-boot-maven-plugin, Spring dependencies in provided scope.
  • Opt-in activation: @ConditionalOnProperty gates the whole factory behind .enabled=true.

Modules

Artifact Pattern shown
dynamic-beans-s3 Two beans per configured client (SDK client with destroy method + wrapper) — the heavyweight variant
dynamic-beans-keyvault One bean per configured vault, no destroy method needed — the lightweight variant
dynamic-beans-kafka N producers/consumers/listener containers from YAML, no @KafkaListener — the containers variant
dynamic-beans-alfresco N named REST clients behind one interface (RestClient built in-constructor, no destroy method) — the article's original case study

Import only what you need

The library is a Maven reactor of four independent artifacts (they share no code). Depend on the module you use — and only on it:

<!-- pick one or more -->
<dependency>
    <groupId>com.example</groupId>
    <artifactId>dynamic-beans-s3</artifactId>
    <version>0.1.0-SNAPSHOT</version>
</dependency>
<dependency>
    <groupId>com.example</groupId>
    <artifactId>dynamic-beans-keyvault</artifactId>
    <version>0.1.0-SNAPSHOT</version>
</dependency>
<dependency>
    <groupId>com.example</groupId>
    <artifactId>dynamic-beans-kafka</artifactId>
    <version>0.1.0-SNAPSHOT</version>
</dependency>
<dependency>
    <groupId>com.example</groupId>
    <artifactId>dynamic-beans-alfresco</artifactId>
    <version>0.1.0-SNAPSHOT</version>
</dependency>

Each artifact brings its own technology dependency — a compile-scope SDK, or provided spring-web in the alfresco module, whose "SDK" is Spring's own RestClient — and its own auto-configuration manifest, gated twice: @ConditionalOnClass (technology on the classpath?) and @ConditionalOnProperty (feature enabled?). Nothing activates until you configure it.

Configuration

See docs/application.yml.sample. Each entry under aws.s3.clients produces two beans:

aws:
  s3:
    enabled: true
    clients:
      primaryStorage:        # -> beans "primaryStorage" and "primaryStorage-sdkClient"
        enabled: true
        region: us-east-1
        bucketName: example-bucket
        presignedUrlDuration: 24h
@Service
public class DocumentService {
    private final AmazonS3Client storage;

    // Explicit constructor: Lombok's @RequiredArgsConstructor would drop @Qualifier.
    public DocumentService(@Qualifier("primaryStorage") AmazonS3Client storage) {
        this.storage = storage;
    }
}

Two flavors of the same pattern

The S3 and Key Vault modules are the same pattern with one deliberate difference — resource lifecycle:

  • S3 (heavyweight client): the AWS SDK S3Client owns connection pools and threads, so the factory registers TWO beans per client — the SDK client with destroy-method: close, and the wrapper referencing it.
  • Key Vault (lightweight client): the Azure SecretClient holds no native resources requiring an explicit close, so the factory registers ONE bean per vault and no destroy method.

Always ask: "does this resource need explicit cleanup?" — that answer, not habit, decides the registration shape. And if a starter already gives you the single client you need (e.g. Spring Cloud Azure's autoconfigured SecretClient when one vault is enough), you may not need dynamic registration at all: reach for BeanDefinitionRegistryPostProcessor when the bean COUNT depends on configuration.

Build

Requires Java 21 and Maven 3.9+. The build is a multi-module reactor:

mvn -B clean verify            # builds and tests all modules
mvn -B clean verify -pl dynamic-beans-kafka   # one module only

License

Copyright 2026 Sourcesense.

Licensed under the Apache License, Version 2.0. You may obtain a copy of the License in the LICENSE file or at https://www.apache.org/licenses/LICENSE-2.0.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages