← Назад к вопросам

Управляет ли контекст 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 бинами
  • Это дает большую гибкость, но требует большей ответственности
Управляет ли контекст Prototype | PrepBro