如何从类路径中的Java包中读取所有类?


94

我需要阅读Java包中包含的类。这些类在类路径中。我需要直接从Java程序执行此任务。您知道简单的方法吗?

List<Class> classes = readClassesFrom("my.package")

7
简而言之,不,您不能轻易做到这一点。在某些情况下,有些花招非常冗长,但我强烈建议您使用其他设计。
skaffman


解决方案可以在Weld项目中找到。
Ondra参观Žižka


Answers:


48

如果您的类路径中包含Spring,则可以执行以下操作。

在包中找到所有用XmlRootElement注释的类:

private List<Class> findMyTypes(String basePackage) throws IOException, ClassNotFoundException
{
    ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
    MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourcePatternResolver);

    List<Class> candidates = new ArrayList<Class>();
    String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                               resolveBasePackage(basePackage) + "/" + "**/*.class";
    Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);
    for (Resource resource : resources) {
        if (resource.isReadable()) {
            MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(resource);
            if (isCandidate(metadataReader)) {
                candidates.add(Class.forName(metadataReader.getClassMetadata().getClassName()));
            }
        }
    }
    return candidates;
}

private String resolveBasePackage(String basePackage) {
    return ClassUtils.convertClassNameToResourcePath(SystemPropertyUtils.resolvePlaceholders(basePackage));
}

private boolean isCandidate(MetadataReader metadataReader) throws ClassNotFoundException
{
    try {
        Class c = Class.forName(metadataReader.getClassMetadata().getClassName());
        if (c.getAnnotation(XmlRootElement.class) != null) {
            return true;
        }
    }
    catch(Throwable e){
    }
    return false;
}

感谢您的代码段。:)如果要避免候选列表中的类重复,请将集合类型从列表更改为集合。并使用classMetaData的hasEnclosingClass()和getEnclosingClassName()。 使用有关底层类的getEnclosingClass方法
traeper

29

您可以使用此处描述的思考项目

它非常完整且易于使用。

上述网站的简要说明:

Reflections扫描您的类路径,为元数据建立索引,使您可以在运行时对其进行查询,并可以保存和收集项目中许多模块的信息。

例:

Reflections reflections = new Reflections(
    new ConfigurationBuilder()
        .setUrls(ClasspathHelper.forJavaClassPath())
);
Set<Class<?>> types = reflections.getTypesAnnotatedWith(Scannable.class);

在Equinox中可以工作吗?如果可以,是否可以在不激活插件的情况下做到这一点?
标记

适用于春分。在旧版本的Reflections上,添加捆绑罐vfsType。看到这里
zapp

28

我用这个,它可以处理文件或jar档案

public static ArrayList<String>getClassNamesFromPackage(String packageName) throws IOException{
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    URL packageURL;
    ArrayList<String> names = new ArrayList<String>();;

    packageName = packageName.replace(".", "/");
    packageURL = classLoader.getResource(packageName);

    if(packageURL.getProtocol().equals("jar")){
        String jarFileName;
        JarFile jf ;
        Enumeration<JarEntry> jarEntries;
        String entryName;

        // build jar file name, then loop through zipped entries
        jarFileName = URLDecoder.decode(packageURL.getFile(), "UTF-8");
        jarFileName = jarFileName.substring(5,jarFileName.indexOf("!"));
        System.out.println(">"+jarFileName);
        jf = new JarFile(jarFileName);
        jarEntries = jf.entries();
        while(jarEntries.hasMoreElements()){
            entryName = jarEntries.nextElement().getName();
            if(entryName.startsWith(packageName) && entryName.length()>packageName.length()+5){
                entryName = entryName.substring(packageName.length(),entryName.lastIndexOf('.'));
                names.add(entryName);
            }
        }

    // loop through files in classpath
    }else{
    URI uri = new URI(packageURL.toString());
    File folder = new File(uri.getPath());
        // won't work with path which contains blank (%20)
        // File folder = new File(packageURL.getFile()); 
        File[] contenuti = folder.listFiles();
        String entryName;
        for(File actual: contenuti){
            entryName = actual.getName();
            entryName = entryName.substring(0, entryName.lastIndexOf('.'));
            names.add(entryName);
        }
    }
    return names;
}

1
如果您删除对File.Separator的引用并仅使用'/',则此方法有效
Will Glass

1
当路径包含空格时,还需要进行另一项更改才能使用jar文件。您必须解码jar文件路径。更改:jarFileName = packageURL.getFile(); 到:jarFileName = URLDecoder.decode(packageURL.getFile());
Will Glass

我只在jar中得到包名。.未得到类名
AutoMEta 2013年

新File(uri)将解决您的空格问题。
2013年

entryName.lastIndexOf('。')将为-1
marstone 2014年


6

Java 1.6.0_24:

public static File[] getPackageContent(String packageName) throws IOException{
    ArrayList<File> list = new ArrayList<File>();
    Enumeration<URL> urls = Thread.currentThread().getContextClassLoader()
                            .getResources(packageName);
    while (urls.hasMoreElements()) {
        URL url = urls.nextElement();
        File dir = new File(url.getFile());
        for (File f : dir.listFiles()) {
            list.add(f);
        }
    }
    return list.toArray(new File[]{});
}

该解决方案在EJB环境中进行了测试。


6

ScannotationReflections使用类路径扫描方法:

Reflections reflections = new Reflections("my.package");
Set<Class<? extends Object>> classes = reflections.getSubTypesOf(Object.class);

另一种方法是使用Java Pluggable Annotation Processing API编写注释处理器,该处理器将在编译时收集所有带注释的类并构建索引文件以供运行时使用。该机制在ClassIndex库中实现:

