可能缺少运行时依赖性的Java类

时间:2012-07-11 12:01:05

标签: java classloader dependency-management

我有一个依赖于库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实现?是否有既定的模式?上面的代码片段是否过于苛刻而无法投入生产?

2 个答案:

答案 0 :(得分:3)

通常,当首次加载类时,如果它引用了不存在的类,则可能导致错误。所以是的,让一个类进行检查,另一个类实际访问外部包而不进行反射将按预期工作,至少在我到目前为止看到的所有实现上都是如此。它不必是内部类。

JVM规范中的Linking section为实现提供了极大的自由。如果您不使用两类方法,那么使用预先链接的实现中U验证将导致尝试加载 {{1导致X。规范也不要求验证引用类,但它也不禁止这样的早期验证。但它确实需要

  

解析期间检测到的任何错误都必须抛出   (直接或间接)使用符号引用的程序   类或接口。

似乎你应该可以安全地假设只有当你真正访问你的内部类时才会抛出错误。如果你看一下这个答案的历史,你会发现我已经两次改变了我的意见,所以这次我无法保证我正确读到它......: - /

答案 1 :(得分: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);
    }
}

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

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