3

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

欢迎任何可靠的基于 Java 的解决方案。


以下是我迄今为止尝试过的一些注意事项:

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

到目前为止,我还没有设法访问系统类加载器的类路径,因为在我使用的 HotSpot VM 上,Object.class.getClassLoader()返回null,并且Thread.currentThread().getContextClassLoader()可以通过委托加载java.lang.Object,但它本身不包含类路径。所以像这样的解决方案我不起作用。此外,保证的系统属性列表不包括具有这种类路径信息的属性(例如sun.boot.class.path)。

如果我根本不必假设存在rt.jar,而是扫描系统类加载器使用的资源列表,那就太好了。对于供应商特定的 JRE 实现,这种方法会更安全。

4

2 回答 2

1

编译的类似乎包含可读的java/lang文本。所以我写了一点代码,看看是否可以提取这些导入。这是一个 hack,所以不可靠,但假设您可以提取/列出 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

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

于 2014-01-14T18:30:29.753 回答
0

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

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

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

如果您想知道其中包括哪些类型,它会因 Java 的版本而异......

于 2014-01-13T22:40:26.793 回答