Область видимости бинов Prototype

Периодически приходится отвечать на вопросы про область видимости  bean в Spring Framework. В данном примере разбирается область видимости(scope) prototype, который не часто встречается в коде. Статья содержит код, который можно найти на GitHub project.

Создадим два бина с разной областью видимости.

@Component
public class SingletonCounter {

private int count = 0;

public void increment() {
count++;
}

public int getCount() {
return count;
}
}

Данный бин имеет область видимости singleton, так как по умолчанию, все бины создаются с данной областью видимости. Данный тип видимости можно задать явно, используя аннотацию ConfigurableBeanFactory.SCOPE_SINGLETON.

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class PrototypeCounter {

private final StringProcessor stringProcessor;
private int count = 0;

public PrototypeCounter(StringProcessor stringProcessor) {
this.stringProcessor = stringProcessor;
}

public void increment() {
count++;
}

public int getCount() {
return count;
}

public String process(String str) {
return stringProcessor.process(str);
}
}

Данный бин имеет область видимости prototype, которая задается соответствующей аннотацией ConfigurableBeanFactory.SCOPE_PROTOTYPE.

Теперь используем данные бины в классах сервисах, через внедрение с помощью конструктора:

@Service
public class PrototypeIncrementService {

private final PrototypeCounter prototypeCounter;
private final SingletonCounter singletonCounter;

public PrototypeIncrementService(PrototypeCounter prototypeCounter, SingletonCounter singletonCounter) {
this.prototypeCounter = prototypeCounter;
this.singletonCounter = singletonCounter;
}

public PrototypeCounter getPrototypeCounter() {
return prototypeCounter;
}

public SingletonCounter getSingletonCounter() {
return singletonCounter;
}
}
@Component
public class SingletonIncrementService {

private final PrototypeCounter prototypeCounter;
private final SingletonCounter singletonCounter;

public SingletonIncrementService(PrototypeCounter prototypeCounter, SingletonCounter singletonCounter) {
this.prototypeCounter = prototypeCounter;
this.singletonCounter = singletonCounter;
}

public PrototypeCounter getPrototypeCounter() {
return prototypeCounter;
}

public SingletonCounter getSingletonCounter() {
return singletonCounter;
}
}

Используем классы сервисы в основном приложении:

@SpringBootApplication
public class ScopeApplication implements ApplicationRunner {

private final PrototypeIncrementService prototypeIncrementService;
private final SingletonIncrementService singletonIncrementService;

public ScopeApplication(PrototypeIncrementService prototypeIncrementService, SingletonIncrementService singletonIncrementService) {
this.prototypeIncrementService = prototypeIncrementService;
this.singletonIncrementService = singletonIncrementService;
}

public static void main(String[] args) {
SpringApplication.run(ScopeApplication.class, args);
}


@Override
public void run(ApplicationArguments args) {

prototypeIncrementService.getPrototypeCounter().increment();
prototypeIncrementService.getSingletonCounter().increment();
singletonIncrementService.getPrototypeCounter().increment();
singletonIncrementService.getSingletonCounter().increment();
}

public PrototypeIncrementService getPrototypeIncrementService() {
return prototypeIncrementService;
}

public SingletonIncrementService getSingletonIncrementService() {
return singletonIncrementService;
}
}

В данным классе мы при запуске приложения инкрементируем наши счетчики.

Соответствующий класс с тестами:

@SpringBootTest
class ScopeApplicationTests {

@Autowired
private ScopeApplication scopeApplication;

@Test
void shouldCheckPrototypeAndSingletonScopeValues() {

assertThat(scopeApplication.getPrototypeIncrementService().getPrototypeCounter().getCount()).isEqualTo(1);
assertThat(scopeApplication.getSingletonIncrementService().getPrototypeCounter().getCount()).isEqualTo(1);
assertThat(scopeApplication.getPrototypeIncrementService().getSingletonCounter().getCount()).isEqualTo(2);
assertThat(scopeApplication.getSingletonIncrementService().getSingletonCounter().getCount()).isEqualTo(2);
assertThat(scopeApplication.getPrototypeIncrementService().getPrototypeCounter().process("String1")).isEqualTo("STRING1");
assertThat(scopeApplication.getSingletonIncrementService().getPrototypeCounter().process("String2")).isEqualTo("STRING2");
}
}

Из теста видно, что бины с областью видимости singleton создаются в единственном экземпляре, и если у них есть состояние, в данном случае поле private int count, при вызове метода для инкремента, будет инкрементироваться именно оно, где бы мы не использовали сервис с внедренным счетчиком.

Однако, если мы рассмотрим бины с областью видимости prototype, можно увидеть, что каждый раз, при внедрении данных бинов, создается новый экземпляр со своим состоянием. Похожего поведения, можно добиться если написать в программе явный вызов создания объекта через оператор new. Однако, в таком случае, мы потеряем возможность внедрять зависимости и друге бины в наши экземпляры, поскольку контейнер не управляет ими.

Выводы:

Singleton beanPrototype bean
Создается только один экземпляр. Он используется везде.Каждый раз при обращению к бину, создается новый экземпляр.
Уничтожается spring при закрытии контекста. Вызывается метод destroyУничтожается сборщиком мусора.
Spring не управляет удалением экземпляра и обработкой закрытия его ресурсов.
Используется, когда не нужно хранить состояние.Используется, когда нужно хранить состояние.