【问题标题】:Intercepting ClassLoader.getResource(String) calls, with a custom ClassLoader使用自定义 ClassLoader 拦截 ClassLoader.getResource(String) 调用
【发布时间】:2016-05-25 12:24:34
【问题描述】:

我们正在尝试调试 WebStart 的一个不可重现的问题,即对 Jars 内资源的访问将“随机”失败。可能每运行 1000 个应用程序就有一个会以这个错误结束,这可能发生在从 jar 读取资源的任何地方。

在 Google 和 Java Bug 数据库中搜索没有任何相似之处(或者至少没有任何帮助)。

我们正试图通过“检测”应用程序来获取更多关于客户端发生的情况的信息,以便我们跟踪对ClassLoader.getResource(String) 的所有调用(包括间接通过ClassLoader.getResourceAsStream(String))。在不更改应用程序代码的情况下,我创建了一个“启动器”,它将使用自定义类加载器运行整个应用程序。

不幸的是,我的 ClassLoader 似乎被绕过了。我没有看到任何预期的 System.out 输出。这是我尝试过的:

private static final class MyClassLoader extends ClassLoader {
    private MyClassLoader() {
        super(TheClassThatMainIsIn.class.getClassLoader());
    }

    @Override
    public URL getResource(String name) {
        System.out.println("getResource("+name+")");
        // Snip
        return super.getResource(name);
    }

    @Override
    public InputStream getResourceAsStream(String name) {
        System.out.println("getResourceAsStream("+name+")");
        final URL url = getResource(name);
        try {
            return url != null ? url.openStream() : null;
        } catch (final IOException e) {
            return null;
        }
    }
}

public static void main(String[] args) {
    System.out.println("Starting MyRealApp Launcher ...");
    final MyClassLoader loader = new MyClassLoader();
    try {
        Class<?> realAppClasss = loader.loadClass("MyRealAppClass");
        Method main = realAppClasss.getMethod("main", String[].class);
        main.invoke(null, (Object) args);
    } catch (final RuntimeException e) {
        throw e;
    } catch (final Error e) {
        throw e;
    } catch (final InvocationTargetException e) {
        final Throwable cause = e.getCause();
        if (cause instanceof RuntimeException) {
            throw (RuntimeException) cause;
        }
        if (cause instanceof Error) {
            throw (Error) cause;
        }
        throw new UndeclaredThrowableException(cause);
    } catch (final Throwable t) {
        throw new UndeclaredThrowableException(t);
    }
}

我在这里做错了什么?

【问题讨论】:

  • 你是否覆盖了 findClass() 和 loadClassData?
  • 不,我不会覆盖那些;我对“课程”不感兴趣;只是“资源”。我是不是该?如果是这样,除了将调用传递给基类之外我应该做什么?
  • 不,你不应该这样做。我第一次阅读时误解了你的问题。你在做什么似乎是正确的。我建议在您的项目中包含 ClassLoader 源代码,使用调试器逐步执行此操作,以查看它为什么不调用您的覆盖方法。
  • Java 中的 CL 用于委托策略。在加载类之前,CL 会询问其父类 X 的字节定义是否已知。只有当没有祖先知道类字节时,实际的 CL 才会加载类定义。在您的情况下,似乎MyRealAppClass 是在应用程序启动时加载的,因此loadClass 调用被委托给系统CL,它还负责@Gamlor 已经指出的资源加载-您可以尝试-verbose:class看看之前是否加载过

标签: java classloader


【解决方案1】:

是的。原则上这是可行的。

但是,您必须考虑资源加载代码是如何到达类加载器的。由于该类没有出现,看起来他们使用了父类加载器。 您必须考虑不同的场景:

使用上下文类加载器的代码,例如:

Thread.currentThread().getContextClassLoader().getResource("via-context");

这很容易实现,通过在调用 main 之前设置它:

Thread.currentThread().setContextClassLoader(loader);
Method main = realAppClasss.getMethod("main", String[].class);
main.invoke(null, (Object) args);

接下来你要考虑的是从当前类中“获取”类加载器并加载它的代码。当您通过父类加载器加载类时,它还将使用该类加载器来获取资源。喜欢:

MyRealAppClass.class.getResource("via-class");
MyRealAppClass.class.getClassLoader().getResource("via-class");
objectInfApp.getClass().getClassLoader().getResource("via-class");

为避免您必须确保应用类实际上是使用您的类加载器而不是父类加载的。对于一个简单的 main,您可以从 URL 类加载器扩展,跳过任何父级并使用 URL 的原始类路径。喜欢:

// URL class loader to lookup in jars etc
private static class MyClassLoader extends URLClassLoader
{
    public MyClassLoader(URL[] urls) {
        // Use the given URLs and skip any parent class loader, directly go to the system loader
        super(urls,null);
    }

// ...

// Then setup the class path
    String[] classPath = System.getProperty("java.class.path").split(";");
    URL[] classPathUrls = new URL[classPath.length];
    for (int i = 0; i < classPath.length; i++) {
        classPathUrls[i] = new File(classPath[i]).toURL();

    }
    MyClassLoader loader = new MyClassLoader(classPathUrls);

这应该涵盖最基本的情况。当您的实际应用程序本身有更多的类加载器技巧时,您可能需要设置更多。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-18
    • 2011-05-05
    • 2022-07-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多