Java >> Java tutorial >  >> Tag >> class

Skjulte klasser i Java 15

1. Oversigt

Java 15 introducerede en hel masse funktioner. I denne artikel vil vi diskutere en af ​​de nye funktioner kaldet Hidden Classes under JEP-371. Denne funktion introduceres som et alternativ til Unsafe API, som ikke anbefales uden for JDK.

Funktionen skjulte klasser er nyttig for alle, der arbejder med dynamisk bytekode eller JVM-sprog.

2. Hvad er en skjult klasse?

Dynamisk genererede klasser giver effektivitet og fleksibilitet til applikationer med lav latens. De er kun nødvendige i en begrænset periode. Ved at beholde dem i hele levetiden for statisk genererede klasser øges hukommelsesfodaftrykket. Eksisterende løsninger til denne situation, såsom læssere pr. klasse, er besværlige og ineffektive.

Siden Java 15 er skjulte klasser blevet en standard måde at generere dynamiske klasser på.

Skjulte klasser er klasser, der ikke kan bruges direkte af bytekoden eller andre klasser. Selvom det er nævnt som en klasse, skal det forstås som enten en skjult klasse eller grænseflade. Det kan også defineres som et medlem af adgangskontrolredet og kan aflæses uafhængigt af andre klasser.

3. Egenskaber for skjulte klasser

Lad os tage et kig på egenskaberne for disse dynamisk genererede klasser:

  • Ikke-opdagelig – en skjult klasse kan ikke opdages af JVM'en under bytekode-kobling, ej heller af programmer, der eksplicit bruger klasseindlæsere. De reflekterende metoder Class::forName , ClassLoader::findLoadedClass , og Lookup::findClass vil ikke finde dem.
  • Vi kan ikke bruge den skjulte klasse som en superklasse, felttype, returtype eller parametertype.
  • Kode i den skjulte klasse kan bruge den direkte uden at være afhængig af klasseobjektet.
  • endelig felter erklæret i skjulte klasser kan ikke ændres uanset deres tilgængelige flag.
  • Det udvider adgangskontrolredet med klasser, der ikke kan findes.
  • Den kan blive aflæst, selvom dens teoretiske definerende klasseindlæser stadig er tilgængelig.
  • Stakspor viser ikke metoderne eller navnene på skjulte klasser som standard, men justering af JVM-indstillinger kan vise dem.

4. Oprettelse af skjulte klasser

Den skjulte klasse er ikke oprettet af nogen klasseindlæser. Den har den samme definerende klasseindlæser, runtime-pakke og beskyttelsesdomæne som opslagsklassen.

Lad os først oprette et opslag objekt:

MethodHandles.Lookup lookup = MethodHandles.lookup();

Lookup::defineHiddenClass metoden skaber den skjulte klasse. Denne metode accepterer et array af bytes.

For nemheds skyld definerer vi en simpel klasse med navnet HiddenClass der har en metode til at konvertere en given streng til store bogstaver:

public class HiddenClass {
    public String convertToUpperCase(String s) {
        return s.toUpperCase();
    }
}

Lad os se stien til klassen og indlæse den i inputstrømmen. Derefter konverterer vi denne klasse til bytes ved hjælp af IOUtils.toByteArray() :

Class<?> clazz = HiddenClass.class;
String className = clazz.getName();
String classAsPath = className.replace('.', '/') + ".class";
InputStream stream = clazz.getClassLoader()
    .getResourceAsStream(classAsPath);
byte[] bytes = IOUtils.toByteArray();

Til sidst sender vi disse konstruerede bytes til Lookup::defineHiddenClass :

Class<?> hiddenClass = lookup.defineHiddenClass(IOUtils.toByteArray(stream),
  true, ClassOption.NESTMATE).lookupClass();

Den anden boolean argumentet sandt initialiserer klassen. Det tredje argument ClassOption.NESTMATE angiver, at den oprettede skjulte klasse vil blive tilføjet som en nestmate til opslagsklassen, så den har adgang til den private medlemmer af alle klasser og grænseflader i samme rede.

