Сколько экземпляров бина с областью видимости prototype будет внедрено в singleton бин?

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

Ответ

Один экземпляр на все время жизни singleton-бина. Это ключевая проблема, которую нужно понимать.

Контекст:

  • Singleton бин создается контейнером Spring один раз на все приложение.
  • Prototype бин должен создаваться новый экземпляр при каждом запросе.

Проблема: Если prototype-бин внедряется в singleton-бин через @Autowired (поле, конструктор, сеттер), то инъекция происходит только один раз — в момент создания singleton-бина. После этого Spring больше не вмешивается, и тот же самый (первоначально внедренный) экземпляр prototype-бина используется на протяжении всей жизни singleton-бина.

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

@Component
@Scope("prototype")
public class PrototypeBean {
    private final UUID id = UUID.randomUUID();
    public UUID getId() { return id; }
}

@Component // По умолчанию scope="singleton"
public class SingletonBean {
    @Autowired
    private PrototypeBean prototypeBean; // Внедряется ОДИН РАЗ!

    public UUID getPrototypeId() {
        // Всегда возвращает один и тот же UUID
        return prototypeBean.getId();
    }
}

Как получить новый prototype при каждом вызове?

  1. Использование ApplicationContext (нежелательно, нарушает инверсию управления):
    @Autowired
    private ApplicationContext context;
    public PrototypeBean getNewPrototype() {
        return context.getBean(PrototypeBean.class);
    }
  2. Внедрение ObjectFactory или Provider (рекомендуемый способ):

    import javax.inject.Provider;
    // или import org.springframework.beans.factory.ObjectFactory;
    
    @Component
    public class SingletonBean {
        @Autowired
        private Provider<PrototypeBean> prototypeBeanProvider;
    
        public void process() {
            PrototypeBean newInstance = prototypeBeanProvider.get(); // Новый бин!
        }
    }
  3. Метод lookup (@Lookup аннотация):

    @Component
    public abstract class SingletonBean {
        @Lookup
        protected abstract PrototypeBean createPrototypeBean();
    
        public void process() {
            PrototypeBean bean = createPrototypeBean(); // Новый бин!
        }
    }

    Вывод: При прямом внедрении (@Autowired) в singleton будет инжектирован ровно один экземпляр prototype-бина. Для получения нового экземпляра при каждом обращении необходимо использовать Provider, ObjectFactory или @Lookup.