Довольно популярный вопрос на собеседовании - Можно ли внедрить 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 при поднятии контекста.