5

我有依赖于库 X 的实用程序类 U,并且必须放入一个包中,该包将用于具有 X 的程序(它应该做它的正常工作)和没有 X 的地方(它不应该做任何事情)。在不将班级一分为二的情况下,我找到了一个解决此问题的简单模式:

package foo;
import bar.MisteriousX;

public class U {
    static private boolean isXPresent = false;
    static {
        try {
            isXPresent = (null != U.class.getClassLoader().loadClass("bar.MisteriousX"));
        } catch (Exception e) {
            // loading of a sample X class failed: no X for you
        }
    }
    public static void doSomething() {
        if (isXPresent) {
            new Runnable() {
                public void run() { 
                    System.err.println("X says " + MisteriousX.say());
                }
            }.run();
        } else {
            System.err.println("X is not there");
        }
    }
    public static void main(String args[]) { doSomething(); } 
}

使用这种模式,U 需要 X 存在才能编译,但在有或没有 X 存在的情况下运行时都按预期工作。除非对 X 库的所有访问都在内部类中,否则此代码会启动类加载器异常。

问题:是否保证导入解析在任何地方都能像这样工作,还是取决于 JVM/ClassLoader 实现?这有一个既定的模式吗?上面的代码片段是否太骇人听闻而无法投入生产?

4

2 回答 2

3

通常,当一个类第一次加载时,如果它引用了一个不存在的类,那可能会导致错误。所以是的,让一个类进行检查,而另一个类在没有反射的情况下实际访问外部包将按预期工作,至少在我迄今为止看到的所有实现中是这样。它不必是内部类。

JVM 规范中的链接部分为实现提供了极大的自由度。如果您不使用二分类方法,那么在使用急切链接的实现中进行验证U将导致尝试加载 X,这会导致LinkageError. 规范也不需要验证引用类,但也不禁止这种早期验证。然而,它确实要求

在解决过程中检测到的任何错误都必须在程序中(直接或间接)使用对类或接口的符号引用的点抛出。

似乎您应该可以安全地假设仅在您实际访问内部类时才引发错误。如果你查看这个答案的历史,你会发现我已经两次改变了我的观点,所以不能保证这次我读对了……:-/

于 2012-07-11T12:17:18.270 回答
3

我在jOOQ中做了类似的事情,以加载可选的日志框架依赖项。不过,我和你一样认为这有点像黑客攻击。字段初始化的示例代码片段,取决于类的可用性:

public final class JooqLogger {

    private org.slf4j.Logger slf4j;
    private org.apache.log4j.Logger log4j;
    private java.util.logging.Logger util;

    public static JooqLogger getLogger(Class<?> clazz) {
        JooqLogger result = new JooqLogger();

        // Prioritise slf4j
        try {
            result.slf4j = org.slf4j.LoggerFactory.getLogger(clazz);
        }

        // If that's not on the classpath, try log4j instead
        catch (Throwable e1) {
            try {
                result.log4j = org.apache.log4j.Logger.getLogger(clazz);
            }

            // If that's not on the classpath either, ignore most of logging
            catch (Throwable e2) {
                result.util= java.util.logging.Logger.getLogger(clazz.getName());
            }
        }

        return result;
    }

    [...]

然后,稍后,根据先前加载的类切换记录器:

public boolean isTraceEnabled() {
    if (slf4j != null) {
        return slf4j.isTraceEnabled();
    }
    else if (log4j != null) {
        return log4j.isTraceEnabled();
    }
    else {
        return util.isLoggable(Level.FINER);
    }
}

其余的源代码可以在这里看到。本质上,我对 slf4j 和 log4j 都有编译时依赖项,我在运行时使用与您类似的模式将其呈现为可选。

这可能会在 OSGi 环境中引起问题,例如,类加载比仅使用标准 JDK/JRE 类加载机制时要复杂一些。但是,到目前为止,我还没有意识到任何问题

于 2012-07-11T12:17:40.037 回答