Java: Ленивая Инициализация Singleton

шаблон для создания синглтонов, кажется, что-то вроде:

public class Singleton {
    private static final Singleton instance = new Singleton();
    private Singleton(){
    }

    public static Singleton getInstance()
    {
        return instance;
    }
}

однако моя проблема заключается в том , как вы блокируете такой класс, если конструктор Singleton делает что-то, что не является дружественным к модульному тесту, например, вызывает внешнюю службу, Поиск jndi и т. д.

Я думаю, я мог бы рефакторинг это так:

public class Singleton {
    private static Singleton instance;
    private Singleton(){
    }

    public synchronized static Singleton getInstance()
    {
        if(instance == null)
             instance = new Singleton();
        return instance;
    }

     //for the unit tests
     public static void setInstance(Singleton s)
     {
          instancce = s;
     }
}

проблема в том, что только для модульной тестируемости я заставил getInstance синхронизироваться, поэтому только для тестирования аспекта окажет негативное влияние на реальное применение. Есть ли способ обойти это, кажется, любой другой вид ленивой инициализации не будет работать из-за сломанной природы шаблона двойной блокировки в java.

5 ответов


можно использовать шаблон фабрики для создания Синглтона и переключения реализаций в зависимости от среды.

или избегайте использования одноэлементного шаблона и используйте Инъекции Зависимостей вместо.


вы можете использовать перечисление как синглтон

enum Singleton {
    INSTANCE;
}

скажите, что ваш синглтон делает что-то нежелательное в модульных тестах, вы можете;

// in the unit test before using the Singleton, or any other global flag.
System.setProperty("unit.testing", "true");

Singleton.INSTANCE.doSomething();

enum Singleton {
    INSTANCE;
    {
        if(Boolean.getBoolean("unit.testing")) {
           // is unit testing.
        } else {
           // normal operation.
        }
    }
}

Примечание: нет синхронизированных блоков или явной блокировки. Экземпляр не будет загружен до тех пор, пока .класс доступен и не инициализируется, пока не будет использован член. при условии, что вы используете только Singleton.INSTANCE, а не Singleton.class не будет проблем со значением, используемым для инициализации изменения позже.


изменить: если вы используете только Singleton.class это может не инициализировать класс. Это не в этом примере на Java 8 update 112.

public class ClassInitMain {
    public static void main(String[] args) {
        System.out.println("Printing a class reference");
        Class clazz = Singleton.class;
        System.out.println("clazz = " + clazz);
        System.out.println("\nUsing an enum value");
        Singleton instance = Singleton.INSTANCE;
    }

    static enum Singleton {
        INSTANCE;

        Singleton() {
            System.out.println(getClass() + " initialised");
        }
    }
}

печать

Printing a class reference
clazz = class ClassInitMain$Singleton

Using an enum value
class ClassInitMain$Singleton initialised

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

Я склонен избегать синглетов, но вы можете использовать шаблон держателя просто отлично, если вам это нужно, как рекомендовано в Josh Bloch's Эффективная Java:

public class Foo
{
  static class Holder
  {
    static final Foo instance = new Foo();
  }

  public static Foo getInstance()
  {
    return Holder.instance;
  }

  private Foo()
  {
  }

  // ...
}

EDIT: исправлена ссылка.


вы можете внедрить одноэлементный экземпляр зависимости, переопределить getInstance () из кода модульного теста, использовать аспектно-ориентированное программирование для перехвата вызова метода и возврата другого объекта или использовать такой инструмент, как jmockit что позволяет вам высмеивать почти все, включая статику, окончательные классы, конструкторы и все, что люди обычно говорят, "непроверено"."

один подход, который я принял в устаревших системах (где я хотел сделать что-то проверяемое с помощью минимальное влияние на архитектуру системы) состояло в изменении заводских методов (getInstance), чтобы проверить системное свойство для альтернативной реализации, которую я бы создал вместо этого. Это было установлено на альтернативный, макет объекта в наборе модульных тестов.

Что касается оператора" двойная проверка блокировки нарушена", это уже не так, если вы используете ключевое слово volatile и Java >= 1.5. Он был сломан (даже летучие) с 1.4 и ранее, но если вы знаете свой код будет работать только на последних JVMs, я бы не беспокоился об этом. Но я бы все равно не использовал синглтон: наличие контейнера DI/IOC для управления жизненным циклом объекта решило бы обе ваши проблемы (тестируемость и узкое место синхронизированного доступа) гораздо более элегантно.


Как насчет ленивой инициализации на этапе сборки, где вы выполняете модульные тесты. Затем вы изменяете код обратно на inline initialize перед его компиляцией для распространения.

ваш производственный код инициализируется inline, за исключением тестов. Возможно, это несоответствие производства кстати и код тестирования может introdude жуков, но что?

(конечно, если это решение, мы позволим инструменту build phase + выполнить работу. Я вижу, что это облегчается с maven и dp4j).