«1. Введение

В этом руководстве мы рассмотрим различные варианты метода BeanFactory.getBean().

Проще говоря, как следует из названия метода, он отвечает за извлечение экземпляра компонента из контейнера Spring.

2. Настройка Spring Beans

Во-первых, давайте определим несколько Spring bean-компонентов для тестирования. Существует несколько способов предоставления определений bean-компонентов для контейнера Spring, но в нашем примере мы будем использовать конфигурацию Java на основе аннотаций:

@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 {}
}

Мы создали два bean-компонента. Lion имеет одноэлементную область действия по умолчанию. Tiger явно настроен на область действия прототипа. Кроме того, обратите внимание, что мы определили имена для каждого компонента, которые мы будем использовать в дальнейших запросах.

3. API getBean()

BeanFactory предоставляет пять различных сигнатур метода getBean(), которые мы рассмотрим в следующих подразделах.

3.1. Получение компонента по имени

Давайте посмотрим, как мы можем получить экземпляр компонента Lion, используя его имя:

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

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

В этом варианте мы указываем имя, а взамен получаем экземпляр класса Object, если компонент с заданным именем существует в контексте приложения. В противном случае и эта, и все другие реализации выдают исключение NoSuchBeanDefinitionException, если поиск компонента завершается неудачно.

Основной недостаток в том, что после извлечения бина мы должны привести его к нужному типу. Это может привести к другому исключению, если возвращаемый компонент имеет тип, отличный от ожидаемого.

Предположим, мы пытаемся получить Тигра, используя имя «лев». Когда мы передаем результат в Tiger, он генерирует исключение ClassCastException:

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

3.2. Получение бина по имени и типу

Здесь нам нужно указать как имя, так и тип запрошенного бина:

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

По сравнению с предыдущим методом, этот безопаснее, потому что мы получаем информацию о несоответствии типов мгновенно:

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

3.3. Получение бина по типу

В третьем варианте getBean() достаточно указать только тип бина:

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

В этом случае нужно обратить особое внимание на потенциально неоднозначный результат: ~~ ~

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

В приведенном выше примере, поскольку и Lion, и Tiger реализуют интерфейс Animal, простого указания типа недостаточно для однозначного определения результата. Поэтому мы получаем исключение NoUniqueBeanDefinitionException.

3.4. Получение бина по имени с параметрами конструктора

В дополнение к имени бина мы также можем передать параметры конструктора:

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

Этот метод немного отличается, потому что он применяется только к бинам с областью видимости прототипа.

В случае синглтонов мы получим исключение BeanDefinitionStoreException.

Поскольку прототип bean-компонента будет возвращать только что созданный экземпляр каждый раз, когда он запрашивается из контейнера приложения, мы можем предоставлять параметры конструктора на лету при вызове getBean():

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

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

Как мы видим, каждый Tiger получает другое имя в зависимости от того, что мы указали в качестве второго параметра при запросе bean-компонента.

3.5. Получение бина по типу с параметрами конструктора

Этот метод аналогичен предыдущему, но нам нужно передать тип вместо имени в качестве первого аргумента:

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

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

Аналогично получению бина по имени с помощью конструктора параметры, этот метод применяется только к bean-компонентам с областью действия прототипа.

4. Рекомендации по использованию

Несмотря на то, что метод getBean() определен в интерфейсе BeanFactory, чаще всего к нему обращаются через ApplicationContext. Как правило, мы не хотим использовать метод getBean() непосредственно в нашей программе.

Beans должен управляться контейнером. Если мы хотим использовать один из них, мы должны полагаться на внедрение зависимостей, а не на прямой вызов ApplicationContext.getBean(). Таким образом, мы можем избежать смешивания логики приложения с деталями, связанными с фреймворком.

5. Заключение

В этом кратком руководстве мы рассмотрели все реализации метода getBean() из интерфейса BeanFactory и описали плюсы и минусы каждой из них.

Все приведенные здесь примеры кода доступны на GitHub.