Como leio todas as classes de um pacote Java no classpath?

94

Preciso ler as classes contidas em um pacote Java. Essas classes estão no caminho de classe. Preciso fazer essa tarefa diretamente de um programa Java. Você conhece uma maneira simples de fazer?

List<Class> classes = readClassesFrom("my.package")
Jørgen R
fonte
7
Simplificando, não, você não pode fazer isso facilmente. Existem alguns truques extremamente longos que funcionam em algumas situações, mas eu sugiro fortemente um design diferente.
skaffman
A solução pode ser encontrada no projeto Weld .
Ondra Žižka
Consulte este link para obter a resposta: stackoverflow.com/questions/176527/…
Karthik E

Respostas:

48

Se você tiver Spring em seu classpath, o seguinte fará isso.

Encontre todas as classes em um pacote que são anotadas com 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;
}
Shalom938
fonte
Obrigado pelo snippet de código. :) Se você quiser evitar a duplicação de classes em uma lista de candidatos, altere o tipo de coleção de Lista para Conjunto. E use hasEnclosingClass () e getEnclosingClassName () de classMetaData. Use o método getEnclosingClass sobre uma classe subjacente
traeper
29

Você pode usar o Projeto Reflexões descrito aqui

É bastante completo e fácil de usar.

Breve descrição do site acima:

O Reflections varre seu caminho de classe, indexa os metadados, permite que você consulte em tempo de execução e pode salvar e coletar essas informações para muitos módulos em seu projeto.

Exemplo:

Reflections reflections = new Reflections(
    new ConfigurationBuilder()
        .setUrls(ClasspathHelper.forJavaClassPath())
);
Set<Class<?>> types = reflections.getTypesAnnotatedWith(Scannable.class);
Renato
fonte
É trabalho no Equinox? E em caso afirmativo, pode fazê-lo sem ativar plug-ins?
Tag de
funciona para o equinócio. em versões anteriores do Reflections, adicione bundle jar vfsType. veja aqui
zapp
28

Eu uso este, ele funciona com arquivos ou arquivos 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;
}
Edoardo Panfili
fonte
1
isso funciona se você retirar a referência a File.Separator e apenas usar '/'
Will Glass
1
Mais uma mudança é necessária para que funcione com arquivos jar quando o caminho contiver espaços. Você deve decodificar o caminho do arquivo jar. Alteração: jarFileName = packageURL.getFile (); para: jarFileName = URLDecoder.decode (packageURL.getFile ());
Will Glass em
recebo apenas o nome do pacote no jar ... não obtendo o nome da classe
AutoMEta
O novo arquivo (uri) corrigirá seu problema com espaços.
Trejkaz
entryName.lastIndexOf ('.') seria -1
marstone
11

Spring implementou uma excelente função de pesquisa de caminho de classe no PathMatchingResourcePatternResolver. Se você usar o classpath*prefixo:, poderá encontrar todos os recursos, incluindo classes em uma determinada hierarquia, e até mesmo filtrá-los, se desejar. Então você pode usar os filhos de AbstractTypeHierarchyTraversingFilter, AnnotationTypeFiltere AssignableTypeFilterpara filtrar esses recursos quer por anotações nível de classe ou em interfaces que implementar.

John Ellinwood
fonte
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[]{});
}

Esta solução foi testada no ambiente EJB .

Paul Kuit
fonte
6

Scannotation e Reflections usam abordagem de varredura de caminho de classe:

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

Outra abordagem é usar a API de processamento de anotação Java Pluggable para escrever um processador de anotação que irá coletar todas as classes anotadas em tempo de compilação e construir o arquivo de índice para uso em tempo de execução. Este mecanismo é implementado na biblioteca ClassIndex :

Iterable<Class> classes = ClassIndex.getPackageClasses("my.package");
Sławek
fonte
3

Essa funcionalidade ainda está ausente da API de reflexão Java, tanto quanto eu sei. Você pode obter um objeto de pacote apenas fazendo isto:

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

Mas, como você provavelmente percebeu, isso não permitirá que você liste as classes desse pacote. A partir de agora, você deve adotar uma abordagem mais orientada para o sistema de arquivos.

Eu encontrei alguns exemplos de implementação neste post

Não estou 100% certo de que esses métodos funcionarão quando suas aulas estiverem enterradas em arquivos JAR, mas espero que um deles faça isso para você.

Eu concordo com @skaffman ... se você tiver outra maneira de fazer isso, eu recomendo fazer isso.

Brent escreve código
fonte
4
Não é suspeito, simplesmente não funciona assim. As classes não "pertencem" a pacotes, elas têm referências a eles. A associação não aponta na outra direção.
skaffman
1
@skaffman Ponto muito interessante. Nunca pensei nisso dessa forma. Então, enquanto estamos vagando por essa trilha de pensamento, por que a associação não é bidirecional (isso é mais para minha própria curiosidade agora)?
Brent escreve o código de
3

