You can create dynamic & configuration driven beans in Spring Boot
in following way.
Note: Below example full code is available at https://github.com/imran9m/spring-boot-dynamic-beans
For the example, Let’s create dynamic RestClient
beans with customized connection timeout and user-agent configuration to use anywhere we want.
- Let’s go with following configuration to manage customized properties for two dynamic
RestClient
beans.
application.yml
restClients:
clients:
- clientName: test1
connectionTimeout: 6000
responseTimeout: 6000
userAgent: test1
- clientName: test2
connectionTimeout: 5000
responseTimeout: 5000
userAgent: test2
- Now, let’s load this configuration into custom configuration properties record.
DynamicRestBuilderProperties.java
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.List;
@ConfigurationProperties(prefix = "rest-clients")
public record DynamicRestBuilderProperties(List<CustomClient> clients) {
public record CustomClient(String clientName, int connectionTimeout, int responseTimeout, String userAgent) {
}
}
- Let’s go ahead and create app config class to create custom and dynamic beans on
@PostConstruct
stage.
import jakarta.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestClient;
@Configuration
@EnableConfigurationProperties(DynamicRestBuilderProperties.class)
public class DemoConfig {
private static final Logger logger = LoggerFactory.getLogger(DemoConfig.class);
@Autowired
private DynamicRestBuilderProperties dynamicRestBuilderProperties;
@Autowired
private ConfigurableApplicationContext configurableApplicationContext;
@Autowired
private RestClient.Builder restClientBuilder;
public DemoConfig() {
logger.info("!!!!! DemoConfig Initialized !!!!");
}
@PostConstruct
public void init() {
ConfigurableListableBeanFactory beanFactory = this.configurableApplicationContext.getBeanFactory();
// iterate over properties and register new beans'
for (DynamicRestBuilderProperties.CustomClient client : dynamicRestBuilderProperties.clients()) {
RestClient tempClient = restClientBuilder
.clone()
.requestFactory(getClientHttpRequestFactory(client.connectionTimeout(), client.responseTimeout()))
.defaultHeader("user-agent", client.userAgent())
.build();
beanFactory.autowireBean(tempClient);
beanFactory.initializeBean(tempClient, client.clientName());
beanFactory.registerSingleton(client.clientName(), tempClient);
logger.info("{} bean created", client.clientName());
}
}
private ClientHttpRequestFactory getClientHttpRequestFactory(int connectionTimeout, int responseTimeout) {
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setReadTimeout(responseTimeout);
factory.setConnectTimeout(connectionTimeout);
return factory;
}
}
- Now, let’s create a sample controller to test these beans. By calling,
https://httpbin.org/user-agent
which returns ourRestClient
agent which we set during initialization.
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestClient;
@RestController
public class DemoController {
private static final Logger logger = LoggerFactory.getLogger(DemoController.class);
@Autowired
@Qualifier("test1")
private RestClient restClient;
@Autowired
@Qualifier("test2")
private RestClient restClient2;
public DemoController() {
}
@GetMapping("/test1")
public ResponseEntity<String> test1() {
return ResponseEntity.ok(restClient.get().uri("https://httpbin.org/user-agent").retrieve().body(String.class));
}
@GetMapping("/test2")
public ResponseEntity<String> test2() {
return ResponseEntity.ok(restClient2.get().uri("https://httpbin.org/user-agent").retrieve().body(String.class));
}
}
- When you build(
mvn clean install
) the code and run it(java -jar .\target\spring-boot-dynamic-beans-0.0.1-SNAPSHOT.jar
), we can see in bootup logs that beans are created.
PS C:\Users\Imran\Downloads\spring-boot-dynamic-beans> java -jar .\target\spring-boot-dynamic-beans-0.0.1-SNAPSHOT.jar
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.4.1)
2025-01-03T23:40:09.019-05:00 INFO 17700 --- [democontroller] [ main] c.e.d.DemocontrollerApplication : Starting DemocontrollerApplication v0.0.1-SNAPSHOT using Java 23.0.1 with PID 17700 (C:\Users\Imran\Downloads\spring-boot-dynamic-beans\target\spring-boot-dynamic-beans-0.0.1-SNAPSHOT.jar started by Imran in C:\Users\Imran\Downloads\spring-boot-dynamic-beans)
2025-01-03T23:40:09.022-05:00 INFO 17700 --- [democontroller] [ main] c.e.d.DemocontrollerApplication : No active profile set, falling back to 1 default profile: "default"
2025-01-03T23:40:09.816-05:00 INFO 17700 --- [democontroller] [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port 8080 (http)
2025-01-03T23:40:09.828-05:00 INFO 17700 --- [democontroller] [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2025-01-03T23:40:09.828-05:00 INFO 17700 --- [democontroller] [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.34]
2025-01-03T23:40:09.852-05:00 INFO 17700 --- [democontroller] [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2025-01-03T23:40:09.853-05:00 INFO 17700 --- [democontroller] [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 776 ms
2025-01-03T23:40:09.887-05:00 INFO 17700 --- [democontroller] [ main] com.example.democontroller.DemoConfig : !!!!! DemoConfig Initialized !!!!
2025-01-03T23:40:10.070-05:00 INFO 17700 --- [democontroller] [ main] com.example.democontroller.DemoConfig : test1 bean created
2025-01-03T23:40:10.072-05:00 INFO 17700 --- [democontroller] [ main] com.example.democontroller.DemoConfig : test2 bean created
2025-01-03T23:40:10.319-05:00 INFO 17700 --- [democontroller] [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8080 (http) with context path '/'
2025-01-03T23:40:10.332-05:00 INFO 17700 --- [democontroller] [ main] c.e.d.DemocontrollerApplication : Started DemocontrollerApplication in 1.712 seconds (process running for 2.108)
- Finally, when we test with following Urls, we should see corresponding responses.
GET http://localhost:8080/test1
HTTP/1.1 200
Content-Type: text/plain;charset=UTF-8
Content-Length: 28
Date: Sat, 04 Jan 2025 04:35:58 GMT
Connection: close
{
"user-agent": "test1"
}
GET http://localhost:8080/test2
HTTP/1.1 200
Content-Type: text/plain;charset=UTF-8
Content-Length: 28
Date: Sat, 04 Jan 2025 04:33:33 GMT
Connection: close
{
"user-agent": "test2"
}