Как работают Prototype бины
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Как работают Prototype бины
Prototype scope — это одна из областей видимости бинов в Spring Framework, которая определяет, как и когда Spring создаёт и управляет экземплярами бинов. В отличие от Singleton scope (по умолчанию), где один экземпляр на всё приложение, Prototype создаёт новый экземпляр при каждом запросе.
Основные области видимости в Spring
// 1. Singleton (по умолчанию) — один экземпляр на всё приложение
@Component
public class SingletonService {}
// 2. Prototype — новый экземпляр при каждом запросе
@Component
@Scope("prototype")
public class PrototypeService {}
// 3. Request — новый экземпляр на один HTTP запрос (Web)
@Component
@Scope("request")
public class RequestService {}
// 4. Session — новый экземпляр на одну сессию пользователя
@Component
@Scope("session")
public class SessionService {}
Как работает Prototype
При использовании @Scope("prototype") Spring создаёт новый экземпляр бина каждый раз, когда вы его запрашиваете:
@Component
@Scope("prototype")
public class MessageService {
public void printMessage() {
System.out.println("Service: " + this);
}
}
@Component
public class MyController {
@Autowired
private MessageService messageService1;
@Autowired
private MessageService messageService2;
public void test() {
// messageService1 и messageService2 — РАЗНЫЕ объекты
messageService1.printMessage(); // Service: com.example.MessageService@1a2b3c
messageService2.printMessage(); // Service: com.example.MessageService@4d5e6f
System.out.println(messageService1 == messageService2); // false
}
}
Когда используется Prototype
Prototype особенно полезен, когда:
1. Бин содержит stateful (изменяемое состояние)
// ПЛОХО: Singleton с состоянием — потокобезопасные проблемы
@Component
public class UserContext {
private String currentUserId; // Общее состояние для всех запросов
public void setUserId(String userId) {
this.currentUserId = userId;
}
public String getUserId() {
return currentUserId; // Может быть вызван другим потоком
}
}
// ХОРОШО: Prototype гарантирует, что каждый запрос имеет свой экземпляр
@Component
@Scope("prototype")
public class UserContext {
private String currentUserId; // Уникально для каждого экземпляра
public void setUserId(String userId) {
this.currentUserId = userId;
}
public String getUserId() {
return currentUserId;
}
}
2. Временные объекты для обработки данных
@Component
@Scope("prototype")
public class DataProcessor {
private List<String> temporaryData = new ArrayList<>();
private long createdAt = System.currentTimeMillis();
public void processData(String data) {
temporaryData.add(data);
// Обработка временных данных
}
public void reset() {
temporaryData.clear();
}
}
Prototype vs Singleton
// SINGLETON (по умолчанию)
@Component
public class SingletonCounter {
private int count = 0;
public void increment() {
count++;
}
public int getCount() {
return count;
}
}
// PROTOTYPE
@Component
@Scope("prototype")
public class PrototypeCounter {
private int count = 0;
public void increment() {
count++;
}
public int getCount() {
return count;
}
}
@Component
public class CounterTest {
@Autowired
private SingletonCounter singletonCounter;
@Autowired
private PrototypeCounter prototypeCounter1;
@Autowired
private PrototypeCounter prototypeCounter2;
public void test() {
// Singleton: все экземпляры указывают на одну переменную count
singletonCounter.increment();
singletonCounter.increment();
System.out.println(singletonCounter.getCount()); // 2
// Prototype: каждый экземпляр имеет свой count
prototypeCounter1.increment(); // count = 1
prototypeCounter2.increment(); // count = 1
System.out.println(prototypeCounter1.getCount()); // 1
System.out.println(prototypeCounter2.getCount()); // 1
}
}
Объявление Prototype через XML конфиг
<!-- Конфиг Spring (XML) -->
<bean id="myService" class="com.example.MyService" scope="prototype"/>
Prototype Бины в Singleton контексте (важно!)
Есть подвох: если вы инъектируете Prototype бин в Singleton бин через @Autowired, бин будет создан только один раз при инициализации Singleton:
@Component
public class SingletonService {
@Autowired
private PrototypeService prototypeService; // ПРОБЛЕМА!
public void doSomething() {
prototypeService.process(); // Всегда один и тот же экземпляр
}
}
Решение 1: ObjectFactory
@Component
public class SingletonService {
@Autowired
private ObjectFactory<PrototypeService> prototypeBeanFactory;
public void doSomething() {
PrototypeService prototype = prototypeBeanFactory.getObject(); // Новый экземпляр
prototype.process();
}
}
Решение 2: @Lookup аннотация
@Component
public class SingletonService {
@Lookup
public PrototypeService getPrototypeService() {
return null; // Spring переопределит метод
}
public void doSomething() {
PrototypeService prototype = getPrototypeService(); // Новый экземпляр
prototype.process();
}
}
Lifecycle callbacks для Prototype
Отличие в lifecycle методах:
@Component
@Scope("prototype")
public class PrototypeBean {
@PostConstruct
public void init() {
System.out.println("Bean инициализирован"); // БУДЕТ вызван
}
@PreDestroy
public void destroy() {
System.out.println("Bean уничтожен"); // НЕ БУДЕТ вызван (Spring не управляет жизненным циклом)
}
}
Важно: Spring вызывает @PostConstruct для Prototype бинов, но НЕ вызывает @PreDestroy, так как не отслеживает их уничтожение.
Практические рекомендации
-
Используйте Prototype для stateful бинов — если бин хранит состояние, специфичное для одного запроса или операции
-
Singleton по умолчанию — большинство бинов должны быть Singleton из соображений производительности
-
Осторожно с потокобезопасностью — даже Singleton должен быть потокобезопасным
-
Используйте ObjectFactory или @Lookup — если нужно инъектировать Prototype в Singleton
Prototype scope — это мощный инструмент для управления жизненным циклом бинов в Spring, когда нужна изоляция состояния для разных операций.