Digitalizando anotações Java em tempo de execução [fechado]

253

Qual é a melhor maneira de pesquisar em todo o caminho de classe por uma classe anotada?

Estou fazendo uma biblioteca e quero permitir que os usuários anotem suas classes, portanto, quando o aplicativo Web for iniciado, preciso varrer todo o caminho de classe para obter determinadas anotações.

Você conhece uma biblioteca ou uma instalação Java para fazer isso?

Edit: Estou pensando em algo como a nova funcionalidade para Java EE 5 Web Services ou EJB's. Você anota sua classe com @WebServiceou @EJBe o sistema encontra essas classes durante o carregamento para que possam ser acessadas remotamente.

Alotor
fonte

Respostas:

210

Use org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider

API

Um provedor de componentes que varre o caminho de classe a partir de um pacote base. Em seguida, aplica-se excluir e incluir filtros nas classes resultantes para encontrar candidatos.

ClassPathScanningCandidateComponentProvider scanner =
new ClassPathScanningCandidateComponentProvider(<DO_YOU_WANT_TO_USE_DEFALT_FILTER>);

scanner.addIncludeFilter(new AnnotationTypeFilter(<TYPE_YOUR_ANNOTATION_HERE>.class));

for (BeanDefinition bd : scanner.findCandidateComponents(<TYPE_YOUR_BASE_PACKAGE_HERE>))
    System.out.println(bd.getBeanClassName());
Arthur Ronald
fonte
5
Obrigado pela informação. Você também sabe como verificar o caminho de classe em busca de classes cujos campos possuem anotações personalizadas?
Javatar 12/12/12
6
@Javatar Use a API de reflexão do Java. <YOUR_CLASS> .class.getFields () Para cada campo, chame getAnnotation (<YOUR_ANNOTATION>)
Arthur Ronald
1
NOTA: Se você estiver fazendo isso em um aplicativo Spring, o Spring ainda avaliará e atuará com base em @Conditionalanotações. Portanto, se o @Conditionalvalor de uma classe retornar false, ele não será retornado findCandidateComponents, mesmo que corresponda ao filtro do scanner. Isso me emocionou hoje - acabei usando a solução de Jonathan abaixo.
Adam Burley
1
@ArthurRonald Desculpe, Arthur. Quero dizer que o BeanDefinitionobjeto não fornece uma maneira de obter a classe diretamente. A coisa mais próxima parece ser a getBeanClassNameque retorna um nome de classe totalmente qualificado, mas o comportamento exato desse método não é claro. Além disso, não está claro em qual carregador de classes a classe foi encontrada.
Max
3
@Max Tente isto: Class<?> cl = Class.forName(beanDef.getBeanClassName()); farenda.com/spring/find-annotated-classes
James Watkins
149

E outra solução são as reflexões do Google .

Revisão rápida:

  • A solução Spring é o caminho a seguir, se você estiver usando o Spring. Caso contrário, é uma grande dependência.
  • Usar o ASM diretamente é um pouco complicado.
  • Usar o Java Assist diretamente também é desajeitado.
  • A inovação é super leve e conveniente. Ainda não há integração inteligente.
  • As reflexões do Google atraem as coleções do Google. Indexa tudo e fica super rápido.
Jonathan
fonte
43
new Reflections("my.package").getTypesAnnotatedWith(MyAnnotation.class). c'est tout.
Zapp
4
preciso especificar um nome de pacote? curinga? para todas as classes no caminho de classe?
Sunnyday
1
Cuidado que ele ainda não tem progresso em Java 9 suporte: github.com/ronmamo/reflections/issues/186
Vadzim
a biblioteca org.reflections não funciona bem no java 13 (talvez antes também). A primeira vez que é chamado, parece estar ok. instâncias e usos subsequentes falham, dizendo que os URLs de pesquisa não estão configurados.
Evvo
44

Você pode encontrar classes com qualquer anotação no ClassGraph , bem como procurar outros critérios de interesse, por exemplo, classes que implementam uma determinada interface. (Isenção de responsabilidade, sou o autor do ClassGraph.) O ClassGraph pode criar uma representação abstrata de todo o gráfico da classe (todas as classes, anotações, métodos, parâmetros do método e campos) na memória, para todas as classes no caminho de classe ou para classes em pacotes na lista de permissões, e você pode consultar esse gráfico de classe da maneira que desejar. O ClassGraph suporta mais mecanismos de especificação de caminho de classe e carregadores de classe do que qualquer outro scanner, e também funciona perfeitamente com o novo sistema de módulo JPMS; portanto, se você basear seu código no ClassGraph, seu código será portátil no máximo. Veja a API aqui.

