← Назад к вопросам
Может ли быть несколько экземпляров Bean в Spring?
2.3 Middle🔥 221 комментариев
#Spring Framework
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Может ли быть несколько экземпляров Bean в Spring
Ответ: ДА, зависит от Scope Bean
Spring может создавать несколько экземпляров одного Bean, в зависимости от его Scope (область видимости). По умолчанию используется singleton scope, который создает только один экземпляр, но есть и другие опции.
Типы Bean Scopes в Spring
┌─────────────────────────────────────────┐
│ Spring Bean Scopes │
├─────────────────────────────────────────┤
│ │
│ 1. SINGLETON (по умолчанию) │
│ - Один экземпляр на весь контекст │
│ - Долгоживущий (пока работает app) │
│ │
│ 2. PROTOTYPE │
│ - Новый экземпляр каждый раз │
│ - Время жизни: в руках клиента │
│ │
│ 3. REQUEST (только web) │
│ - Один экземпляр на HTTP request │
│ - Пересоздается для каждого запроса │
│ │
│ 4. SESSION (только web) │
│ - Один экземпляр на сессию │
│ - Пересоздается для новой сессии │
│ │
│ 5. APPLICATION (только web) │
│ - Один на все приложение │
│ - Эквивалент singleton │
│ │
│ 6. WEBSOCKET (только web) │
│ - Один на WebSocket соединение │
│ - Для работы с WebSocket │
│ │
└─────────────────────────────────────────┘
1. SINGLETON Scope (по умолчанию)
Только ОДИН экземпляр на все приложение
// Явное указание singleton (обычно опускается)
@Component
@Scope("singleton")
public class UserService {
public void processUser(String username) {
System.out.println("Processing: " + username);
}
}
// Эквивалент (без аннотации)
@Component // По умолчанию singleton
public class OrderService {
public void processOrder(Long orderId) {
System.out.println("Processing order: " + orderId);
}
}
// Использование
@SpringBootApplication
public class SingletonDemo {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(SingletonDemo.class, args);
UserService service1 = context.getBean(UserService.class);
UserService service2 = context.getBean(UserService.class);
System.out.println(service1 == service2); // true (один и тот же объект)
System.out.println(System.identityHashCode(service1));
System.out.println(System.identityHashCode(service2)); // одинаковые
}
}
Характеристики:
- Один экземпляр создается при инициализации контекста
- Все инъекции указывают на один объект
- Потокобезопасность — ответственность разработчика
- Экономно по памяти
2. PROTOTYPE Scope
НОВЫЙ экземпляр каждый раз при запросе Bean
@Component
@Scope("prototype")
public class TaskProcessor {
private UUID id = UUID.randomUUID();
public void process() {
System.out.println("Processing with ID: " + id);
}
}
// Или через аннотацию
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class RequestHandler {
private long createdAt = System.currentTimeMillis();
public void handle() {
System.out.println("Created at: " + createdAt);
}
}
// Использование
@SpringBootApplication
public class PrototypeDemo {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(PrototypeDemo.class, args);
TaskProcessor processor1 = context.getBean(TaskProcessor.class);
TaskProcessor processor2 = context.getBean(TaskProcessor.class);
TaskProcessor processor3 = context.getBean(TaskProcessor.class);
System.out.println(processor1 == processor2); // false (разные объекты)
System.out.println(processor2 == processor3); // false
processor1.process(); // Processing with ID: xxx-1
processor2.process(); // Processing with ID: xxx-2
processor3.process(); // Processing with ID: xxx-3
}
}
Характеристики:
- Новый экземпляр при каждом getBean() вызове
- Каждый экземпляр независим
- Разработчик отвечает за cleanup
- Spring НЕ вызывает @PreDestroy методы
- Использует больше памяти
3. REQUEST Scope (Web приложения)
Один экземпляр на HTTP request
@Component
@Scope("request")
@Slf4j
public class RequestAwareService {
private String requestId = UUID.randomUUID().toString();
private LocalDateTime createdTime = LocalDateTime.now();
public void doSomething() {
log.info("Request ID: " + requestId);
}
}
// Использование в контроллере
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private RequestAwareService service; // Новый для каждого запроса
@GetMapping("/profile")
public ResponseEntity<String> getProfile() {
service.doSomething(); // Использует Bean для этого запроса
return ResponseEntity.ok("User profile");
}
}
// Пример: два параллельных HTTP запроса
public class RequestScopeDemo {
public static void testTwoRequests() {
// Request 1: /api/users/profile -> RequestAwareService (instance 1)
// Request 2: /api/users/profile -> RequestAwareService (instance 2) параллельно
// Каждый запрос получит СВОЙ экземпляр Bean
}
}
Характеристики:
- Автоматическое создание для каждого HTTP request
- Автоматическое удаление в конце request
- Состояние сохраняется в течение одного запроса
- Идеален для параметров конкретного запроса
4. SESSION Scope (Web приложения)
Один экземпляр на HTTP Session
@Component
@Scope("session")
@Slf4j
public class UserSessionData {
private Long userId;
private String username;
private List<String> permissions = new ArrayList<>();
private LocalDateTime loginTime = LocalDateTime.now();
public void setUser(Long userId, String username) {
this.userId = userId;
this.username = username;
log.info("User logged in: " + username);
}
public String getUsername() {
return username;
}
}
// Использование
@RestController
@RequestMapping("/api/auth")
public class AuthController {
@Autowired
private UserSessionData sessionData; // Один на всю сессию
@PostMapping("/login")
public ResponseEntity<String> login(@RequestParam String username) {
// sessionData существует для всей сессии пользователя
sessionData.setUser(1L, username);
return ResponseEntity.ok("Logged in");
}
@GetMapping("/profile")
public ResponseEntity<String> getProfile() {
// Все запросы из одной сессии видят один и тот же sessionData
return ResponseEntity.ok("User: " + sessionData.getUsername());
}
}
// Пример: три разные сессии
public class SessionScopeDemo {
public static void testThreeSessions() {
// User 1 (Browser 1): /api/auth/login -> UserSessionData (instance 1)
// User 1 (Browser 1): /api/auth/profile -> UserSessionData (instance 1)
// User 1 (Browser 1): /api/auth/logout -> UserSessionData (instance 1)
// User 2 (Browser 2): /api/auth/login -> UserSessionData (instance 2)
// User 2 (Browser 2): /api/auth/profile -> UserSessionData (instance 2)
// User 3 (Browser 3): /api/auth/login -> UserSessionData (instance 3)
// Каждая сессия получит СВОЙ экземпляр
}
}
Характеристики:
- Один экземпляр на всю HTTP сессию
- Создается при первом обращении в сессии
- Удаляется при истечении сессии
- Идеален для хранения data конкретного пользователя
Сравнительная таблица
| Scope | Количество экземпляров | Время жизни | Потокобезопасность | Использование |
|---|---|---|---|---|
| SINGLETON | 1 на контекст | Весь app | Нужна синхронизация | БД, сервисы |
| PROTOTYPE | N (новый каждый раз) | Клиент | Ненужна | Stateful объекты |
| REQUEST | 1 на запрос | 1 HTTP запрос | Не нужна | Параметры запроса |
| SESSION | 1 на сессию | HTTP сессия | Не нужна | Данные сессии |
| WEBSOCKET | 1 на соединение | WebSocket | Не нужна | WebSocket состояние |
Практический пример: Singleton vs Prototype
// Singleton сервис (обычно)
@Service
public class UserService {
@Autowired
private UserRepository repository;
public User getUser(Long id) {
return repository.findById(id).orElse(null);
}
}
// Prototype задача (статефул)
@Component
@Scope("prototype")
public class DataProcessingTask {
private List<String> data = new ArrayList<>();
private int processedCount = 0;
private String taskId = UUID.randomUUID().toString();
public void addData(String item) {
data.add(item);
}
public void process() {
for (String item : data) {
System.out.println("Task " + taskId + " processing: " + item);
processedCount++;
}
}
}
// Использование
@Service
public class ProcessingService {
@Autowired
private ApplicationContext context; // Нужен для создания prototypes
public void processBatch(List<String> items) {
// Создаем новую задачу для каждого batch
DataProcessingTask task = context.getBean(DataProcessingTask.class);
task.addData("item1");
task.addData("item2");
task.process(); // Task ID 1
// Создаем еще одну задачу
DataProcessingTask task2 = context.getBean(DataProcessingTask.class);
task2.addData("item3");
task2.process(); // Task ID 2 (разный от task1)
}
}
Когда использовать каждый Scope
public class ScopeUsageGuide {
// ✅ SINGLETON: stateless сервисы
@Service
public class EmailService {
public void send(String to, String subject, String body) { }
}
// ✅ SINGLETON: репозитории
@Repository
public class UserRepository { }
// ✅ PROTOTYPE: Stateful объекты с уникальным состоянием
@Component
@Scope("prototype")
public class ReportGenerator {
private List<String> lines = new ArrayList<>();
}
// ✅ REQUEST: Параметры текущего HTTP запроса
@Component
@Scope("request")
public class RequestContext {
private HttpServletRequest request;
private String userId;
}
// ✅ SESSION: Данные пользователя сессии
@Component
@Scope("session")
public class UserSession {
private Long userId;
private List<String> permissions;
}
}
Проблемы и решения
// ❌ Проблема: внедрение prototype в singleton
@Service
public class BadService {
@Autowired
private PrototypeComponent prototype; // Внедрится ОДИН раз
public void process() {
// Все вызовы используют ОДИН prototype
prototype.doSomething();
prototype.doSomething(); // Тот же объект
}
}
// ✅ Решение 1: ObjectFactory
@Service
public class GoodService1 {
@Autowired
private ObjectFactory<PrototypeComponent> factory;
public void process() {
// Каждый раз новый экземпляр
PrototypeComponent proto1 = factory.getObject();
PrototypeComponent proto2 = factory.getObject();
}
}
// ✅ Решение 2: @Lookup аннотация
@Service
public class GoodService2 {
@Lookup
public PrototypeComponent getPrototype() {
return null; // Spring переопределит метод
}
public void process() {
PrototypeComponent proto1 = getPrototype();
PrototypeComponent proto2 = getPrototype();
}
}
// ✅ Решение 3: ApplicationContext
@Service
public class GoodService3 {
@Autowired
private ApplicationContext context;
public void process() {
PrototypeComponent proto1 = context.getBean(PrototypeComponent.class);
PrototypeComponent proto2 = context.getBean(PrototypeComponent.class);
}
}
Заключение
Да, Spring МОЖЕТ создавать несколько экземпляров Bean:
- SINGLETON (по умолчанию) — один экземпляр
- PROTOTYPE — новый экземпляр каждый раз
- REQUEST — новый экземпляр на каждый HTTP запрос
- SESSION — один экземпляр на сессию
- WEBSOCKET — один экземпляр на WebSocket соединение
Выбор Scope зависит от:
- Нужно ли состояние? → PROTOTYPE
- Состояние для запроса? → REQUEST
- Состояние для пользователя? → SESSION
- Stateless и переиспользуемое? → SINGLETON (по умолчанию)
Основное правило: используй SINGLETON для всего, что stateless, и другие scopes только когда действительно нужны.