Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
# Как работают Singleton бины в Spring
Singleton — это scope бина в Spring Framework, при котором только один экземпляр бина создаётся и переиспользуется на протяжении всего жизненного цикла приложения.
Что такое Singleton scope
@Component // По умолчанию scope = SINGLETON
public class UserService {
public void saveUser(User user) {
// ...
}
}
// Эквивалентно:
@Component
@Scope("singleton") // Явный singleton scope
public class UserService {
}
Как Spring создаёт Singleton бины
1. Инициализация контекста
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
// В этот момент Spring:
// 1. Сканирует классы с @Component, @Service, @Repository
// 2. Создаёт Bean Definitions
// 3. Создаёт экземпляры singleton бинов (eager initialization)
2. Создание единственного экземпляра
Spring IoC Container (Singleton Registry)
┌──────────────────────────────────────┐
│ Singleton Registry (Map) │
├──────────────────────────────────────┤
│ "userService" → UserService@1a2b3 │
│ "paymentService" → PaymentService@4c5d6 │
│ "emailService" → EmailService@7e8f9 │
└──────────────────────────────────────┘
↑
Один экземпляр для всех инъекций
Практический пример
@Service
public class UserService {
private static int instanceCount = 0;
public UserService() {
instanceCount++;
System.out.println("UserService created! Count: " + instanceCount);
}
public void saveUser(User user) {
System.out.println(this.hashCode() + ": Saving user");
}
}
@RestController
public class UserController {
@Autowired
private UserService userService1; // Этот же экземпляр
@Autowired
private UserService userService2; // Тот же самый объект!
}
public class Main {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
// Вывод: "UserService created! Count: 1"
UserService service1 = context.getBean(UserService.class);
UserService service2 = context.getBean(UserService.class);
System.out.println(service1 == service2); // true!
System.out.println(service1.hashCode()); // 1a2b3c
System.out.println(service2.hashCode()); // 1a2b3c (один и тот же)
// UserService создан только один раз!
}
}
Внутренний механизм: DefaultListableBeanFactory
Spring использует регистр для хранения singleton бинов:
// Внутри Spring (упрощённо)
public class DefaultListableBeanFactory {
// Регистр singleton бинов
private final Map<String, Object> singletonObjects =
new ConcurrentHashMap<>();
public Object getBean(String beanName) {
// 1. Проверяем, есть ли уже в регистре
if (singletonObjects.containsKey(beanName)) {
return singletonObjects.get(beanName); // Возвращаем существующий
}
// 2. Если нет — создаём
Object instance = createBeanInstance(beanName);
// 3. Сохраняем в регистр
singletonObjects.put(beanName, instance);
return instance;
}
}
Другие Scopes в Spring
// 1. SINGLETON — один на всё приложение (по умолчанию)
@Component
@Scope("singleton")
public class UserService {}
// 2. PROTOTYPE — новый экземпляр при каждой инъекции
@Component
@Scope("prototype")
public class RequestProcessor {}
// Создаётся заново каждый раз!
// 3. REQUEST — один на HTTP request (только для web)
@Component
@Scope("request")
public class RequestData {}
// Разные request → разные экземпляры
// 4. SESSION — один на HTTP session
@Component
@Scope("session")
public class UserSession {}
// 5. APPLICATION — один на ServletContext
@Component
@Scope("application")
public class GlobalConfig {}
Пример: Singleton vs Prototype
@Service
@Scope("singleton")
public class SingletonService {
private int counter = 0;
public void increment() {
counter++;
}
public int getCounter() {
return counter;
}
}
@Service
@Scope("prototype")
public class PrototypeService {
private int counter = 0;
public void increment() {
counter++;
}
public int getCounter() {
return counter;
}
}
public class Test {
public static void main(String[] args) {
ApplicationContext context = ...;
// SINGLETON
SingletonService s1 = context.getBean(SingletonService.class);
SingletonService s2 = context.getBean(SingletonService.class);
s1.increment(); // counter = 1
System.out.println(s2.getCounter()); // 1 — один и тот же объект!
// PROTOTYPE
PrototypeService p1 = context.getBean(PrototypeService.class);
PrototypeService p2 = context.getBean(PrototypeService.class);
p1.increment(); // счётчик p1 = 1
System.out.println(p2.getCounter()); // 0 — разные объекты!
}
}
Потокобезопасность Singleton
Singleton бины НЕ являются автоматически потокобезопасными!
@Service
public class CounterService {
private int count = 0; // ❌ Это может быть race condition!
public void increment() {
count++; // Не атомарная операция
}
}
// Два потока одновременно:
// Thread 1: count = 0, ++, count = 1, сохрани 1
// Thread 2: count = 0, ++, count = 1, сохрани 1
// Результат: count = 1 вместо 2
Решение: Use atomic operations или synchronized
@Service
public class SafeCounterService {
private final AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet(); // ✅ Потокобезопасно
}
public int getCount() {
return count.get();
}
}
// Или
@Service
public class SyncCounterService {
private int count = 0;
public synchronized void increment() { // ✅ Синхронизированный доступ
count++;
}
}
Жизненный цикл Singleton бина
1. INSTANTIATION — создание объекта
↓
2. PROPERTY INJECTION — внедрение зависимостей
↓
3. @PostConstruct — вызов инициализации
↓
4. IN USE — бин используется на протяжении жизни приложения
↓
5. @PreDestroy — вызов cleanup при выключении приложения
Пример с жизненным циклом
@Service
public class DatabaseConnection {
private Connection conn;
@PostConstruct
public void init() {
System.out.println("Инициализация связи с БД");
this.conn = DriverManager.getConnection(...);
}
public void executeQuery(String sql) {
// Используем conn
}
@PreDestroy
public void cleanup() {
System.out.println("Закрытие связи с БД");
conn.close();
}
}
Best Practices для Singleton бинов
-
Избегай состояния (stateful) в singleton бинах
// ❌ Плохо @Service public class UserService { private User currentUser; // Состояние! } // ✅ Хорошо @Service public class UserService { public void saveUser(User user) { // Параметр вместо состояния // ... } } -
Используй dependency injection, не сохраняй ссылки на bean
// ❌ Плохо UserService service = context.getBean(UserService.class); service.saveUser(user); // Неправильно // ✅ Хорошо @Autowired private UserService service; // Spring управляет service.saveUser(user); -
Будь осторожен с multi-threading
@Service public class ThreadSafeService { private final Object lock = new Object(); private List<Item> items = new ArrayList<>(); public void addItem(Item item) { synchronized (lock) { items.add(item); } } } -
Используй lazy initialization если нужно
@Component @Scope(value = "singleton", proxyMode = ScopedProxyMode.TARGET_CLASS) public class HeavyService { // Создаётся только при первом использовании }
Итог
Singleton бины в Spring:
- Создаются один раз при инициализации контекста
- Переиспользуются для всех инъекций
- Экономят память (один экземпляр на приложение)
- Требуют потокобезопасности при работе с состоянием
- Живут до shutdown приложения (вызывается @PreDestroy)