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

Может ли быть несколько экземпляров 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Количество экземпляровВремя жизниПотокобезопасностьИспользование
SINGLETON1 на контекстВесь appНужна синхронизацияБД, сервисы
PROTOTYPEN (новый каждый раз)КлиентНенужнаStateful объекты
REQUEST1 на запрос1 HTTP запросНе нужнаПараметры запроса
SESSION1 на сессиюHTTP сессияНе нужнаДанные сессии
WEBSOCKET1 на соединение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:

  1. SINGLETON (по умолчанию) — один экземпляр
  2. PROTOTYPE — новый экземпляр каждый раз
  3. REQUEST — новый экземпляр на каждый HTTP запрос
  4. SESSION — один экземпляр на сессию
  5. WEBSOCKET — один экземпляр на WebSocket соединение

Выбор Scope зависит от:

  • Нужно ли состояние? → PROTOTYPE
  • Состояние для запроса? → REQUEST
  • Состояние для пользователя? → SESSION
  • Stateless и переиспользуемое? → SINGLETON (по умолчанию)

Основное правило: используй SINGLETON для всего, что stateless, и другие scopes только когда действительно нужны.

Может ли быть несколько экземпляров Bean в Spring? | PrepBro