1. Introduction
In this short tutorial, we'll show how to dynamically autowire a bean in Spring.
We'll start by presenting a real-world use case where dynamic autowiring might be helpful. In addition to this, we'll show how to solve it in Spring in two different ways.
2. Dynamic Autowiring Use Cases
Dynamic autowiring is helpful in places where we need to dynamically change the Spring's bean execution logic. It's practical especially in places where what code to execute is chosen based on some runtime variables.
To demonstrate a real-world use case, let's create an application that controls servers in different regions of the world. For this reason, we've created an interface with two simple methods:
public interface RegionService { boolean isServerActive(int serverId); String getISOCountryCode(); }
and two implementations:
@Service("GBregionService") public class GBRegionService implements RegionService { @Override public boolean isServerActive(int serverId) { return false; } @Override public String getISOCountryCode() { return "GB"; } }
@Service("USregionService") public class USRegionService implements RegionService { @Override public boolean isServerActive(int serverId) { return true; } @Override public String getISOCountryCode() { return "US"; } }
Let's say we have a website where a user has an option to check whether the server is active in the selected region. Consequently, we'd like to have a service class that dynamically changes the RegionService interface implementation given the input of the user. Undoubtedly, this is the use case where dynamic bean autowiring comes into play.
3. Using BeanFactory
BeanFactory is a root interface for accessing a Spring bean container. In particular, it contains useful methods to obtain specific beans. Since BeanFactory is also a Spring bean, we can autowire and use it directly in our class:
@Service public class BeanFactoryDynamicAutowireService { private static final String SERVICE_NAME_SUFFIX = "regionService"; private final BeanFactory beanFactory; @Autowired public BeanFactoryDynamicAutowireService(BeanFactory beanFactory) { this.beanFactory = beanFactory; } public boolean isServerActive(String isoCountryCode, int serverId) { RegionService service = beanFactory.getBean(getRegionServiceBeanName(isoCountryCode), RegionService.class); return service.isServerActive(serverId); } private String getRegionServiceBeanName(String isoCountryCode) { return isoCountryCode + SERVICE_NAME_SUFFIX; } }
We've used an overloaded version of the getBean() method to get the bean with the given name and desired type.
And while this works, we'd really rather rely on something more idiomatic; that is, something that uses dependency injection.
4. Using Interfaces
To solve this with dependency injection, we'll rely on one of Spring's lesser-known features.
Besides standard single-field autowiring, Spring gives us an ability to collect all beans that are implementations of the specific interface into a Map:
@Service public class CustomMapFromListDynamicAutowireService { private final Map<String, RegionService> servicesByCountryCode; @Autowired public CustomMapFromListDynamicAutowireService(List<RegionService> regionServices) { servicesByCountryCode = regionServices.stream() .collect(Collectors.toMap(RegionService::getISOCountryCode, Function.identity())); } public boolean isServerActive(String isoCountryCode, int serverId) { RegionService service = servicesByCountryCode.get(isoCountryCode); return service.isServerActive(serverId); } }
We've created a map in a constructor that holds implementations by their country code. Furthermore, we can use it later in a method to get a particular implementation to check whether a given server is active in a specific region.
5. Conclusion
In this quick tutorial, we've seen how to dynamically autowire a bean in Spring using two different approaches.
As always, the code shown in this article is available over on GitHub.