Luke Hutchison
fonte
1
Isso precisa do Java 8 para ser executado?
David George
1
Atualizado para usar Java7, não há problema. Apenas remova as anotações e converta as funções para usar classes internas anônimas. Eu gosto do estilo de 1 arquivo. O código é bom e limpo, portanto, mesmo que não suporte algumas coisas que eu gostaria (anotação de classe + ao mesmo tempo), acho que seria muito fácil de adicionar. Ótimo trabalho! Se alguém não conseguir fazer o trabalho de modificação para a v7, provavelmente deve continuar Reflections. Além disso, se você estiver usando o goiaba / etc e quiser alterar as coleções, é fácil como torta. Grandes comentários dentro também.
Andrew Backer
2
@Alexandros obrigado, você deve conferir o ClassGraph, ele foi significativamente aprimorado em relação ao FastClasspathScanner.
Luke Hutchison
2
O @AndrewBacker ClassGraph (a nova versão do FastClasspathScanner) tem suporte total para operações booleanas, por meio de filtros ou operações definidas. Veja exemplos de código aqui: github.com/classgraph/classgraph/wiki/Code-examples
Luke Hutchison
1
@Luke Hutchison Já está usando o ClassGraph. Ajudou-me na migração para o Java 10. Biblioteca realmente útil.
Alexandros
25

Se você quer uma solução realmente leve (sem dependências, API simples, arquivo jar de 15 kb) e uma solução muito rápida , dê uma olhada annotation-detectorem https://github.com/rmuller/infomas-asl

Disclaimer: Eu sou o autor.

rmuller
fonte
20

É possível usar a API de processamento de anotação conectável do Java para gravar o processador de anotação que será executado durante o processo de compilação e coletará todas as classes anotadas e criará o arquivo de índice para uso em tempo de execução.

Essa é a maneira mais rápida possível de descobrir anotações de classe, porque você não precisa varrer seu caminho de classe em tempo de execução, o que geralmente é uma operação muito lenta. Além disso, essa abordagem funciona com qualquer carregador de classes e não apenas com URLClassLoaders geralmente suportados por scanners de tempo de execução.

O mecanismo acima já está implementado na biblioteca ClassIndex .

Para usá-lo, anote sua anotação personalizada com a meta-anotação @IndexAnnotated . Isso criará no momento da compilação um arquivo de índice: META-INF / anotações / com / test / YourCustomAnnotation listando todas as classes anotadas. Você pode acessar o índice em tempo de execução executando:

ClassIndex.getAnnotated(com.test.YourCustomAnnotation.class)
Sławek
fonte
5

É tarde demais para responder. Eu diria que é melhor usar bibliotecas como ClassPathScanningCandidateComponentProvider ou como Scannotations

Mas, mesmo depois que alguém quer tentar trabalhar com o classLoader, escrevi alguns para imprimir as anotações das classes em um pacote:

