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

Как работает Singletone бины?

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

Комментарии (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 бинов

  1. Избегай состояния (stateful) в singleton бинах

    // ❌ Плохо
    @Service
    public class UserService {
        private User currentUser;  // Состояние!
    }
    
    // ✅ Хорошо
    @Service
    public class UserService {
        public void saveUser(User user) {  // Параметр вместо состояния
            // ...
        }
    }
    
  2. Используй dependency injection, не сохраняй ссылки на bean

    // ❌ Плохо
    UserService service = context.getBean(UserService.class);
    service.saveUser(user);  // Неправильно
    
    // ✅ Хорошо
    @Autowired
    private UserService service;  // Spring управляет
    service.saveUser(user);
    
  3. Будь осторожен с 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);
            }
        }
    }
    
  4. Используй lazy initialization если нужно

    @Component
    @Scope(value = "singleton", proxyMode = ScopedProxyMode.TARGET_CLASS)
    public class HeavyService {
        // Создаётся только при первом использовании
    }
    

Итог

Singleton бины в Spring:

  • Создаются один раз при инициализации контекста
  • Переиспользуются для всех инъекций
  • Экономят память (один экземпляр на приложение)
  • Требуют потокобезопасности при работе с состоянием
  • Живут до shutdown приложения (вызывается @PreDestroy)
Как работает Singletone бины? | PrepBro