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

Как работают Prototype бины

2.0 Middle🔥 131 комментариев
#Spring Boot и Spring Data#Spring Framework

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

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

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

Как работают 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, так как не отслеживает их уничтожение.

Практические рекомендации

  1. Используйте Prototype для stateful бинов — если бин хранит состояние, специфичное для одного запроса или операции

  2. Singleton по умолчанию — большинство бинов должны быть Singleton из соображений производительности

  3. Осторожно с потокобезопасностью — даже Singleton должен быть потокобезопасным

  4. Используйте ObjectFactory или @Lookup — если нужно инъектировать Prototype в Singleton

Prototype scope — это мощный инструмент для управления жизненным циклом бинов в Spring, когда нужна изоляция состояния для разных операций.

Как работают Prototype бины | PrepBro