找出Java类加载的位置

215

有没有人知道如何以编程方式找出Java类加载器实际从哪里加载类?

我经常在大型项目上工作,其中类路径变得非常长,手动搜索不是一个真正的选项。 最近,我遇到了一个问题,因为同一类在类路径中存在两个不同的位置,导致类加载器加载了一个错误版本的类。

那么,我怎样才能让类加载器告诉我实际的类文件在磁盘上来自哪里呢?

编辑:如果类加载器由于版本不匹配(或其他原因)而无法加载类,我们是否有办法在读取文件之前找出它尝试读取的文件名?

12个回答

209

这里是一个例子:

package foo;

public class Test
{
    public static void main(String[] args)
    {
        ClassLoader loader = Test.class.getClassLoader();
        System.out.println(loader.getResource("foo/Test.class"));
    }
}

这将输出:

file:/C:/Users/Jon/Test/foo/Test.class

41
为了减少重复打字,可以使用更短的版本:Test.class.getResource("Test.class"),它不会重复包名。 - meriton
38
为了经得起重构,可以改为以下代码:Test.class.getResource(Test.class.getSimpleName() + ".class") - leonbloy
1
对于 BouncyCastleProvider,需要完整的包名。 - Pavel Vlasov
3
getClassLoader()方法可能会返回null。请参考这里中对该方法进行扩展来处理这种情况。 - OldCurmudgeon
1
我使用这个来加载资源,但是它在这里失败了。具体来说,在运行Maven测试类时失败了 - 在那个时候,我的项目中的类尝试从/target/test-classes而不是/target/classes加载资源。问题通过使用new MyClass().getClass().getResource("...") - 或者在实例化上下文中只需使用this.getClass ...来解决。 - Tomáš Zato
显示剩余4条评论

110

另一种无需操作源代码即可找出类加载位置的方法是使用以下选项启动Java虚拟机:-verbose:class


6
这个很好地运作了,并且不会出现处理具有空ClassLoader类的问题。 - lexicalscope
2
@ries 如果不需要以编程方式完成此操作,那么这绝对是最好的方法,并且它确实解决了我的问题。然而,原帖的作者明确要求如何以编程方式完成此操作。 - SantiBailors

97
getClass().getProtectionDomain().getCodeSource().getLocation();

4
是的,虽然如果安装了安全管理器并且没有所需的权限,它就无法运行。 - Tom Hawtin - tackline
1
FYI,NPE = 空指针异常。HTH! - Evgeni Sergeev
只要您有一个实例的引用,这种方法就是首选,因为您可以从两个不同的位置加载相同的类。 - Miguel Ping
3
当从Java 9+模块中调用时也无法工作(当然,您在2008年不可能知道这一点)。 - Jeff G

29

这是我们使用的内容:

public static String getClassResource(Class<?> klass) {
  return klass.getClassLoader().getResource(
     klass.getName().replace('.', '/') + ".class").toString();
}

这将取决于ClassLoader实现的情况: getClass().getProtectionDomain().getCodeSource().getLocation()


21

当对象的 ClassLoader 注册为 null 时,Jon的版本会失败,这似乎意味着它是由Boot ClassLoader 加载的。

该方法处理这个问题:

public static String whereFrom(Object o) {
  if ( o == null ) {
    return null;
  }
  Class<?> c = o.getClass();
  ClassLoader loader = c.getClassLoader();
  if ( loader == null ) {
    // Try the bootstrap classloader - obtained from the ultimate parent of the System Class Loader.
    loader = ClassLoader.getSystemClassLoader();
    while ( loader != null && loader.getParent() != null ) {
      loader = loader.getParent();
    }
  }
  if (loader != null) {
    String name = c.getCanonicalName();
    URL resource = loader.getResource(name.replace(".", "/") + ".class");
    if ( resource != null ) {
      return resource.toString();
    }
  }
  return "Unknown";
}

5

仅编辑第一行:Main.class

Class<?> c = Main.class;
String path = c.getResource(c.getSimpleName() + ".class").getPath().replace(c.getSimpleName() + ".class", "");

System.out.println(path);

输出:

/C:/Users/Test/bin/

也许样式不太好看,但是功能很好!

3
通常情况下,我们不建议使用硬编码。我们可以先获取className,然后使用ClassLoader获取类的URL。
        String className = MyClass.class.getName().replace(".", "/")+".class";
        URL classUrl  = MyClass.class.getClassLoader().getResource(className);
        String fullPath = classUrl==null ? null : classUrl.getPath();

需要的是:URL classUrl = MyClass.class.getClassLoader().getResource("/" + className); - Andrew Coates
MyClass.class是一个重要的部分 - getClass()可以返回代理!然后你可以得到类名,比如MyClass$$EnhancerBySpringCGLIB$$a98db882.class,以及null URL。 - jalmasi

1
简单方法:

System.out.println(java.lang.String.class.getResource(String.class.getSimpleName()+".class"));

示例输出:

jar:file:/D:/Java/jdk1.8/jre/lib/rt.jar!/java/lang/String.class

或者

字符串 obj = "simple test"; 系统。打印ln(obj.getClass().getResource(obj.getClass().getSimpleName()+".class"));

输出示例:

jar:file:/D:/Java/jdk1.8/jre/lib/rt.jar!/java/lang/String.class


1

看一下这个类似的问题。 工具来发现相同的类..

我认为最相关的障碍是如果你有一个自定义的类加载器(从数据库或LDAP加载)


0

用于单元测试

如果您需要定位名为Coverage.java的类,该类位于com.acme.api包中

Class clazz = Coverage.class;
ClassLoader loader = clazz.getClassLoader();    
String coverageClazzCompiledAbsoluteTargetLocation = loader
.getResource(clazz.getCanonicalName().replace(".", File.separator) + ".class")
.getFile().replace(File.separatorChar + "$", "");
System.out.println(coverageClazzCompiledAbsoluteTargetLocation);

结果

/home/Joe/repositories/acme-api/target/test-classes/com/acme/api/Coverage.class

注意:由于Java需要编译,因此您无法获取.java文件的位置(src/main/java/...)。您需要将.java编译为.class文件,因此如果您使用Maven,则位置将在target文件夹内。

运行时:JAR

如果您将应用程序构建为jar文件,则类的位置将是jar文件位置加上clazz包。

运行时:WAR

如果您将应用程序构建为war文件,则类的位置将是war文件位置(Tomcat中的/bin文件夹)加上clazz包。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接