Antag, at vi vil binde den skjulte klasse stærkt med dens klasseindlæser, ClassOption.STRONG . Dette betyder, at den skjulte klasse kun kan fjernes, hvis dens definerende indlæser ikke er tilgængelig.

5. Brug af skjulte klasser

Skjulte klasser bruges af rammer, der genererer klasser under kørsel og bruger dem indirekte via refleksion.

I det foregående afsnit så vi på at oprette en skjult klasse. I dette afsnit vil vi se, hvordan du bruger det og opretter en instans.

Siden casting af klasserne opnået fra Lookup.defineHiddenClass er ikke muligt med noget andet klasseobjekt, vi bruger Object at gemme den skjulte klasseforekomst. Hvis vi ønsker at caste den skjulte klasse, kan vi definere en grænseflade og oprette en skjult klasse, der implementerer grænsefladen:

Object hiddenClassObject = hiddenClass.getConstructor().newInstance();

Lad os nu få metoden fra den skjulte klasse. Efter at have fået metoden, vil vi påberåbe den som enhver anden standardmetode:

Method method = hiddenClassObject.getClass()
    .getDeclaredMethod("convertToUpperCase", String.class);
Assertions.assertEquals("HELLO", method.invoke(hiddenClassObject, "Hello"));

Nu kan vi verificere nogle få egenskaber for en skjult klasse ved at påkalde nogle af dens metoder:

Metoden isHidden() vil returnere true for denne klasse:

Assertions.assertEquals(true, hiddenClass.isHidden());

Da der ikke er noget egentligt navn for en skjult klasse, vil dens kanoniske navn være null :

Assertions.assertEquals(null, hiddenClass.getCanonicalName());

Den skjulte klasse vil have den samme definerende loader som den klasse, der foretager opslag. Da opslaget sker i samme klasse, vil følgende påstand være vellykket:

Assertions.assertEquals(this.getClass()
    .getClassLoader(), hiddenClass.getClassLoader());

Hvis vi forsøger at få adgang til den skjulte klasse gennem nogen metoder, vil de kaste ClassNotFoundException . Dette er indlysende, da det skjulte klassenavn er tilstrækkeligt usædvanligt og ukvalificeret til at være synligt for andre klasser. Lad os tjekke et par påstande for at bevise, at den skjulte klasse ikke kan opdages:

Assertions.assertThrows(ClassNotFoundException.class, () -> Class.forName(hiddenClass.getName()));
Assertions.assertThrows(ClassNotFoundException.class, () -> lookup.findClass(hiddenClass.getName()));

Bemærk, at den eneste måde, andre klasser kan bruge en skjult klasse på, er via dens Klasse objekt.

6. Anonym klasse vs. skjult klasse

Vi oprettede en skjult klasse i tidligere afsnit og legede med nogle af dens egenskaber. Lad os nu uddybe forskellene mellem anonyme klasser – indre klasser uden eksplicitte navne – og skjulte klasser:

  • Anonym klasse har et dynamisk genereret navn med en $ imellem, mens en skjult klasse afledt af com.baeldung.reflection.hiddenclass.HiddenClass ville være com.baeldung.reflection.hiddenclass.HiddenClass/1234.
  • Anonym klasse instansieres ved hjælp af Unsafe::defineAnonymousClass , som er forældet, mens Lookup::defineHiddenClass instansierer en skjult klasse.
  • Skjulte klasser understøtter ikke konstant-pool-patching. Det hjælper med at definere anonyme klasser med deres konstante puljeposter allerede løst til konkrete værdier.
  • I modsætning til en skjult klasse kan en anonym klasse få adgang til beskyttet medlemmer af en værtsklasse, selvom den er i en anden pakke og ikke en underklasse.
  • En anonym klasse kan omslutte andre klasser for at få adgang til sine medlemmer, men en skjult klasse kan ikke omslutte andre klasser.

Selvom den skjulte klasse ikke er en erstatning for en anonym klasse , erstatter de nogle af anvendelserne af anonyme klasser i JDK. Fra Java 15 bruger lambda-udtryk skjulte klasser .

7. Konklusion

I denne artikel diskuterede vi en ny sprogfunktion kaldet Hidden Classes i detaljer. Som altid er koden tilgængelig på GitHub.


Java tag