Iterable<Class> classes = ClassIndex.getPackageClasses("my.package");

3

据我所知,Java反射API仍可疑地缺少该功能。您可以通过执行以下操作获取包对象:

Package packageObj = Package.getPackage("my.package");

但是,您可能已经注意到,这不会让您列出该软件包中的类。到目前为止,您必须采取一种更加面向文件系统的方法。

我在这篇文章中 找到了一些示例实现

我不是100%确信当您的类埋在JAR文件中时,这些方法是否会起作用,但是我希望其中一种能为您做到。

我同意@skaffman ...如果您还有另一种解决方法,建议您改用该方法。


4
它不是可疑的,只是不能那样工作。类不“属于”包,它们具有对它们的引用。关联没有指向另一个方向。
skaffman

1
@skaffman非常有趣的一点。从来没有那样想过。因此,只要我们徘徊在那条思路上,为什么关联不是双向的(这更多是出于我自己的好奇心)?
布伦特写代码

3

当前列出给定包中所有类的最健壮的机制是ClassGraph,因为它可以处理最广泛的类路径规范机制,包括新的JPMS模块系统。(我是作者。)

List<String> classNames;
try (ScanResult scanResult = new ClassGraph().whitelistPackages("my.package")
        .enableClassInfo().scan()) {
    classNames = scanResult.getAllClasses().getNames();
}

几年后我才碰到这个。这是一个很棒的工具!它的工作速度比反射快得多,我喜欢它不调用静态初始化程序。这正是我解决问题所需要的。
akagixxer

2

eXtcos看起来很有希望。假设您要查找所有符合以下条件的类:

  1. 从“组件”类扩展,并存储它们
  2. 带有“ MyComponent”的注释,并且
  3. 在“普通”包装中。

使用eXtcos,这就像

ClasspathScanner scanner = new ClasspathScanner();
final Set<Class> classStore = new ArraySet<Class>();

Set<Class> classes = scanner.getClasses(new ClassQuery() {
    protected void query() {
        select().
        from(“common”).
        andStore(thoseExtending(Component.class).into(classStore)).
        returning(allAnnotatedWith(MyComponent.class));
    }
});

2
  1. Bill Burke写了一篇(有关类扫描的不错的文章),然后他写了Scannotation

  2. Hibernate已经编写了以下代码:

    • org.hibernate.ejb.packaging.Scanner
    • org.hibernate.ejb.packaging.NativeScanner
  3. CDI可以解决这个问题,但不知道-尚未进行全面调查

@Inject Instance< MyClass> x;
...
x.iterator() 

也用于注释:

abstract class MyAnnotationQualifier
extends AnnotationLiteral<Entity> implements Entity {}

链接到文章已失效。
user2418306'9

1

我恰好实现了它,并且在大多数情况下都可以使用。由于很长,我把它放在这里文件中

这个想法是找到在大多数情况下都可用的类源文件的位置(据我所测试,JVM类文件是一个已知的例外)。如果代码在目录中,请扫描所有文件,仅扫描类文件。如果代码在JAR文件中,请扫描所有条目。

此方法只能在以下情况下使用:

  1. 您有一个与要发现的包相同的类,该类称为SeedClass。例如,如果要列出“ java.io”中的所有类,则种子类可能是java.io.File

  2. 您的类位于目录中或JAR文件中,它具有源文件信息(不是源代码文件,而只是源文件)。据我所试,除JVM类(那些类随JVM一起提供)外,它几乎可以100%工作。

  3. 您的程序必须具有访问这些类的ProtectionDomain的权限。如果您的程序是本地加载的,那应该没有问题。

我仅对该程序进行了常规使用测试,因此它可能仍然有问题。

我希望这有帮助。


1
它似乎是一个非常有趣的东西!我会尝试使用它。如果发现对我有帮助,我可以在开源项目中使用您的代码吗?

1
我也准备开源。因此:D
NawaMan,

@NawaMan:终于开源了吗?如果是这样:我们在哪里可以找到最新版本?谢谢!
乔尼斯(Joanis)2012年

1

这是另一种选择,对上方/下方的另一个答案进行了轻微修改:

Reflections reflections = new Reflections("com.example.project.package", 
    new SubTypesScanner(false));
Set<Class<? extends Object>> allClasses = 
    reflections.getSubTypesOf(Object.class);

0

当小程序很常见时,可能在类路径上有一个URL。当类加载器需要一个类时,它将搜索类路径上的所有位置,包括http资源。因为您可以在类路径中包含URL和目录之类的内容,所以没有简单的方法来获得这些类的确定列表。

但是,您可以很接近。一些Spring库现在正在这样做。您可以将所有jar放在类路径中,然后像文件一样打开它们。然后,您可以获取此文件列表,并创建一个包含类的数据结构。


0

使用依赖Maven:

groupId: net.sf.extcos
artifactId: extcos
version: 0.4b

然后使用此代码:

ComponentScanner scanner = new ComponentScanner();
        Set classes = scanner.getClasses(new ComponentQuery() {
            @Override
            protected void query() {
                select().from("com.leyton").returning(allExtending(DynamicForm.class));
            }
        });

-2

Brent-关联是一种方式的原因与CLASSPATH的任何组件上的任何类都可以在任何程序包中声明自己有关(Java / javax除外)。因此,给定的“包”中没有所有类的映射,因为没人知道也不知道。您明天可以更新jar文件并删除或添加类。这就像试图获取世界上所有国家/地区的所有名叫John / Jon / Johan的人的名单一样-我们每个人都不是无所不知的,因此我们每个人都找不到正确的答案。


好的哲学答案,但是CDI bean扫描如何工作?还是Hibernate如何扫描@Entities?
Ondra参观Žižka
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.