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

Как в планировщике реализуешь запуск задания, чистящего логи

2.2 Middle🔥 141 комментариев
#Spring Framework

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

# Prototype паттерн: когда создаёт новый объект

Определение

Prototype — это паттерн проектирования, который создаёт новый объект при каждом обращении к bean'у, помеченному как @Scope("prototype").

Области видимости (Scopes) в Spring

Scope              Создание объекта     Жизненный цикл
─────────────────────────────────────────────────────────
singleton          один раз              от старта до завершения
prototype          каждое обращение      до первого использования
request            раз на HTTP request    на время обработки
session            раз на сессию         время сессии
application        один раз              жизнь приложения

Singleton (по умолчанию)

@Component  // или @Service, @Repository
public class UserService {
    // По умолчанию scope = singleton
    // Создаётся один раз при старте приложения
}

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(Application.class);
        
        // Получаем bean первый раз
        UserService service1 = context.getBean(UserService.class);
        
        // Получаем bean второй раз
        UserService service2 = context.getBean(UserService.class);
        
        // ✅ Это один и тот же объект!
        System.out.println(service1 == service2);  // true
    }
}

Prototype (новый объект каждый раз)

@Component
@Scope("prototype")  // ← Вот это создаст новый объект
public class RequestHandler {
    private String requestId = UUID.randomUUID().toString();
    
    public void handle() {
        System.out.println("Handling request: " + requestId);
    }
}

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(Application.class);
        
        // Получаем bean первый раз
        RequestHandler handler1 = context.getBean(RequestHandler.class);
        // requestId = "550e8400-e29b-41d4-a716-446655440001"
        
        // Получаем bean второй раз
        RequestHandler handler2 = context.getBean(RequestHandler.class);
        // requestId = "550e8400-e29b-41d4-a716-446655440002"
        
        // ❌ Это разные объекты!
        System.out.println(handler1 == handler2);  // false
        
        handler1.handle();  // Handling request: 550e...
        handler2.handle();  // Handling request: 550e...
    }
}

Когда Prototype создаёт новый объект

1. При явном вызове context.getBean()

@Component
@Scope("prototype")
public class Worker {
    private String workerId = UUID.randomUUID().toString();
}

public class Main {
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(...);
        
        // Каждый вызов создаёт НОВЫЙ объект
        Worker w1 = context.getBean(Worker.class);  // Новый объект
        Worker w2 = context.getBean(Worker.class);  // Новый объект
        Worker w3 = context.getBean(Worker.class);  // Новый объект
        
        System.out.println(w1.workerId);  // unique-1
        System.out.println(w2.workerId);  // unique-2
        System.out.println(w3.workerId);  // unique-3
    }
}

2. При dependency injection в другой bean

@Component
@Scope("prototype")
public class Task {
    private String taskId = UUID.randomUUID().toString();
    
    public String getTaskId() { return taskId; }
}

@Component
public class TaskProcessor {
    @Autowired
    private Task task;  // Каждый раз новый Task!
    
    public void processTask() {
        System.out.println("Processing: " + task.getTaskId());
    }
}

public class Main {
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(...);
        
        TaskProcessor processor = context.getBean(TaskProcessor.class);
        
        // Обработка 1
        processor.processTask();  // Processing: task-1 (новый Task создан)
        
        // Обработка 2
        processor.processTask();  // Processing: task-1 (тот же Task, внедрён один раз)
    }
}

3. При использовании ObjectFactory/Provider

Это специальный способ получить новый Prototype каждый раз:

@Component
@Scope("prototype")
public class Connection {
    private String connectionId = UUID.randomUUID().toString();
    
    public void connect() {
        System.out.println("Connected: " + connectionId);
    }
}

@Component
public class ConnectionManager {
    @Autowired
    private ObjectFactory<Connection> connectionFactory;
    
    public void openConnection() {
        // Каждый раз новое соединение
        Connection conn1 = connectionFactory.getObject();  // NEW
        conn1.connect();  // Connected: conn-1
        
        Connection conn2 = connectionFactory.getObject();  // NEW
        conn2.connect();  // Connected: conn-2
    }
}

Singleton vs Prototype

Пример проблемы

// ❌ ПЛОХО: Singleton с состоянием
@Component
public class RequestContext {
    private String userId;  // Состояние!
    
    public void setUserId(String userId) {
        this.userId = userId;  // Все потоки используют одно значение!
    }
    
    public String getUserId() {
        return userId;
    }
}

// При обработке двух запросов параллельно:
Thread 1: context.setUserId("user-1");  
Thread 2: context.setUserId("user-2");
Thread 1: System.out.println(context.getUserId());  // user-2! ❌

Решение: использовать Prototype

// ✅ ХОРОШО: Prototype для каждого запроса
@Component
@Scope("prototype")
public class RequestContext {
    private String userId;  // Каждый поток получает свой объект
    
    public void setUserId(String userId) {
        this.userId = userId;
    }
    
    public String getUserId() {
        return userId;
    }
}

// При обработке двух запросов параллельно:
Thread 1: context1.setUserId("user-1");  
Thread 2: context2.setUserId("user-2");  // Разные объекты!
Thread 1: System.out.println(context1.getUserId());  // user-1 ✅

Использование @Scope аннотации

// Способ 1: На классе
@Component
@Scope("prototype")
public class MyBean { }

// Способ 2: На методе @Bean
@Configuration
public class BeanConfig {
    @Bean
    @Scope("prototype")
    public MyBean myBean() {
        return new MyBean();
    }
}

// Способ 3: Использование ScopedProxyMode
@Component
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class ProxiedBean { }

Жизненный цикл Prototype

Singleton:                    Prototype:

Апп стартует                  Запрос getBean()
    ↓                              ↓
Создание объекта            Создание объекта
    ↓                              ↓
@PostConstruct               @PostConstruct
    ↓                              ↓
Использование                Использование
    ↓                              ↓
Апп завершается             Spring НЕ вызывает
    ↓                           @PreDestroy
@PreDestroy                       ↓
    ↓                        Объект собирается GC
Удаление объекта

Когда использовать Prototype

Используй Prototype для:

  • Stateful объектов (с состоянием)
  • Request-scoped данных
  • Временных объектов
  • Объектов, которые нужны в одном месте

Не используй Prototype для:

  • Stateless сервисов (используй Singleton)
  • Дорогих для создания объектов
  • Объектов, которые должны быть одни на всё приложение

На собеседовании

"Prototype в Spring создаёт новый объект при каждом обращении.

Это происходит когда:

  1. Явно вызвать context.getBean(Class)
  2. При dependency injection, если компонент помечен @Scope("prototype")
  3. Через ObjectFactory<T>.getObject()

По умолчанию beans имеют scope = singleton (один объект на приложение).

Пример: @Component @Scope("prototype") создаст новый объект каждый раз.

Отличие от Singleton:

  • Singleton: один объект на всё приложение
  • Prototype: новый объект на каждое обращение

Протotype используется для stateful объектов, чтобы избежать race conditions."