Guia da Biblioteca Classgraph

Guia da Biblioteca Classgraph

1. Visão geral

Neste breve tutorial, vamos falar sobre a bibliotecaClassgraph - o que ela ajuda e como podemos usá-la.

Classgraph nos ajuda a encontrar recursos de destino no classpath Java, cria metadados sobre os recursos encontrados e fornece APIs convenientes para trabalhar com os metadados.

Esse caso de uso é muito popular em aplicativos baseados em Spring, onde os componentes marcados com anotações de estereótipo são registrados automaticamente no contexto do aplicativo. No entanto, também podemos explorar essa abordagem para tarefas personalizadas. Por exemplo, podemos querer encontrar todas as classes com uma anotação específica ou todos os arquivos de recursos com um determinado nome.

O legal é queClassgraph is fast, as it works on the byte-code level, o que significa que as classes inspecionadas não são carregadas para a JVM e não usa reflexão para processamento.

2. Dependências do Maven

Primeiro, vamos adicionar a bibliotecaclassgraph ao nossopom.xml:


    io.github.classgraph
    classgraph
    4.8.28

Nas próximas seções, veremos vários exemplos práticos com a API da biblioteca.

3. Uso básico

Existem três etapas básicas para usar a biblioteca:

  1. Configurar opções de verificação - por exemplo, pacotes de destino

  2. Realize a digitalização

  3. Trabalhar com os resultados da verificação

Vamos criar o seguinte domínio para nossa configuração de exemplo:

@Target({TYPE, METHOD, FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {

    String value() default "";
}
@TestAnnotation
public class ClassWithAnnotation {
}

Agora vamos ver as 3 etapas acima em um exemplo de busca de classes com@TestAnnotation:

try (ScanResult result = new ClassGraph().enableClassInfo().enableAnnotationInfo()
  .whitelistPackages(getClass().getPackage().getName()).scan()) {

    ClassInfoList classInfos = result.getClassesWithAnnotation(TestAnnotation.class.getName());

    assertThat(classInfos).extracting(ClassInfo::getName).contains(ClassWithAnnotation.class.getName());
}

Vamos analisar o exemplo acima:

  • começamos porsetting up the scan options (configuramos o scanner para analisar apenas informações de classe e anotação, além de instruí-lo a analisar apenas arquivos do pacote de destino)

  • nósperformed the scan usando o métodoClassGraph.scan()

  • nósused the ScanResult para encontrar classes anotadas chamando o métodogetClassWithAnnotation()

Como também veremos nos próximos exemplos, o objetoScanResult pode conter muitas informações sobre as APIs que queremos inspecionar, como oClassInfoList.

4. Filtrando por anotação de método

Vamos expandir nosso exemplo para anotações de método:

public class MethodWithAnnotation {

    @TestAnnotation
    public void service() {
    }
}

Podemos encontrar todas as classes que possuem métodos marcados pela anotação de destino usando um método semelhante -getClassesWithMethodAnnotations():

try (ScanResult result = new ClassGraph().enableAllInfo()
  .whitelistPackages(getClass().getPackage().getName()).scan()) {

    ClassInfoList classInfos = result.getClassesWithMethodAnnotation(TestAnnotation.class.getName());

    assertThat(classInfos).extracting(ClassInfo::getName).contains(MethodWithAnnotation.class.getName());
}

O método retorna um objetoClassInfoList contendo informações sobre as classes que correspondem à verificação.

5. Filtrando por parâmetro de anotação

Vejamos também como podemos encontrar todas as classes com métodos marcados pela anotação de destino e com um valor de parâmetro de anotação de destino.

Primeiro, vamos definir classes contendo métodos com@TestAnnotation, com 2 valores de parâmetro diferentes:

public class MethodWithAnnotationParameterDao {

    @TestAnnotation("dao")
    public void service() {
    }
}
public class MethodWithAnnotationParameterWeb {

    @TestAnnotation("web")
    public void service() {
    }
}

Agora, vamos iterar o resultado deClassInfoList e verificar as anotações de cada método:

try (ScanResult result = new ClassGraph().enableAllInfo()
  .whitelistPackages(getClass().getPackage().getName()).scan()) {

    ClassInfoList classInfos = result.getClassesWithMethodAnnotation(TestAnnotation.class.getName());
    ClassInfoList webClassInfos = classInfos.filter(classInfo -> {
        return classInfo.getMethodInfo().stream().anyMatch(methodInfo -> {
            AnnotationInfo annotationInfo = methodInfo.getAnnotationInfo(TestAnnotation.class.getName());
            if (annotationInfo == null) {
                return false;
            }
            return "web".equals(annotationInfo.getParameterValues().getValue("value"));
        });
    });

    assertThat(webClassInfos).extracting(ClassInfo::getName)
      .contains(MethodWithAnnotationParameterWeb.class.getName());
}

Aqui, usamos as classes de metadadosAnnotationInfoeMethodInfo para encontrar metadados sobre os métodos e anotações que queremos verificar.

6. Filtrando por anotação de campo

Também podemos usar o métodogetClassesWithFieldAnnotation() para filtrar um resultadoClassInfoList com base nas anotações de campo:

public class FieldWithAnnotation {

    @TestAnnotation
    private String s;
}
try (ScanResult result = new ClassGraph().enableAllInfo()
  .whitelistPackages(getClass().getPackage().getName()).scan()) {

    ClassInfoList classInfos = result.getClassesWithFieldAnnotation(TestAnnotation.class.getName());

    assertThat(classInfos).extracting(ClassInfo::getName).contains(FieldWithAnnotation.class.getName());
}

7. Encontrar recursos

Finalmente, veremos como podemos encontrar informações sobre os recursos do classpath.

Vamos criar um arquivo de recurso no diretório raiz do classpathclassgraph - por exemplo,src/test/resources/classgraph/my.config - e fornecer a ele algum conteúdo:

my data

Agora podemos encontrar o recurso e obter seu conteúdo:

try (ScanResult result = new ClassGraph().whitelistPaths("classgraph").scan()) {
    ResourceList resources = result.getResourcesWithExtension("config");
    assertThat(resources).extracting(Resource::getPath).containsOnly("classgraph/my.config");
    assertThat(resources.get(0).getContentAsString()).isEqualTo("my data");
}

Podemos ver aqui que usamos o métodoScanResult'sgetResourcesWithExtension() para procurar nosso arquivo específico. The class has a few other useful resource-related methods, such as getAllResources(), getResourcesWithPath() and*getResourcesMatchingPattern()*.

Esses métodos retornam um objetoResourceList, que pode ser usado posteriormente para iterar e manipular objetosResource.

8. Instanciação

Quando queremos instanciar classes encontradas, é muito importante fazer isso não por meio deClass.forName,, mas usando o método de bibliotecaClassInfo.loadClass.

O motivo é que o Classgraph usa seu próprio carregador de classes para carregar classes de alguns arquivos JAR. Portanto, se usarmosClass.forName, a mesma classe pode ser carregada mais de uma vez por diferentes carregadores de classe, e isso pode levar a bugs não triviais.

9. Conclusão

Neste artigo, aprendemos como encontrar efetivamente os recursos do caminho de classe e inspecionar seu conteúdo com a biblioteca Classgraph.

Como de costume, o código-fonte completo deste artigo está disponívelover on GitHub.