Java >> Java tutorial >  >> Tag >> package

Find alle klasser i en Java-pakke

1. Oversigt

Nogle gange ønsker vi at få information om køretidsadfærden for vores applikation, såsom at finde alle klasser, der er tilgængelige på runtime.

I denne øvelse vil vi udforske flere eksempler på, hvordan du finder alle klasser i en Java-pakke under kørsel.

2. Klasseindlæsere

Først starter vi vores diskussion med Java-klasseindlæserne. Java-klasseindlæseren er en del af Java Runtime Environment (JRE), der dynamisk indlæser Java-klasser i Java Virtual Machine (JVM). Java-klasseindlæseren afkobler JRE fra viden om filer og filsystemer. Ikke alle klasser indlæses af en enkelt klasseindlæser .

Lad os forstå de tilgængelige klasseindlæsere i Java gennem billedrepræsentation:

Java 9 introducerede nogle større ændringer til klasseindlæserne. Med introduktionen af ​​moduler har vi mulighed for at give modulstien sammen med klassestien. Systemklasseindlæseren indlæser de klasser, der er til stede på modulstien.

Klasslæssere er dynamiske . De er ikke forpligtet til at fortælle JVM'en, hvilke klasser den kan levere under kørsel. At finde klasser i en pakke er derfor i det væsentlige en filsystemoperation snarere end en, der udføres ved at bruge Java Reflection.

Vi kan dog skrive vores egne klasseindlæsere eller undersøge klassestien for at finde klasser inde i en pakke.

3. Sådan finder du klasser i en Java-pakke

Til vores illustration, lad os oprette en pakke com.baeldung.reflection.access.packages.search .

Lad os nu definere en eksempelklasse:

public class ClassExample {
    class NestedClass {
    }
}

Lad os derefter definere en grænseflade:

public interface InterfaceExample {
}

I næste afsnit vil vi se på, hvordan man finder klasser ved hjælp af systemklasseindlæseren og nogle tredjepartsbiblioteker.

3.1. System Class Loader

Først skal vi bruge den indbyggede systemklasseindlæser. Systemklasseindlæseren indlæser alle klasser fundet i klassestien . Dette sker under den tidlige initialisering af JVM:

public class AccessingAllClassesInPackage {

    public Set<Class> findAllClassesUsingClassLoader(String packageName) {
        InputStream stream = ClassLoader.getSystemClassLoader()
          .getResourceAsStream(packageName.replaceAll("[.]", "/"));
        BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
        return reader.lines()
          .filter(line -> line.endsWith(".class"))
          .map(line -> getClass(line, packageName))
          .collect(Collectors.toSet());
    }
 
    private Class getClass(String className, String packageName) {
        try {
            return Class.forName(packageName + "."
              + className.substring(0, className.lastIndexOf('.')));
        } catch (ClassNotFoundException e) {
            // handle the exception
        }
        return null;
    }
}

I vores eksempel ovenfor indlæser vi systemklasseindlæseren ved hjælp af den statiske getSystemClassLoader()  metode.

Dernæst finder vi ressourcerne i den givne pakke. Vi læser ressourcerne som en strøm af webadresser ved hjælp af getResourceAsStream  metode. For at hente ressourcerne under en pakke, skal vi konvertere pakkenavnet til en URL-streng. Så vi er nødt til at erstatte alle prikkerne (.) med en sti-separator (“/”).

Derefter vil vi indtaste vores strøm til en BufferedReader og filtrer alle URL'er med .class udvidelse. Efter at have fået de nødvendige ressourcer, konstruerer vi klassen og samler alle resultaterne i et Set . Da Java ikke tillader lambda at kaste en undtagelse, er vi nødt til at håndtere det i getClass metode .

Lad os nu teste denne metode:

@Test
public void when_findAllClassesUsingClassLoader_thenSuccess() {
    AccessingAllClassesInPackage instance = new AccessingAllClassesInPackage();
 
    Set<Class> classes = instance.findAllClassesUsingClassLoader(
      "com.baeldung.reflection.access.packages.search");
 
    Assertions.assertEquals(3, classes.size());
}

Der er kun to Java-filer i pakken. Vi har dog tre klasser erklæret - inklusive den indlejrede klasse, NestedExample . Som et resultat resulterede vores test i tre klasser.

Bemærk, at søgepakken er forskellig fra den aktuelle arbejdspakke.

3.2. Refleksionsbibliotek

Reflections er et populært bibliotek, der scanner den aktuelle klassesti og giver os mulighed for at forespørge på den under kørsel.

Lad os starte med at tilføje refleksionerne afhængighed af vores Maven-projekt:

<dependency>
    <groupId>org.reflections</groupId>
    <artifactId>reflections</artifactId> 
    <version>0.9.12</version>
</dependency>

Lad os nu dykke ned i kodeeksemplet:

public Set<Class> findAllClassesUsingReflectionsLibrary(String packageName) {
    Reflections reflections = new Reflections(packageName, new SubTypesScanner(false));
    return reflections.getSubTypesOf(Object.class)
      .stream()
      .collect(Collectors.toSet());
}

