Getting Started with Spring Boot, Part 8 Configuring beans in Spring
03/15/2021
Author / Editor: Dr. Dirk Koller / Stephan Augsten
The dependency injection container is at the heart of Spring. This can manage various components, including, of course, Java Beans. These beans are configured in a contemporary and type-safe manner using a Java class.
Companies on the subject
Our example bean here is supposed to be a DatabasePersister, ie a class that stores data in a database. Because our hypothetical application could have different types of storage—such as a FilesystemPersister or a CloudPersister—the class implements the Persister interface with the persist method:
public interface Persister { public void persist();}
public class DatabasePersister implements Persister {
@Override public void persist() { System.out.println("In DatabasePersister persist"); }}
Spring Boot tutorial
Image gallery with 20 images
Configuration in the Java class
The database persistor is to be injected into another class, so it must be made known to the container. This is done in a Java class denoted by the @Configuration annotation and typically ending in Config. Internally, this class is marked with @Component and thus found and evaluated during component scanning:
@Configurationpublic class PersisterConfig {
}
In the class, the different beans are now made available through bean definition methods and these are marked with the @Bean annotation:
@Configurationpublic class PersisterConfig {
@Bean public Persister databasePersister() { return new DatabasePersister(); }}
The also possible configuration of components with the help of XML is no longer common today, but is still often found in existing projects.
Injecting the beans
A bean configured in this way can now be injected with Autowired as usual, for example in a service. Ideally, the service does not know the specific persistor, it works with the interface and calls the persist method defined there:
@Servicepublic class PersisterService {
@Autowired private persister persister;
public void persistData() { persister.persist(); }}
The container now looks for a bean with the right type, i.e. persistor, in the existing components when it is started and finds the configured DatabasePersister. Due to the implementation of the interface, it is a persister and is used as a candidate for injection.
When encountering problems, it is sometimes helpful to dump the beans in the container. The run method of the SpringApplication class in the main class returns an ApplicationContext that allows access to the configured beans or their names via the getBeanDefinitionNames() method:
@SpringBootApplicationpublic class DemoApplication {
private static ApplicationContext applicationContext; public static void main(String[] args) { applicationContext = SpringApplication.run(DemoApplication.class, args);> String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames(); for (String beanName : beanDefinitionNames) { System.out.println(beanName); } }}
The configured persister can then be found in the console output along with numerous other beans:
...
persisterService
personController
persisterConfig
databasePersister
...
You're spoiled for choice
It gets exciting when you include a second bean of the same interface type in the configuration, such as the FilesystemPersister. It also implements persisters:
public class FilesystemPersister implements Persister {
@Override public void persist() { System.out.println("In FilesystemPersister persist"); }}
The configuration in PersisterConfig is analogous:
@Configurationpublic class PersisterConfig {
@Bean public Persister databasePersister() { return new DatabasePersister(); }
@Bean public Persister filesystemPersister() { return new FilesystemPersister(); }}
When injecting the bean into the service, the container now faces a problem: which of the two persistors should it inject? Spring cannot solve the dilemma alone. The application no longer starts and instead reports an error message:
Field persister in com.example.demo.PersisterService required a single bean, but 2 were found: - databasePersister: defined by method 'databasePersister' in class path resource [com/example/demo/PersisterConfig.class] - filesystemPersister: defined by method 'filesystemPersister' in class path resource [com/example/demo/PersisterConfig.class]
Possible solutions to the problem are fortunately also included in the message:
Action: Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed
The conflict can be resolved using the @Primary or @Qualifier annotations. The @Primary annotation on one(!) of the two bean definition methods means that the corresponding bean is preferred. Of course, the configuration class must be accessible for this. This is not always the case when using external libraries.
An alternative is the @Qualifier annotation, which can be attached to the injection point, i.e. to the using class. By specifying the method name, preference is given to the FilesystemPersister in the PersisterService:
@Servicepublic class PersisterService {
@Qualifier("filesystemPersister") @Autowired private Persister persister;
public void persistData() { persister.persist(); }}
Spring Boot tutorial
Image gallery with 20 images
In another class, however, the DatabasePersister may be preferred. This pattern is often found when configuring multiple DataSources.
In the next part of the Spring Boot series, we'll look at the different ways Spring Boot can inject beans. Spoiler: The field injection used here is well suited for blog articles, but is only second choice in practice.
(ID:47119037)