以编程方式确定不需要导入的JRE类列表

4
我需要以编程方式找出哪些JRE类可以在编译单元中被引用而无需导入(用于静态代码分析)。我们可以忽略包局部类。根据JLS, 来自java.lang包的类是隐式导入的。输出应该是二进制类名列表。解决方案应该适用于纯Java 5及以上版本(没有Guava,Reflections等),并且与供应商无关。
欢迎任何可靠的基于Java的解决方案。

以下是我尝试过的一些注释:

乍一看,问题似乎归结为“如何从包中加载所有类?”,当然这几乎是不可能的,尽管存在几种解决方法(例如 thisthis,以及链接到那里的博客文章)。但我的情况要简单得多,因为不存在多个类加载器的问题。 java.lang 的东西总是可以由系统/引导类加载器加载的,而你无法在该包中创建自己的类。问题在于,系统类加载器不会透露其类路径,而链接的方法依赖于此。

到目前为止,我还没有成功获取系统类加载器的类路径,因为在我使用的HotSpot VM上,Object.class.getClassLoader()返回null,而Thread.currentThread().getContextClassLoader()可以通过委派加载java.lang.Object,但它本身不包括类路径。所以像this one这样的解决方案对我无效。此外,保证的系统属性列表不包括具有此类类路径信息(例如sun.boot.class.path)的属性。
如果我不必假设根本不存在rt.jar,而是扫描系统类加载器使用的资源列表,那就太好了。这种方法在供应商特定的JRE实现方面更加安全。

请注意,您所引用的JLS段落指出这些包是“可观察的”,但这并不意味着它们被隐式导入。第7.3段指出,总是有一个隐式的import java.lang.*; - Jesper
@ Jesper 谢谢,你说得对。这也是我凭经验观察到的。我已相应地更新了问题。 - barfuin
你没有忘记包内类吗? - Joop Eggen
@JoopEggen 谢谢。据我所知,java.lang是一个被禁止的包名。我在问题中澄清了与java.lang之外的包级局部类相关的问题。 - barfuin
2个回答

1

已编译的类似乎包含可读的java/lang文本。因此,我编写了一小段代码来查看是否可以提取这些导入项。这是一种不可靠的方法,但假设您可以提取/列出jar文件中的所有类,则可以将其视为起点。

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;

public class Q21102294 {

public static final String EXTERNAL_JAR = "resources/appboot-1.1.1.jar";
public static final String SAMPLE_CLASS_NAME = "com/descartes/appboot/AppBoot.class";

public static HashSet<String> usedLangClasses = new HashSet<String>();

public static void main(String[] args) {

    try {
        Path f = Paths.get(EXTERNAL_JAR);
        if (!Files.exists(f)) {
            throw new RuntimeException("Could not find file " + f);
        }
        URLClassLoader loader = new URLClassLoader(new URL[] { f.toUri().toURL() }, null);
        findLangClasses(loader, SAMPLE_CLASS_NAME);

        ArrayList<String> sortedClasses = new ArrayList<String>();
        sortedClasses.addAll(usedLangClasses);
        Collections.sort(sortedClasses);
        System.out.println("Loaded classes: ");
        for (String s : sortedClasses) {
            System.out.println(s);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }

}

public static void findLangClasses(URLClassLoader loader, String classResource) throws Exception {

    URL curl = loader.getResource(classResource);
    if (curl != null) {
        System.out.println("Got class as resource.");
    } else {
        throw new RuntimeException("Can't open resource.");
    }
    ByteArrayOutputStream bout = new ByteArrayOutputStream();
    InputStream in = curl.openStream();
    try { 
        byte[] buf = new byte[8192];
        int l = 0;
        while ((l = in.read(buf)) > -1) {
            bout.write(buf, 0, l);
        }
    } finally {
        in.close();
    }
    String ctext = new String(bout.toByteArray(), StandardCharsets.UTF_8);
    int offSet = -1;
    while ((offSet = ctext.indexOf("java/lang/", offSet)) > -1) {
        int beginIndex = offSet;
        offSet += "java/lang/".length();
        char cnext = ctext.charAt(offSet);
        while (cnext != ';' && (cnext == '/' || Character.isAlphabetic(cnext))) {
            offSet += 1;
            cnext = ctext.charAt(offSet);
        }
        String langClass = ctext.substring(beginIndex, offSet);
        //System.out.println("adding class " + langClass);
        usedLangClasses.add(langClass);
    }
}

}

给出以下输出:
Got class as resource.
Loaded classes: 
java/lang/Class
java/lang/ClassLoader
java/lang/Exception
java/lang/Object
java/lang/RuntimeException
java/lang/String
java/lang/StringBuilder
java/lang/System
java/lang/Thread
java/lang/Throwable
java/lang/reflect/Method

使用的已编译类的源代码可在此处获取。


很好的跳出思维定势!但是它并没有返回完整的 java.lang 类列表(只有被引用的类)。此外,它需要访问已编译的类文件(我目前没有,但没关系)。还是非常感谢你提供这种有趣的新方法! - barfuin
啊哈,我现在更好地理解这个问题了。希望有人能有另一个创意火花。 - vanOekel

0

好的,我误读了问题。查看JLS,我看到的是:

“每个编译单元都隐式导入预定义包java.lang中声明的每个公共类型名称,就好像在每个编译单元的任何包语句之后立即出现import java.lang.*;声明一样。因此,所有这些类型的名称都可以作为简单名称在每个编译单元中使用。”

(http://docs.oracle.com/javase/specs/jls/se7/html/jls-7.html)

如果你想知道其中包括哪些类型,这将根据Java的版本而有所不同...


是的!这个描述问题的方式比我的更好 :-) - barfuin

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