I denne metode starter vi SubTypesScanner klasse og henter alle undertyper af Objektet klasse. Gennem denne tilgang får vi mere granularitet, når vi henter klasserne.

Lad os igen teste det:

@Test
public void when_findAllClassesUsingReflectionsLibrary_thenSuccess() {
    AccessingAllClassesInPackage instance = new AccessingAllClassesInPackage();
 
    Set<Class> classes = instance.findAllClassesUsingReflectionsLibrary(
      "com.baeldung.reflection.access.packages.search");
 
    Assertions.assertEquals(3, classes.size());
}

I lighed med vores tidligere test finder denne test de klasser, der er erklæret i den givne pakke.

Lad os nu gå videre til vores næste eksempel.

3.3. Google Guava-bibliotek

I dette afsnit vil vi se, hvordan du finder klasser ved hjælp af Google Guava-biblioteket. Google Guava tilbyder en ClassPath hjælpeklasse, der scanner kilden til klasseindlæseren og finder alle indlæsbare klasser og ressourcer.

Lad os først tilføje guavaen afhængighed af vores projekt:

<dependency>
      <groupId>com.google.guava</groupId>
      <artifactId>guava</artifactId>
      <version>31.0.1-jre</version>
</dependency>

Lad os dykke ned i koden:

public Set<Class> findAllClassesUsingGoogleGuice(String packageName) throws IOException {
    return ClassPath.from(ClassLoader.getSystemClassLoader())
      .getAllClasses()
      .stream()
      .filter(clazz -> clazz.getPackageName()
        .equalsIgnoreCase(packageName))
      .map(clazz -> clazz.load())
      .collect(Collectors.toSet());
}

I ovenstående metode leverer vi systemklasseindlæseren som input til ClassPath#from metode. Alle klasser scannet af ClassPath  filtreres baseret på pakkenavnet. De filtrerede klasser indlæses derefter (men ikke linket eller initialiseret) og samles i et Set .

Lad os nu teste denne metode:

@Test
public void when_findAllClassesUsingGoogleGuice_thenSuccess() throws IOException {
    AccessingAllClassesInPackage instance = new AccessingAllClassesInPackage();
 
    Set<Class> classes = instance.findAllClassesUsingGoogleGuice(
      "com.baeldung.reflection.access.packages.search");
 
    Assertions.assertEquals(3, classes.size());
}

Derudover tilbyder Google Guava-biblioteket getTopLevelClasses() og getTopLevelClassesRecursive() metoder.

Det er vigtigt at bemærke, at i alle ovenstående eksempler, pakke-info er inkluderet i listen over tilgængelige klasser, hvis de er til stede under pakken og er kommenteret med en eller flere annoteringer på pakkeniveau .

Det næste afsnit vil diskutere, hvordan man finder klasser i en modulær applikation.

4. Sådan finder du klasser i en modulær applikation

Java Platform Module System (JPMS) introducerede os til et nyt niveau af adgangskontrol gennem moduler . Hver pakke skal eksplicit eksporteres for at få adgang uden for modulet.

I en modulær applikation kan hvert modul være et af navngivne, unavngivne eller automatiske moduler.

For de navngivne og automatiske moduler vil den indbyggede systemklasseindlæser ikke have nogen klassesti. Systemklasseindlæseren vil søge efter klasser og ressourcer ved hjælp af applikationsmodulstien.

For et unavngivet modul vil det sætte klassestien til den aktuelle arbejdsmappe.

4.1. Inden for et modul

Alle pakker i et modul har synlighed til andre pakker i modulet. Koden inde i modulet har reflekterende adgang til alle typer og alle deres medlemmer.

4.2. Uden for et modul

Da Java håndhæver den mest restriktive adgang, er vi nødt til eksplicit at erklære pakker ved hjælp af eksport eller åben  modulerklæring for at få reflekterende adgang til klasserne inde i modulet.

For et normalt modul giver den reflekterende adgang for eksporterede pakker (men ikke åbne) kun adgang til offentlige  og beskyttet typer og alle deres medlemmer af den erklærede pakke.

Vi kan konstruere et modul, der eksporterer den pakke, der skal søges i:

module my.module {
    exports com.baeldung.reflection.access.packages.search;
}

For et normalt modul giver den reflekterende adgang for åbne pakker adgang til alle typer og deres medlemmer af den erklærede pakke:

module my.module {
    opens com.baeldung.reflection.access.packages.search;
}

Ligeledes giver et åbent modul reflekterende adgang til alle typer og deres medlemmer, som om alle pakker var blevet åbnet. Lad os nu åbne hele vores modul for reflekterende adgang:

open module my.module{
}

Endelig, efter at have sikret sig, at de korrekte modulære beskrivelser til at få adgang til pakker er tilvejebragt for modulet, kan enhver af metoderne fra det foregående afsnit bruges til at finde alle tilgængelige klasser inde i en pakke.

5. Konklusion

Afslutningsvis lærte vi om klasseindlæsere og de forskellige måder at finde alle klasser i en pakke. Vi diskuterede også adgang til pakker i en modulær applikation.

Som sædvanlig er al koden tilgængelig på GitHub.


Java tag