多个类加载器和单例

问题描述 投票:0回答:3

我最近了解到一个java程序可以有多个类加载器。我发现一些 StackOverflow 帖子解释了如何创建单例对象。

常见的方法是这样的:

if (instance == null) {
      ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
      if (classLoader == null) {
        classLoader = Singleton.class.getClassLoader();
      }
      Class<?> sclass = (classLoader.loadClass(Singleton.class.getCanonicalName()));
}

我不太明白这是如何摆脱多个类加载器的问题的。如果您有多个 ClassLoader,则当每个 ClassLoader 尝试加载该类时,该实例将为 null。这不是正确的吗?如果我能得到关于其工作原理的简短解释,我会发现它会很有帮助。

此外,我们什么时候能够检索实际的 Singleton 实例?我可以做类似

instance = (Singleton) sclass.newInstance();
的事情,但这将为每个
ClassLoader
创建一个新实例。

java singleton classloader
3个回答
0
投票

这如何摆脱多个类加载器的问题。

我不明白你指的是哪个问题,但它并没有消除任何问题。

当它们每个人尝试加载类时,实例将为空。

实例不会为空。

我们能够检索实际的 Singleton 实例吗?

当线程拥有所需的ClassLoader时,您可以使用任一方法获取SIngleton实例。

Singleton s = Singleton.INSTANCE;

Singleton s = Singleton.getInstance();

取决于您通常如何访问单例。如果您需要在另一个类加载器中使用单例,则必须执行相同的操作,除非通过反射。


0
投票

有很多这样的例子,但它们似乎是从JavaWorld上的一篇文章复制的。

它并没有消除多个类加载器的问题。您仍然可以拥有多个实例。

它尝试通过线程上下文类加载器加载类。问题是您不能依赖它作为任何特定的类加载器,或者对于每个线程或调用它的位置都相同。如果这不可用,那么它会尝试,

Singleton.class.getClassLoader()
这是无论如何都会使用的类加载器,并且不保证每次都相同。

如果上下文类加载器始终相同,那么每次都会获得相同的实例,但需要通过反射访问该实例。您无法将其转换为正确的类型,否则您会得到一个

ClassCastException
,因为加载该类的类加载器与当前的类加载器不匹配。

这里是一个演示

getClass
方法用法的示例,使用 ByteBuddy 库创建类加载器:

public class Main {
    public static void main(String[] args) throws Exception {
        run();
        run();
    }

    public static void run() throws Exception {
        ClassLoader parent = Main.class.getClassLoader();

        byte[] runnerBytes = Files.readAllBytes(new File("/path/to/bin/Runner.class").toPath());
        byte[] singletonBytes = Files.readAllBytes(new File("/path/to/bin/Singleton.class").toPath());

        Map<String, byte[]> classes = new HashMap<>();
        classes.put("Runner", runnerBytes);
        classes.put("Singleton", singletonBytes);

        ByteArrayClassLoader classLoader = new ByteArrayClassLoader.ChildFirst(
            parent,
            classes,
            null,
            ByteArrayClassLoader.PersistenceHandler.MANIFEST,
            PackageDefinitionStrategy.NoOp.INSTANCE
        );

        Class<?> clazz = classLoader.loadClass("Runner");

        Thread.currentThread().setContextClassLoader(classLoader);
        Object instance = clazz.newInstance();
        clazz.getMethod("run").invoke(instance);
    }
}

public class Runner {
    public void run() {
        try {
            Object instance = getClass("Singleton").getMethod("getInstance").invoke(null);
            Object result = instance.getClass().getMethod("getValue").invoke(instance);
            System.out.println("RESULT: " + result);
        } catch(ReflectiveOperationException e) {
            e.printStackTrace();
        }
    }

    private static Class<?> getClass(String className) throws ClassNotFoundException {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        if(classLoader == null) {
            classLoader = Singleton.class.getClassLoader();
        }
        return (classLoader.loadClass(className));
    }
}

public class Singleton {
    private static final Singleton instance = new Singleton();
    private double value = Math.random();

    public static Singleton getInstance() {
        return instance;
    }

    public double getValue() {
        return value;
    }
}

结果:

RESULT: 0.8998675708591397
RESULT: 0.7230302140857906

所以这不会创建相同的实例。


0
投票

这不是创建单例对象的常见方法。

常见的egar做法是这样的:

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

如果你想偷懒,就这样:

public class SomeClass {
    private static SomeClass instance;
    private SomeClass() {}
    public static SomeClass getInstance() {
        if (instance == null) instance = new SomeClass();
        return instance;
    }
}

当类加载器加载类时,静态代码确实会运行。并且该类有可能被多个类加载器加载。在大多数情况下,按照惯例,这是不可能的。正确实现的类加载器应该在尝试尝试之前要求其父级加载该类。在您的情况下,如果上下文类加载器是基类加载器的子级,则无论您询问哪个类加载器,您的 Singleton.class 都应该是完全相同的类。

话虽如此,有一些类加载器打破了这一约定,其中最著名的是 Tomcat 类加载器。当类加载器违反此规则时,您可能会遇到一些非常奇怪的事情。例如,两个具有相同名称和字节码的类彼此不相等。这意味着如果一个对象是使用一个类加载器实例化的,而另一个对象是使用另一个类加载器实例化的,即使它们具有相同的值(它们的 .equals() 方法应该返回 true),但也不会,因为它们的类不匹配。 instanceof 将不起作用,如果您要绕过实例并仅强制转换它,它将抛出 ClassCastException。

这里的底线是你会遇到更大的问题

© www.soinside.com 2019 - 2024. All rights reserved.