Довольно популярный вопрос на собеседовании - Можно ли внедрить Prototyte в Singleton и как это сделать?
Если просто добавить к определению бина аннотацию @Scope(SCOPE_PROTOTYPE)
, и использовать этот бин в синглтоне через аннотацию @Autowired
– будет создан только один объект. Потому что синглтон создается только однажды, и обращение к прототипу случится тоже однажды при его создании (при внедрении зависимости).
На самом деле вариантов довольно много:
с помощью
@Lookup
context.getBean(Prototype.class)
API -интерфейс javax.inject
Интерфейс ObjectFactory
Но эти варианты мягко скажем "не очень".
В некоторых статьях пишут что можно сделать это с помощью параметра ProxyMode в аннотации @Scope, но я не смог найти реальных примеров внедрения, so напишу свой.
Для начала создадим наш будущий Prototype.
Каждый раз когда создается новый экземпляр будем выводить в консоль "new bank created".
-
Добавим @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE,proxyMode = TARGET_CLASS)
Выражение proxyMode=TARGET_CLASS oзначает, что класс создается с помощью наследования.
Можно сделать proxyMode = INTERFACES, тогда proxy создастся c использованием интерфейсов, наш класс не реализует никаких интерфейсов(интерфейсы - маркеры не подойдут), поэтому используем TARGET_CLASS.
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import java.util.UUID;
import static org.springframework.context.annotation.ScopedProxyMode.TARGET_CLASS;
/**
* Created in SoftMediaLab
* by Mark Ponomarev
* Date : 15.09.2023
**/
@Component
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE, proxyMode=TARGET_CLASS)
public class Bank {
private final String bankName;
public Bank() {
this.bankName = UUID.randomUUID().toString();
System.out.println("new bank created");
}
public String getInput() {
return bankName;
}
}
Сейчас создадим Singleton.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* Created in SoftMediaLab
* by Mark Ponomarev
* Date : 15.09.2023
**/
@Service()
public class BankService {
@Autowired
Bank bank;
public Bank getNewBank(){
return bank;
}
}
Тут все довольно просто, Мы инжектим наш Prototype c помощью aннотации @Autowired.
Для проверки напишем дополнительный метод.
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class PrimaryExampleApplicationTests {
@Autowired
BankService messageService;
@Test
public void testing(){
Bank newBank1 = messageService.getNewBank();
Bank newBank = messageService.getNewBank();
System.out.println(newBank.getInput());
System.out.println(newBank1.getInput());
}
}
Можете самостоятельно проверить, что будет создано 2 экземпляра класса Bank.
Для полноты картины вы можете самостоятельно внедрить Prototype, используя динамический Proxy(proxyMode = INTERFACES). Не забудьте про то, что нужно реализовать интерфейс, иначе будет Exeption при поднятии контекста.
Krusho
В Вашем реализации, в синглтон внедряется не реальный объект класса, а прокси. И при каждом вызове его методов, прокси решает, создавать новый экземпляр класса для вызова или нет. То есть, в Вашем случае, при каждом вызове метода getInput(), прокси решил создавать новый объект. Вызовите метод getInput() на одном и том же прокси 100 раз, думаю, там будет создано гораздо больше экземпляров, чем 1.
Простой пример, получен один бин из Вашей реализации, и на одном и том же бине вызван 500 раз один и тот же метод, вывод по спойлером под кодом.
Вывод кода выше
new bank created
356f5044-9117-4d2a-b67a-e7d1690cc84b
new bank created
53950529-5dfb-4c85-b95f-0e84f25b2108
new bank created
bcd131c0-09d8-49e7-ad2b-4331aac0cabf
new bank created
f8fa5bbe-ee93-42cb-ab16-e153a0a013ed
new bank created
5707d254-78e9-4bf5-85d6-4b842da36403
new bank created
648d3719-fb8b-4c39-b3af-ca1e9736a05d
new bank created
102b8341-5309-4dfc-9cb3-2d3e3717971d
new bank created
9e9e1189-7eb5-43cd-b9c8-b2438c0db6c9
new bank created
3b762b69-2746-4ef4-9a8e-36238129617b
new bank created
704b9894-47fa-420b-9c7f-b86e019bcebe
new bank created
9d1a519f-d841-4cb4-ace2-2fbcd0cb983d
new bank created
0e55e9a6-662e-4fed-b568-dd44abe5829d
new bank created
43162b6d-ed89-451a-9fce-cec798862d96
new bank created
427bcda1-6d2d-4cf0-8b1e-17aaaec1ebee
Всё же, внедрять Prototype лучше так, как описано в документации, например, имплементировать интерфейс ApplicationContextAware и вызывать context.getBean(Prototype.class).https://docs.spring.io/spring-framework/docs/3.0.0.M4/reference/html/ch03s04.html#beans-factory-method-injection
m1llark Автор
Вызов ApplicationContext.getBean() это не Инверсия Контроля! Несмотря на то, что по-прежнему легко изменить реализацию, настроенную для заданного имени компонента, класс теперь напрямую полагается на Spring для предоставления этой зависимости и не может получить ее каким-либо другим способом. Вы не можете просто сделать свою собственную имитацию реализации в тестовом классе и передать ее ему самостоятельно. Это, по сути, противоречит цели Spring как контейнера для внедрения зависимостей.
Если кратко то ваш вариант это костыль.
Krusho
Видимо, я не смог донести до Вас свою мысль.
В Ваш синглтон всегда инжектится один, и только один экземпляр Bank. Только в случае использования
proxyMode=TARGET_CLASS
, инжектится не объект класса, а прокси. Вы всегда получаете один и тот же объект, когда вызываете метод синглтона getNewBank(), можете это проверить.Пример ниже, в котором получены два экземпляра Bank через BankService, убеждаемся, что получили один и тот же экземпляр и вызываем дважды метод getInput() для обоих Bank. Было создано 4 реальных экземпляра Bank, по одному на каждый вызов метод. Остаётся вопрос, действительно ли разработчик хотел бы вызвать метод getInput() на 4 разных объектах, или же только на 2, как это видно в коде.
Результат выполнения
There is the same bank
Created new bank.
3dafc23c-ad22-4a05-96af-ec155cc9cd46
Created new bank.
e328e94f-3a62-44df-837f-df3c8f6fd440
Created new bank.
39381838-c273-4131-9a54-0e4736cf3155
Created new bank.
3f7dca89-15ef-49e8-b71f-4289790d51e3
Bonce
Я бы не утверждал, что это не инверсия контроля. Это скорее dependency lookup