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

Может ли Singleton бин иметь два экземпляра?

2.0 Middle🔥 171 комментариев
#SOLID и паттерны проектирования#Spring Framework

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

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

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

Может ли Singleton бин иметь два экземпляра?

Да, Singleton бин в Spring может иметь два экземпляра при определённых условиях.

Сценарий 1: Несколько ApplicationContext

@Configuration
public class Config1 {
    @Bean
    public SingletonService singletonService() {
        return new SingletonService();
    }
}

@Configuration
public class Config2 {
    @Bean
    public SingletonService singletonService() {
        return new SingletonService();
    }
}

public class Main {
    public static void main(String[] args) {
        ApplicationContext context1 = new AnnotationConfigApplicationContext(Config1.class);
        ApplicationContext context2 = new AnnotationConfigApplicationContext(Config2.class);
        
        SingletonService bean1 = context1.getBean(SingletonService.class);
        SingletonService bean2 = context2.getBean(SingletonService.class);
        
        System.out.println(bean1 == bean2); // false - ДВА разных экземпляра
    }
}

Когда две разные ApplicationContext создают бины — это ДВА разных экземпляра.

Сценарий 2: ClassLoader

Если один и тот же класс загружен двумя разными ClassLoader'ами — это будут два разных класса и два разных экземпляра.

Сценарий 3: Сериализация

@Component
public class SingletonService implements Serializable {
    private Object readResolve() {
        return this;
    }
}

Без метода readResolve() десериализация создаёт новый объект.

Сценарий 4: Reflection

public class EagerSingleton {
    public static final EagerSingleton INSTANCE = new EagerSingleton();
    private EagerSingleton() { }
}

Constructor<?> constructor = EagerSingleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
EagerSingleton obj2 = (EagerSingleton) constructor.newInstance();

Reflection может создать новый экземпляр, обойдя приватный конструктор.

Почему это происходит?

  1. Spring Singleton — это Singleton на уровне контейнера, не на уровне JVM
  2. Разные контексты — разные контейнеры, разные бины
  3. ClassLoader — разные загрузчики = разные классы
  4. Reflection — может обойти приватные конструкторы
  5. Сериализация — десериализация создаёт новый объект

Гарантированный Singleton — Enum

public enum TrueSingleton {
    INSTANCE;
    
    public void doSomething() {
        System.out.println("Истинный Singleton");
    }
}

Enum — это наиболее безопасный способ. Он:

  • Потокобезопасен по умолчанию
  • Защищен от сериализации
  • Защищен от Reflection
  • Не может быть клонирован

Bill Pugh Singleton

public class BillPughSingleton {
    private BillPughSingleton() { }
    
    private static class SingletonHelper {
        private static final BillPughSingleton INSTANCE = new BillPughSingleton();
    }
    
    public static BillPughSingleton getInstance() {
        return SingletonHelper.INSTANCE;
    }
}

Паттерн использует классовую инициализацию для потокобезопасного Singleton.

Итог

Spring Singleton НЕ гарантирует ровно один экземпляр на всё приложение. Он гарантирует один экземпляр в рамках одного ApplicationContext.

Для действительно глобального Singleton используй Enum или Bill Pugh паттерн.