По какому алгоритму Spring выбирает конкретный бин для внедрения?

«По какому алгоритму Spring выбирает конкретный бин для внедрения?» — вопрос из категории Spring, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Spring использует четкий алгоритм разрешения зависимостей при выполнении автопривязки (autowiring).

Алгоритм выбора бина для поля/параметра, аннотированного @Autowired:

  1. Поиск всех кандидатов по типу. Spring находит в контексте все бины, тип которых соответствует (является присваиваемым) требуемому типу зависимости (классу или интерфейсу).
  2. Фильтрация по @Qualifier. Если указана аннотация @Qualifier, остаются только бины, имя которых (или значение их @Qualifier) совпадает с указанным.
  3. Проверка аннотации @Primary. Если среди оставшихся кандидатов есть ровно один бин с аннотацией @Primary, он выбирается.
  4. Сопоставление по имени. Если @Qualifier не указан и нет @Primary, Spring пытается найти бин, имя которого совпадает с именем поля или параметра.
  5. Инъекция всех бинов (для Collection или Map). Если тип зависимости — List<MyType>, Set<MyType> или Map<String, MyType>, Spring внедрит все подходящие бины. Для Map ключом будет имя бина.

Результаты алгоритма:

  • 0 бинов: Выбрасывается NoSuchBeanDefinitionException, если required=true (по умолчанию).
  • 1 бин: Успешная инъекция.
  • >1 бин без разрешения неоднозначности: Выбрасывается NoUniqueBeanDefinitionException.

Пример, демонстрирующий приоритеты:

@Configuration
public class Config {
    @Bean
    @Primary // Шаг 3: Выбран, если нет @Qualifier
    public Formatter primaryFormatter() { return new PrimaryFormatter(); }

    @Bean("customFormatter") // Имя бина = "customFormatter"
    @Qualifier("custom") // Имеет квалификатор "custom"
    public Formatter customFormatter() { return new CustomFormatter(); }

    @Bean
    public Formatter defaultFormatter() { return new DefaultFormatter(); }
}

@Service
public class MyService {
    @Autowired // Шаг 1: Найдено 3 бина типа Formatter.
               // Шаг 3: Выбран primaryFormatter(), так как он помечен @Primary.
    private Formatter formatter1;

    @Autowired
    @Qualifier("custom") // Шаг 2: Выбран customFormatter(), так как его @Qualifier совпадает.
    private Formatter formatter2;

    @Autowired // Шаг 1: Найдено 3 бина.
               // Шаг 4: Выбран бин с именем "defaultFormatter" (совпадает с именем поля).
    private Formatter defaultFormatter;
}