public class ElementScanner {

public void scanElements(){
    try {
    //Get the package name from configuration file
    String packageName = readConfig();

    //Load the classLoader which loads this class.
    ClassLoader classLoader = getClass().getClassLoader();

    //Change the package structure to directory structure
    String packagePath  = packageName.replace('.', '/');
    URL urls = classLoader.getResource(packagePath);

    //Get all the class files in the specified URL Path.
    File folder = new File(urls.getPath());
    File[] classes = folder.listFiles();

    int size = classes.length;
    List<Class<?>> classList = new ArrayList<Class<?>>();

    for(int i=0;i<size;i++){
        int index = classes[i].getName().indexOf(".");
        String className = classes[i].getName().substring(0, index);
        String classNamePath = packageName+"."+className;
        Class<?> repoClass;
        repoClass = Class.forName(classNamePath);
        Annotation[] annotations = repoClass.getAnnotations();
        for(int j =0;j<annotations.length;j++){
            System.out.println("Annotation in class "+repoClass.getName()+ " is "+annotations[j].annotationType().getName());
        }
        classList.add(repoClass);
    }
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
}

/**
 * Unmarshall the configuration file
 * @return
 */
public String readConfig(){
    try{
        URL url = getClass().getClassLoader().getResource("WEB-INF/config.xml");
        JAXBContext jContext = JAXBContext.newInstance(RepositoryConfig.class);
         Unmarshaller um =  jContext.createUnmarshaller();
         RepositoryConfig rc = (RepositoryConfig) um.unmarshal(new File(url.getFile()));
         return rc.getRepository().getPackageName();
        }catch(Exception e){
            e.printStackTrace();
        }
    return null;

}
}

E no arquivo de configuração, você coloca o nome do pacote e o desmarca em uma classe.

kenobiwan
fonte
3

Com o Spring, você também pode escrever o seguinte usando a classe AnnotationUtils. ou seja:

Class<?> clazz = AnnotationUtils.findAnnotationDeclaringClass(Target.class, null);

Para obter mais detalhes e todos os métodos diferentes, consulte os documentos oficiais: https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/core/annotation/AnnotationUtils.html

artesão
fonte
4
Boa ideia, mas se você colocar um nullvalor como o segundo parâmetro (que define a classe, na qual a hierarquia de herança Spring procurará uma classe que use a Anotação), você sempre receberá uma nullresposta, de acordo com a implementação.
precisa saber é o seguinte
3

Há um comentário maravilhoso do zapp que se encaixa em todas essas respostas:

new Reflections("my.package").getTypesAnnotatedWith(MyAnnotation.class)
Zon
fonte
2

A API do Classloader não possui um método "enumerar", porque o carregamento de classes é uma atividade "sob demanda" - você geralmente possui milhares de classes em seu caminho de classe, das quais apenas uma fração será necessária (o arquivo rt.jar hoje tem 48MB hoje!).

Portanto, mesmo se você pudesse enumerar todas as classes, isso consumiria muito tempo e memória.

A abordagem simples é listar as classes em questão em um arquivo de configuração (xml ou o que melhor lhe convier); se você quiser fazer isso automaticamente, restrinja-se a um diretório JAR ou a uma classe.

mfx
fonte
2

Google Reflection se você quiser descobrir interfaces também.

O Spring ClassPathScanningCandidateComponentProvidernão está descobrindo interfaces.

madhu_karnati
fonte
1

O Google Reflections parece ser muito mais rápido que o Spring. Encontrei esta solicitação de recurso que aborda essa diferença: http://www.opensaga.org/jira/browse/OS-738

Esse é um motivo para usar o Reflections, pois o tempo de inicialização do meu aplicativo é realmente importante durante o desenvolvimento. O Reflections também parece ser muito fácil de usar para o meu caso de uso (encontre todos os implementadores de uma interface).

Martin Aubele
fonte
1
Se você olhar para a questão do JIRA, há comentários de que eles se afastaram do Reflections devido a problemas de estabilidade.
Wim Deblauwe
1

Se você está procurando uma alternativa para reflexões , gostaria de recomendar o Panda Utilities - AnnotationsScanner . É um scanner livre de goiaba (goiaba tem ~ 3MB, Panda Utilities tem ~ 200kb) com base no código-fonte da biblioteca de reflexões.

Também é dedicado a pesquisas baseadas no futuro. Se você desejar varrer várias vezes as fontes incluídas ou fornecer uma API, que permita que alguém verifique o caminho de classe atual,AnnotationsScannerProcess armazena em cache todos osClassFiles , por isso é muito rápido.

Exemplo simples de AnnotationsScanneruso:

AnnotationsScanner scanner = AnnotationsScanner.createScanner()
        .includeSources(ExampleApplication.class)
        .build();

AnnotationsScannerProcess process = scanner.createWorker()
        .addDefaultProjectFilters("net.dzikoysk")
        .fetch();

Set<Class<?>> classes = process.createSelector()
        .selectTypesAnnotatedWith(AnnotationTest.class);
dzikoysk
fonte
1

A primavera tem algo chamado AnnotatedTypeScannerclasse.
Esta classe usa internamente

ClassPathScanningCandidateComponentProvider

Esta classe possui o código para a verificação real dos recursos do caminho de classe . Isso é feito usando os metadados da classe disponíveis no tempo de execução.

Pode-se simplesmente estender essa classe ou usar a mesma classe para digitalização. Abaixo está a definição do construtor.

/**
     * Creates a new {@link AnnotatedTypeScanner} for the given annotation types.
     * 
     * @param considerInterfaces whether to consider interfaces as well.
     * @param annotationTypes the annotations to scan for.
     */
    public AnnotatedTypeScanner(boolean considerInterfaces, Class<? extends Annotation>... annotationTypes) {

        this.annotationTypess = Arrays.asList(annotationTypes);
        this.considerInterfaces = considerInterfaces;
    }
swayamraina
fonte