Updated Answer:
If you are creating dynamic beans from shared library OR want to avoid PostConstruct approach, go with @Piritz answer with BeanDefinitionRegistryPostProcessor which seems to be cleaner approach and potentially avoids circular dependency!!.
Original Answer:
Here is one way to create dynamic RestClients. There can be other optimized ways too but this is my take. Let me know your thoughts. FYI, an example GitHub repo and little bit more details are here.
application.yml
restClients:
clients:
- clientName: test1
connectionTimeout: 6000
responseTimeout: 6000
userAgent: test1
- clientName: test2
connectionTimeout: 5000
responseTimeout: 5000
userAgent: test2
Let's load the configuration into ConfigProperties 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) {
}
}
Config Class(DemoConfig.java) where we can create dynamic beans. By using clone() on Autowired RestClient.Builder bean you can retain all spring default auto configuration when creating new ones.
@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, you can use these dynamically created beans anywhere like following.
@Autowired
@Qualifier("test1")
private RestClient restClient;
@Autowired
@Qualifier("test2")
private RestClient restClient2;
restClient.get().uri("https://httpbin.org/user-agent").retrieve().body(String.class) //Should return test1
restClient2.get().uri("https://httpbin.org/user-agent").retrieve().body(String.class) //Should return test2