Какие знаешь способы запроса нового экземпляра prototype бина?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Способы запроса нового экземпляра prototype бина в Spring
Prototype scope в Spring означает, что контейнер создаёт новый экземпляр бина каждый раз, когда тот запрашивается. Это отличается от singleton (по умолчанию), который создаётся один раз.
Проблема: Singleton инъекция prototype бина
Есть опасная ситуация:
@Component
@Scope("prototype")
public class PrototypeService {
private String id = UUID.randomUUID().toString();
public String getId() {
return id;
}
}
@Component
public class SingletonService {
@Autowired
private PrototypeService prototypeService; // Проблема!
public void doWork() {
// Это будет всегда один и тот же объект!
// Prototype scope игнорируется
System.out.println(prototypeService.getId());
}
}
// Тест
SingletonService service1 = context.getBean(SingletonService.class);
service1.doWork(); // ID: abc123
SingletonService service2 = context.getBean(SingletonService.class);
service2.doWork(); // ID: abc123 (ДОЛЖНО БЫТЬ ДРУГОЕ!)
Почему это происходит?
- SingletonService создаётся один раз
- PrototypeService инъектируется один раз
- Spring не создаёт новый prototype при каждом вызове метода
Способ 1: ObjectFactory (Рекомендуется)
Используй ObjectFactory для ленивого создания prototype экземпляров:
@Component
public class SingletonService {
@Autowired
private ObjectFactory<PrototypeService> prototypeFactory;
public void doWork() {
// Новый экземпляр каждый раз!
PrototypeService proto = prototypeFactory.getObject();
System.out.println(proto.getId());
}
}
// Тест
SingletonService service = context.getBean(SingletonService.class);
service.doWork(); // ID: abc123
service.doWork(); // ID: def456
service.doWork(); // ID: ghi789
Преимущества:
- Тип-безопасен
- Прост в использовании
- Рекомендуется Spring
Способ 2: ObjectProvider (Spring 4.3+)
ObjectProvider — более гибкая версия ObjectFactory:
@Component
public class SingletonService {
@Autowired
private ObjectProvider<PrototypeService> prototypeProvider;
public void doWork() {
PrototypeService proto = prototypeProvider.getObject();
System.out.println(proto.getId());
}
public void doWorkOptional() {
// С optional поведением
prototypeProvider.ifAvailable(proto -> {
System.out.println(proto.getId());
});
}
public void doWorkWithDefault() {
// С дефолтным значением
PrototypeService proto = prototypeProvider.getIfAvailable(
() -> new PrototypeService() // fallback
);
}
}
Преимущества:
- Более функциональный API
- null-safety методы
- Более читаемый код
Способ 3: Метод с аннотацией @Lookup
@Lookup — интересный декларативный способ:
@Component
public class SingletonService {
// Spring сгенерирует реализацию этого метода
@Lookup
public PrototypeService getPrototype() {
return null; // Spring переопределит
}
public void doWork() {
PrototypeService proto = getPrototype();
System.out.println(proto.getId());
}
}
Spring будет использовать CGLIB для создания динамического подкласса:
// Примерно так Spring реализует это
public class SingletonServiceEnhanced extends SingletonService {
@Override
public PrototypeService getPrototype() {
return context.getBean(PrototypeService.class); // Новый каждый раз
}
}
Ограничения:
- Класс не должен быть final
- Метод не должен быть final
- Требует CGLIB (для прокси создания)
Способ 4: Прямое обращение к ApplicationContext
Менее элегантно, но работает:
@Component
public class SingletonService {
@Autowired
private ApplicationContext context;
public void doWork() {
PrototypeService proto = context.getBean(PrototypeService.class);
System.out.println(proto.getId());
}
}
Недостатки:
- Service coupling к Spring
- Контейнер не инъектируется, а используется напрямую
- Сложнее тестировать
Способ 5: Параметризованный @Lookup
С параметрами при инъекции:
@Configuration
public class AppConfig {
@Bean
@Scope("prototype")
public PrototypeService prototypeService() {
return new PrototypeService();
}
}
@Component
public class SingletonService {
private ObjectFactory<PrototypeService> prototypeFactory;
public SingletonService(ObjectFactory<PrototypeService> prototypeFactory) {
this.prototypeFactory = prototypeFactory;
}
public void doWork() {
PrototypeService proto = prototypeFactory.getObject();
System.out.println(proto.getId());
}
}
Сравнение способов
| Способ | Тип-безопасность | Простота | Зависимость от Spring | Производительность |
|---|---|---|---|---|
| ObjectFactory | Да | Хорошая | Есть | Отличная |
| ObjectProvider | Да | Отличная | Есть | Отличная |
| @Lookup | Да | Хорошая | Есть | Хорошая |
| ApplicationContext | Нет | Плохая | Есть | Хорошая |
Практический пример: Request scope
Аналогичная проблема с request scope в web приложениях:
@Component
@Scope("request") // Новый экземпляр для каждого HTTP запроса
public class RequestContext {
private String requestId = UUID.randomUUID().toString();
public String getRequestId() {
return requestId;
}
}
@Component
public class SingletonService {
@Autowired
private ObjectFactory<RequestContext> requestContextFactory;
public void processRequest() {
// Новый RequestContext для каждого запроса
RequestContext ctx = requestContextFactory.getObject();
System.out.println("Request ID: " + ctx.getRequestId());
}
}
Когда использовать prototype?
Используй prototype scope когда:
// 1. Stateful объекты, которые не должны быть переиспользованы
@Component
@Scope("prototype")
public class UserSession {
private User currentUser;
private List<Action> history = new ArrayList<>();
}
// 2. Дорогостоящие объекты, которые нужны на короткий период
@Component
@Scope("prototype")
public class DatabaseConnection {
// Новое соединение для каждого запроса
}
// 3. Объекты с mutable state, которые изменяются
@Component
@Scope("prototype")
public class Calculator {
private BigDecimal result;
// Состояние не должно быть разделено
}
НЕ используй prototype для:
// Stateless services (это singleton)
@Component
public class EmailService {
public void send(Email email) { /* ... */ }
}
// Stateless utilities
@Component
public class DateFormatter {
public String format(Date date) { /* ... */ }
}
Частые ошибки
Ошибка 1: Забыли ObjectFactory
// Плохо
@Component
public class Service {
@Autowired
private PrototypeBean proto; // Всегда один объект!
}
// Хорошо
@Component
public class Service {
@Autowired
private ObjectFactory<PrototypeBean> protoFactory;
}
Ошибка 2: @Lookup на final методе
// Ошибка: CGLIB не может переопределить final метод
@Lookup
public final PrototypeService getPrototype() { /* ... */ }
// Правильно
@Lookup
public PrototypeService getPrototype() { /* ... */ }
Заключение
Для запроса нового экземпляра prototype бина:
- Используй ObjectFactory — стандартный способ
- Используй ObjectProvider — если нужна большая функциональность
- Используй @Lookup — если нравится декларативный стиль
- Избегай ApplicationContext — последний вариант
Лучшая практика:
@Component
public class MyService {
private final ObjectFactory<PrototypeBean> beanFactory;
public MyService(ObjectFactory<PrototypeBean> beanFactory) {
this.beanFactory = beanFactory;
}
public void work() {
PrototypeBean bean = beanFactory.getObject();
// Новый экземпляр каждый раз
}
}