← Назад к вопросам
Управляет ли контекст Prototype
2.2 Middle🔥 121 комментариев
#Docker, Kubernetes и DevOps#JVM и управление памятью
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Управление Prototype-скопом в Spring Context
Нет, Spring контекст не управляет жизненным циклом бинов со скопом Prototype. Это ключевое отличие от Singleton.
Основные различия Singleton vs Prototype
@Configuration
public class BeanScopeConfig {
@Bean
@Scope(BeanDefinition.SCOPE_SINGLETON) // По умолчанию
public SingletonService singletonService() {
System.out.println("Создание Singleton бина");
return new SingletonService();
}
@Bean
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public PrototypeService prototypeService() {
System.out.println("Создание Prototype бина");
return new PrototypeService();
}
}
Singleton:
- Создается один раз при старте контекста
- Контекст управляет всем жизненным циклом
- Один экземпляр для всего приложения
- Вызывает @PreDestroy при shutdown
Prototype:
- Создается при каждом запросе бина
- Контекст создает экземпляр и уходит (не управляет дальше)
- Каждый клиент получает новый экземпляр
- @PreDestroy НЕ вызывается контекстом
Жизненный цикл Prototype бина
public class PrototypeService {
private String id = UUID.randomUUID().toString();
public PrototypeService() {
System.out.println("Конструктор вызван, ID: " + id);
}
@PostConstruct
public void init() {
System.out.println("@PostConstruct вызван, ID: " + id);
}
@PreDestroy
public void destroy() {
// ⚠️ ЭТО НЕ БУДЕТ ВЫЗВАНО Spring-ом!
System.out.println("@PreDestroy вызван, ID: " + id);
}
public void doWork() {
System.out.println("Работаю, ID: " + id);
}
}
@RestController
public class MyController {
@Autowired
private PrototypeService prototypeService;
@GetMapping("/test")
public String test() {
// Каждый запрос создаст НОВЫЙ экземпляр
prototypeService.doWork();
return "done";
}
}
Вывод в консоль:
Конструктор вызван, ID: abc-123
@PostConstruct вызван, ID: abc-123
Работаю, ID: abc-123
Конструктор вызван, ID: def-456
@PostConstruct вызван, ID: def-456
Работаю, ID: def-456
Конструктор вызван, ID: ghi-789
@PostConstruct вызван, ID: ghi-789
Работаю, ID: ghi-789
// @PreDestroy НЕ вызывается!
Управление очисткой Prototype
Поскольку Spring не управляет cleanup, тебе нужно это делать вручную:
1. DisposableBean интерфейс
@Component
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class MyPrototypeService implements DisposableBean {
private Connection conn;
@PostConstruct
public void init() {
this.conn = createConnection(); // Открываем ресурс
}
@Override
public void destroy() throws Exception {
if (conn != null) {
conn.close(); // Закрываем вручную
}
}
}
2. ObjectFactory для управления жизненным циклом
@Service
public class MyService {
@Autowired
private ObjectFactory<PrototypeService> prototypeFactory;
public void processRequest(Request request) {
PrototypeService service = prototypeFactory.getObject(); // Новый экземпляр
try {
service.process(request);
} finally {
cleanup(service); // Чистим сами
}
}
private void cleanup(PrototypeService service) {
service.destroy();
}
}
3. ObjectProvider для более удобного управления
@Service
public class RequestService {
@Autowired
private ObjectProvider<RequestContext> contextProvider;
public void handleRequest(Request request) {
// getObject() создает новый экземпляр
RequestContext ctx = contextProvider.getObject();
try {
ctx.processRequest(request);
} finally {
contextProvider.getIfAvailable(RequestContext::cleanup);
}
}
}
Практические примеры
Проблема: используем Prototype с Dependencies
@Component
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class DatabaseConnection {
private Connection conn;
private String sessionId;
@PostConstruct
public void init() {
this.sessionId = UUID.randomUUID().toString();
this.conn = createConnection();
System.out.println("Открыли соединение: " + sessionId);
}
public void query(String sql) {
conn.executeQuery(sql);
}
@PreDestroy
public void closeConnection() {
conn.close();
System.out.println("Закрыли соединение: " + sessionId);
}
}
@Service
public class UserRepository {
@Autowired
private DatabaseConnection db; // ⚠️ НЕПРАВИЛЬНО!
public User findById(Long id) {
// db будет одно и тоже для всех запросов!
// Для Prototype нужно использовать ObjectFactory
db.query("SELECT * FROM users WHERE id = " + id);
return new User();
}
}
Правильный подход:
@Service
public class UserRepository {
@Autowired
private ObjectFactory<DatabaseConnection> dbFactory;
public User findById(Long id) {
DatabaseConnection db = dbFactory.getObject(); // Новый для каждого запроса
try {
db.query("SELECT * FROM users WHERE id = " + id);
return new User();
} finally {
db.closeConnection(); // Закроем вручную
}
}
}
Когда использовать Prototype
// ✅ Используй Prototype, когда:
// 1. Нужно отдельное состояние для каждого использования
@Bean
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public RequestContext requestContext() {
return new RequestContext(System.currentTimeMillis());
}
// 2. Нужны дорогие ресурсы (соединения БД, потоки)
@Bean
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public TaskExecutor taskExecutor() {
return new SimpleAsyncTaskExecutor();
}
// ❌ НЕ используй Prototype, если:
// - Бин stateless (нет состояния)
// - Бин дорогой в создании (конструктор выполняет тяжелые операции)
// - Нет особой необходимости в новом экземпляре
Ключевые выводы
- Spring контекст НЕ управляет жизненным циклом Prototype бинов
- @PostConstruct - вызывается контекстом (создание)
- @PreDestroy - НЕ вызывается контекстом, только для Singleton
- Ты сам отвечаешь за cleanup Prototype бинов
- Используй ObjectFactory / ObjectProvider для управления Prototype бинами
- Это дает большую гибкость, но требует большей ответственности