使用自定义 ClassLoader 拦截 ClassLoader.getResource(String) 调用
Intercepting ClassLoader.getResource(String) calls, with a custom ClassLoader
我们正在尝试调试 WebStart 的一个不可重现的问题,在该问题中访问 Jars 内的资源将 "randomly" 失败。也许每 1000 个应用程序中就有一个 运行 会以这个错误结束,这种错误可能发生在从 jar 中读取资源的任何地方。
在 Google 和 Java 错误数据库中搜索没有找到任何类似的东西(或者至少,没有任何帮助)。
我们正试图通过 "instrumenting" 应用程序获取有关客户端发生的情况的更多信息,因此我们跟踪对 ClassLoader.getResource(String)
的所有调用(包括间接通过 ClassLoader.getResourceAsStream(String)
调用)。在不更改应用程序代码的情况下,我创建了一个 "launcher",它将 运行 整个应用程序与自定义类加载器。
不幸的是,我的 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);
}
}
我做错了什么?
是的。这在原则上是可行的。
但是,您必须考虑资源加载代码如何到达 class 加载程序。由于 class 没有出现,看起来他们使用了父 class 加载器。
您必须考虑不同的场景:
使用上下文 class 加载程序的代码,例如:
Thread.currentThread().getContextClassLoader().getResource("via-context");
这很容易实现,只需在调用 main 之前设置即可:
Thread.currentThread().setContextClassLoader(loader);
Method main = realAppClasss.getMethod("main", String[].class);
main.invoke(null, (Object) args);
接下来您要考虑的是来自当前 class 的 'takes' class 加载器代码,并加载它。当你 class 通过父 class 加载器加载时,它也会使用那个 class 加载器来获取资源。喜欢:
MyRealAppClass.class.getResource("via-class");
MyRealAppClass.class.getClassLoader().getResource("via-class");
objectInfApp.getClass().getClassLoader().getResource("via-class");
为避免这种情况,您必须确保应用程序 class 确实是使用您的 class 加载程序而不是父加载程序加载的。对于一个简单的主程序,您可以从 URL class 加载器扩展,跳过任何父级和用户 URL 的原始 class 路径。喜欢:
// 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);
这应该涵盖了最基本的情况。当您实际的应用程序本身有更多 class 加载程序技巧时,您可能需要设置更多。
我们正在尝试调试 WebStart 的一个不可重现的问题,在该问题中访问 Jars 内的资源将 "randomly" 失败。也许每 1000 个应用程序中就有一个 运行 会以这个错误结束,这种错误可能发生在从 jar 中读取资源的任何地方。
在 Google 和 Java 错误数据库中搜索没有找到任何类似的东西(或者至少,没有任何帮助)。
我们正试图通过 "instrumenting" 应用程序获取有关客户端发生的情况的更多信息,因此我们跟踪对 ClassLoader.getResource(String)
的所有调用(包括间接通过 ClassLoader.getResourceAsStream(String)
调用)。在不更改应用程序代码的情况下,我创建了一个 "launcher",它将 运行 整个应用程序与自定义类加载器。
不幸的是,我的 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);
}
}
我做错了什么?
是的。这在原则上是可行的。
但是,您必须考虑资源加载代码如何到达 class 加载程序。由于 class 没有出现,看起来他们使用了父 class 加载器。 您必须考虑不同的场景:
使用上下文 class 加载程序的代码,例如:
Thread.currentThread().getContextClassLoader().getResource("via-context");
这很容易实现,只需在调用 main 之前设置即可:
Thread.currentThread().setContextClassLoader(loader);
Method main = realAppClasss.getMethod("main", String[].class);
main.invoke(null, (Object) args);
接下来您要考虑的是来自当前 class 的 'takes' class 加载器代码,并加载它。当你 class 通过父 class 加载器加载时,它也会使用那个 class 加载器来获取资源。喜欢:
MyRealAppClass.class.getResource("via-class");
MyRealAppClass.class.getClassLoader().getResource("via-class");
objectInfApp.getClass().getClassLoader().getResource("via-class");
为避免这种情况,您必须确保应用程序 class 确实是使用您的 class 加载程序而不是父加载程序加载的。对于一个简单的主程序,您可以从 URL class 加载器扩展,跳过任何父级和用户 URL 的原始 class 路径。喜欢:
// 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);
这应该涵盖了最基本的情况。当您实际的应用程序本身有更多 class 加载程序技巧时,您可能需要设置更多。