Java:延迟初始化单例

发布于 2024-11-04 22:20:47 字数 864 浏览 0 评论 0 原文

创建单例的模式似乎是这样的:

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

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

但是我的问题是,如果单例构造函数做了一些不适合单元测试的操作,例如调用外部服务、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中双重锁定模式的破坏性,似乎任何其他类型的延迟初始化都不起作用。

The pattern to create singletons seems to be something like:

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

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

However my problem is how do you Unit with a class like this if the Singleton Constructor does something that is not unit test friendly e.g. calls external service , jndi lookup etc.

I would think i could refactor it like:

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;
     }
}

The problem now is that just for unit testability I have forced the getInstance to be synchronized so just for testing aspect it will have a negative impact on the real application. Is there a way around it, it seems any other sort of lazy initialization will not work because of the broken nature of double locking pattern in java.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

纵情客 2024-11-11 22:20:47

你可以使用枚举作为单例

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.
        }
    }
}

注意:不需要同步块或显式锁。在访问 .class 之前,不会加载 INSTANCE;在使用成员之前,不会初始化实例。如果您只使用 Singleton.INSTANCE 而不是 Singleton.class,那么稍后用于初始化更改的值不会出现问题。


编辑:如果您仅使用 Singleton.class 这可能不会初始化该类。在 Java 8 更新 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

You can use an enum as a Singleton

enum Singleton {
    INSTANCE;
}

Say your singleton does something undesirable in unit tests, you can;

// 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.
        }
    }
}

Note: there is no synchronised blocks or explicit lock needed. The INSTANCE will not be loaded until the .class is accessed and not initialised until a member is used. provided you only use Singleton.INSTANCE and not Singleton.class there won't be a problem with the value used to initialise changing later.


Edit: if you use just the Singleton.class this might not initialise the class. It doesn't in this example on 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");
        }
    }
}

prints

Printing a class reference
clazz = class ClassInitMain$Singleton

Using an enum value
class ClassInitMain$Singleton initialised
爱本泡沫多脆弱 2024-11-11 22:20:47

您可以使用工厂模式来创建单例,并根据环境切换实现。

或者,避免使用单例模式,而使用 依赖注入 代替。

You could use the Factory pattern to create the singleton, and switch implementations depending on evironment.

Or, avoid using the singleton pattern, and use Dependency Injection instead.

囚你心 2024-11-11 22:20:47

双重检查锁定在每种语言中都被破坏,而不仅仅是 Java。

我倾向于避开单例,但如果需要的话,您可以很好地使用持有者模式,正如 Josh Bloch 的 Effective Java 中所建议的:

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

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

  private Foo()
  {
  }

  // ...
}

编辑:修复了参考。

Double-checked locking is broken in every language, not just Java.

I tend to eschew singletons, but you can use the holder pattern just fine if you need them, as recommended in Josh Bloch's Effective Java:

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

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

  private Foo()
  {
  }

  // ...
}

EDIT: Repaired the reference.

暗藏城府 2024-11-11 22:20:47

您可以依赖注入单例实例,覆盖单元测试代码中的 getInstance(),使用面向方面的编程来拦截方法调用并返回不同的对象,或者使用诸如 jmockit 它可以让你模拟几乎任何东西,包括静态、最终类、构造函数,以及人们通常所说的“不可测试”的所有东西。

我在遗留系统中采用的一种方法(我想让一些可测试的东西对系统架构的影响最小)是修改工厂方法(getInstance)来检查我将实例化的替代实现的系统属性。这被设置为单元测试套件中的备用模拟对象。

至于“双重检查锁定被破坏”的说法,如果您使用 volatile 关键字,并且 Java >= 1.5,那么这不再是真的。它在 1.4 及更早版本中被破坏了(即使使用 volatile),但如果您知道您的代码将仅在最新的 JVM 上运行,我就不会担心它。但我也不会使用单例:让 DI/IOC 容器管理对象的生命周期将更优雅地解决您的两个问题(可测试性和同步访问器瓶颈)。

You can dependency inject the singleton instance, override the getInstance() from the unit test code, use aspect oriented programming to intercept the method call and return a different object, or use a tool like jmockit which lets you mock pretty much anything, including statics, final classes, constructors, and all the stuff people normally say is "untestable."

One approach I've taken in legacy systems (where I wanted to make something testable with a minimal impact on the system's architecture) was to modify the factory methods (getInstance) to check a system property for an alternate implementation that I would instantiate instead. This was set to an alternate, mock object in the unit test suite.

As for the "double checked locking is broken" statement, that's not really true anymore, if you use the volatile keyword, and Java >= 1.5. It was broken (even with volatile) with 1.4 and earlier, but if you know your code will be run on only recent JVMs, I wouldn't worry about it. But I also wouldn't use a singleton anyway: having a DI/IOC container manage the lifecycle of the object would solve both of your problems (testability and synchronized accessor bottleneck) much more elegantly.

青巷忧颜 2024-11-11 22:20:47

在执行单元测试的构建阶段进行延迟初始化怎么样?然后,在编译代码进行分发之前,将代码更改回内联初始化。

您的生产代码是内联初始化的,测试期间除外。也许生产和测试代码之间的这种差异可能会引入错误,但是哪个呢?

(当然,如果这是一个解决方案,我们让构建阶段 + 工具来完成工作。我看到 maven 和 dp4j)。

How about you lazy initialize in the build phase where you execute the unit tests. Then you change the code back to inline initialize before it's compiled for distribution.

Your production code is inline initialized, except during your tests. Perhaps this discrepancy btw production and testing code could introdude bugs, but which?

(Of course if this is a solution, we let a build phase + tool do the work. I see this facilitated with maven and dp4j).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文