Spring Top

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE

1. Introduction

In this tutorial, we’re going to go through different variants of the BeanFactory.getBean() method.

Simply put, as the name of the method also suggests, this is responsible for retrieving a bean instance from the Spring container.

2. Spring Beans Setup

First, let’s define a few Spring beans for testing. There are several ways in which we can provide bean definitions for the Spring container, but in our example, we’ll use annotation-based Java config:

@Configuration
class AnnotationConfig {

    @Bean(name = {"tiger", "kitty"})
    @Scope(value = "prototype")
    Tiger getTiger(String name) {
        return new Tiger(name);
    }

    @Bean(name = "lion")
    Lion getLion() {
        return new Lion("Hardcoded lion name");
    }

    interface Animal {}
}

We’ve created two beans. Lion has the default singleton scope. Tiger is explicitly set to prototype scope. Additionally, please note that we defined names for each bean that we’ll use in further requests.

3. The getBean() APIs

BeanFactory provides five different signatures of the getBean() method that we’re going to examine in the following subsections.

3.1. Retrieving Bean by Name

Let’s see how we can retrieve a Lion bean instance using its name:

Object lion = context.getBean("lion");

assertEquals(Lion.class, lion.getClass());

In this variant, we provide a name, and in return, we get an instance of Object class if a bean with the given name exists in the application context. Otherwise, both this and all other implementations throw NoSuchBeanDefinitionException if the bean lookup fails.

The main disadvantage is that after retrieving the bean, we have to cast it to the desired type. This may produce another exception if the returned bean has a different type than we expected.

Suppose we try to get a Tiger using the name “lion”.  When we cast the result to Tiger, it will throw a ClassCastException:

assertThrows(ClassCastException.class, () -> {
    Tiger tiger = (Tiger) context.getBean("lion");
});

3.2. Retrieving Bean by Name and Type

Here we need to specify both the name and type of the requested bean:

Lion lion = context.getBean("lion", Lion.class);

Compared to the previous method, this one is safer because we get the information about type mismatch instantly:

assertThrows(BeanNotOfRequiredTypeException.class, () -> 
    context.getBean("lion", Tiger.class));
}

3.3. Retrieving Bean by Type

With the third variant of getBean(), it is enough to specify only the bean type:

Lion lion = context.getBean(Lion.class);

In this case, we need to pay special attention to a potentially ambiguous outcome:

assertThrows(NoUniqueBeanDefinitionException.class, () -> 
    context.getBean(Animal.class));
}

In the example above, because both Lion and Tiger implement the Animal interface, merely specifying type isn’t enough to unambiguously determine the result. Therefore, we get a NoUniqueBeanDefinitionException.

3.4. Retrieving Bean by Name with Constructor Parameters

In addition to the bean name, we can also pass constructor parameters:

Tiger tiger = (Tiger) context.getBean("tiger", "Siberian");

This method is a bit different because it only applies to beans with prototype scope.

In the case of singletons, we’re going to get a BeanDefinitionStoreException.

Because a prototype bean will return a newly created instance every time it’s requested from the application container, we can provide constructor parameters on-the-fly when invoking getBean():

Tiger tiger = (Tiger) context.getBean("tiger", "Siberian");
Tiger secondTiger = (Tiger) context.getBean("tiger", "Striped");

assertEquals("Siberian", tiger.getName());
assertEquals("Striped", secondTiger.getName());

As we can see, each Tiger gets a different name according to what we specified as a second parameter when requesting the bean.

3.5. Retrieving Bean by Type With Constructor Parameters

This method is analogous to the last one, but we need to pass the type instead of the name as the first argument:

Tiger tiger = context.getBean(Tiger.class, "Shere Khan");

assertEquals("Shere Khan", tiger.getName());

Similar to retrieving a bean by name with constructor parameters, this method only applies to beans with prototype scope.

4. Usage Considerations

Despite being defined in the BeanFactory interface, the getBean() method is most frequently accessed through the ApplicationContext. Typically, we don’t want to use the getBean() method directly in our program.

Beans should be managed by the container. If we want to use one of them, we should rely on dependency injection rather than a direct call to ApplicationContext.getBean(). That way, we can avoid mixing application logic with framework-related details.

5. Conclusion

In this quick tutorial, we went through all implementations of the getBean() method from the BeanFactory interface and described the pros and cons of each.

All the code examples shown here are available over on GitHub.

Spring bottom

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE

Leave a Reply

avatar
  Subscribe  
Notify of