O mecanismo mais robusto para listar todas as classes em um determinado pacote é atualmente ClassGraph , porque ele lida com a maior variedade possível de mecanismos de especificação de caminho de classe , incluindo o novo sistema de módulo JPMS. (Eu sou o autor.)

List<String> classNames;
try (ScanResult scanResult = new ClassGraph().whitelistPackages("my.package")
        .enableClassInfo().scan()) {
    classNames = scanResult.getAllClasses().getNames();
}
Luke Hutchison
fonte
Eu só me deparei com isso alguns anos depois. Essa é uma ótima ferramenta! Funciona muito mais rápido do que a reflexão e gosto que não invoque inicializadores estáticos. Exatamente o que eu precisava para resolver um problema que tínhamos.
akagixxer
2

eXtcos parece promissor. Imagine que você deseja encontrar todas as classes que:

  1. Estenda da classe "Componente" e armazene-os
  2. São anotados com "MeuComponente" e
  3. Estão no pacote “comum”.

Com eXtcos, isso é tão simples quanto

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));
    }
});
noelob
fonte
2
  1. Bill Burke escreveu um (bom artigo sobre digitalização de classe] e então escreveu Scannotation .

  2. O Hibernate já está escrito:

    • org.hibernate.ejb.packaging.Scanner
    • org.hibernate.ejb.packaging.NativeScanner
  3. O CDI pode resolver isso, mas não sei - não investigou totalmente ainda

.

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

Também para anotações:

abstract class MyAnnotationQualifier
extends AnnotationLiteral<Entity> implements Entity {}
Ondra Žižka
fonte
O link para o artigo está morto.
user2418306
1

Acontece que eu o implementei e funciona na maioria dos casos. Como é longo, coloco em um arquivo aqui .

A ideia é encontrar a localização do arquivo de origem da classe que está disponível na maioria dos casos (uma exceção conhecida são os arquivos da classe JVM - até onde testei). Se o código estiver em um diretório, verifique todos os arquivos e apenas os arquivos de classe local. Se o código estiver em um arquivo JAR, verifique todas as entradas.

Este método só pode ser usado quando:

  1. Você tem uma classe que está no mesmo pacote que deseja descobrir. Essa classe é chamada de SeedClass. Por exemplo, se você deseja listar todas as classes em 'java.io', a classe inicial pode ser java.io.File.

  2. Suas aulas estão em um diretório ou em um arquivo JAR que contém informações do arquivo de origem (não o arquivo de código-fonte, mas apenas o arquivo de origem). Pelo que tentei, funciona quase 100%, exceto a classe JVM (essas classes vêm com a JVM).

  3. Seu programa deve ter permissão para acessar o ProtectionDomain dessas classes. Se o seu programa for carregado localmente, não haverá problema.

Eu testei o programa apenas para o meu uso normal, então ele ainda pode ter problemas.

Eu espero que isso ajude.

NawaMan
fonte
1
parece ser uma coisa muito interessante! vou tentar usá-lo. Se eu achar útil para mim, posso usar seu código em um projeto de código aberto?
1
Estou me preparando para abrir o código também. Então vá em frente: D
NawaMan
@NawaMan: Você finalmente abriu o código? Em caso afirmativo: onde podemos encontrar a última versão? Obrigado!
Joanis
1

Aqui está outra opção, pequena modificação em outra resposta acima / abaixo:

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

Na época em que os miniaplicativos eram comuns, era possível ter uma URL no caminho de classe. Quando o carregador de classe exigisse uma classe, ele pesquisaria todos os locais no caminho de classe, incluindo recursos http. Como você pode ter coisas como URLs e diretórios no caminho de classe, não há uma maneira fácil de obter uma lista definitiva das classes.

No entanto, você pode chegar bem perto. Algumas das bibliotecas do Spring estão fazendo isso agora. Você pode obter todos os jar's no classpath e abri-los como arquivos. Você pode então pegar essa lista de arquivos e criar uma estrutura de dados contendo suas classes.

brianegge
fonte
0

usar especialista em dependência:

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

então use este código:

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

Brent - a razão pela qual a associação é uma forma tem a ver com o fato de que qualquer classe em qualquer componente de seu CLASSPATH pode se declarar em qualquer pacote (exceto para java / javax). Assim, simplesmente não há mapeamento de TODAS as classes em um determinado "pacote" porque ninguém sabe nem pode saber. Você pode atualizar um arquivo jar amanhã e remover ou adicionar classes. É como tentar obter uma lista de todas as pessoas chamadas John / Jon / Johan em todos os países do mundo - nenhum de nós é onisciente, portanto, nenhum de nós jamais terá a resposta correta.

idarwin
fonte
Boa resposta filosófica, mas como funciona então a varredura de bean CDI? Ou como o Hibernate verifica @Entities?
Ondra Žižka