Geschiedenis. Voor Java 9 kon reflectie de toepassingsmodifiers willekeurig omzeilen via setAccessible(true), waarmee encapsulation naar believen werd doorbroken. De introductie van het Java Platform Module System (JPMS) heeft sterke encapsulation standaard ingesteld, waarbij modules expliciet toestemming moeten verlenen voor diepe reflectieve toegang tot hun interne pakketten.
Probleem. Wanneer code in de ene module probeert een niet-publiek veld in het pakket van een andere module te bereiken via MethodHandles of kernreflectie, voert de JVM een rigoureuze toegankelijkheidscontrole uit. Deze verificatie zorgt ervoor dat het doelpakket expliciet is geopend voor de module van de oproeper. Zonder deze toestemming gooit de JVM een InaccessibleObjectException (of IllegalAccessException voor traditionele reflectie), ongeacht of er een SecurityManager is geïnstalleerd of het veld wordt benaderd via VarHandle.
Oplossing. De module moet opens package.name [to specific.module]; verklaren in zijn module-info.java, of de toepassing moet worden gestart met de vlag --add-opens source.module/package.name=target.module. Deze directive past dynamisch de interne toegankelijkheidsgrafiek van de module aan, waardoor de doeltarget-module wordt toegevoegd aan de set van modules die bevoegd zijn om diepe reflectie uit te voeren op de private leden van dat pakket.
// Module: app.core (module-info.java) module app.core { // Pakket com.app.internal is niet geopend exports com.app.api; } // Module: framework.inject public class Injector { public void inject(Object target) throws Throwable { MethodHandles.Lookup lookup = MethodHandles.privateLookupIn( target.getClass(), MethodHandles.lookup() ); // Gooi InaccessibleObjectException zonder --add-opens VarHandle handle = lookup.findVarHandle( target.getClass(), "secretField", String.class ); handle.set(target, "injected"); } }
Een ontwikkelingsteam migreerde hun monolithische Spring-gebaseerde toepassing naar het Java Module System, waarbij de codebasis werd verdeeld in de core business logic module (app.core) en een aparte afhankelijkheidsinjectiemechanisme module (framework.inject). Onmiddellijk na de uitrol crashte de toepassing tijdens de bean-initialisatie met een InaccessibleObjectException toen het framework probeerde configuratiewaarden in te voegen in private velden die zich binnen het interne pakket com.app.internal van app.core bevonden.
Drie potentiële architecturale oplossingen werden geëvalueerd. De eerste benadering hield in dat alle injecteerbare klassen naar geëxporteerde pakketten binnen app.core werden verplaatst. Hoewel dit de onmiddellijke toegangsovertreding zou oplossen, zou het de principes van encapsulation fundamenteel schenden door interne implementatiedetails aan alle andere modules bloot te stellen, wat de onderhoudslast zou verhogen en het aanvalsoppervlak zou uitbreiden voor toekomstige beveiligingsaudits. De tweede oplossing stelde voor om het JVM-argument --add-exports te gebruiken om de interne pakketten aan de frameworkmodule bloot te stellen. Echter, terwijl --add-exports compile-tijd en runtime zichtbaarheid aan publieke types verleent, staat het uitdrukkelijk geen diepe reflectie toe op private leden, waardoor het onvoldoende is voor Spring's mechanismen voor veldinjectie die vereisen dat private status wordt gewijzigd. De derde optie gebruikte het gerichte commandoregelargument --add-opens app.core/com.app.internal=framework.inject. Deze aanpak handhaafde strikte encapsulation op bronniveau voor alle andere modules, terwijl alleen het injectiekader de nodige privileges kreeg om diepe reflectie op het specifieke interne pakket uit te voeren.
Het team koos uiteindelijk de derde optie, documenteerde de vereiste --add-opens directives in hun implementatiescripts en Docker-configuraties. Deze oplossing behield de integriteit van het modulesysteem tijdens de ontwikkeling en stelde het framework in staat correct te functioneren, wat resulteerde in een succesvolle migratie met expliciet gecontroleerde toegangslimieten.
Waarom faalt setAccessible(true) op een privéveld binnen een geëxporteerd pakket wanneer het vanuit een andere module wordt benaderd, ondanks de afwezigheid van een SecurityManager?
Kandidaten verwarren vaak pakketexport met openheid. De exports directive maakt alleen publieke types en leden toegankelijk voor standaard compilatie en aanroep; het verleent niet de ReflectPermission die nodig is om toegangcontroles in de Java-taal te onderdrukken. JPMS sterke encapsulation opereert onafhankelijk van de SecurityManager, die wordt afgedwongen door de toegangcontrolemechanismen van de JVM. Om setAccessible(true) op niet-publieke leden mogelijk te maken, moet het pakket expliciet als open worden gedeclareerd, of de hele module moet worden verklaard als een open module.
Hoe beïnvloedt het MethodHandles.Lookup-vastlegmechanisme de toegankelijkheid tussen modules, en waarom kan de aanroep van MethodHandles.lookup().in(targetClass) de capaciteiten van de lookup verzwakken?
Een Lookup-object encapsuleert de toegangsvrijheden van de module en het pakketcontext van de maker ervan. Wanneer Lookup.in(targetClass) wordt aangeroepen, evalueert de JVM de privileges van de lookup opnieuw op basis van de module van de doelklasse. Als de doelklasse zich in een andere module bevindt die zijn pakket niet heeft geopend voor de module van de lookup, wordt de lookup "verzwakt" naar de PUBLIC-modus, waarbij de PRIVATE en MODULE toegangsrechten worden ontnomen. Om volledige toegang te behouden over modules, moet de doelmodule expliciet het pakket openen voor de module van de lookup, of de code moet privateLookupIn gebruiken, wat vereist dat de doelklasse zich binnen dezelfde module bevindt of toegankelijk is via de modulegrafiek.
Wat is het fundamentele onderscheid tussen --add-exports en --add-opens op het niveau van de JVM, en waarom veroorzaakt de eerste IllegalAccessException tijdens afhankelijkheidsinjectie, zelfs wanneer de compilatie succesvol is?
De vlag --add-exports voegt een pakket toe aan de geëxporteerde lijst van de module, waardoor de doelmodule toegang krijgt tot publieke types zowel compile-tijd als runtime. Deze directive verandert echter de "open" set van de module niet, die de diepe reflectie controleert. De Java Language Specification scheidt strikt leesbaarheid (exports) van reflectie (opens). Afhankelijkheidsinjectiekaders vereisen het laatste om private velden via Reflectie of VarHandle te manipuleren. Gevolgelijk, terwijl --add-exports de compiler tevredenstelt en de aanroep van methoden toestaat, zullen pogingen om private status op runtime te wijzigen nog steeds mislukken. Alleen --add-opens voegt het pakket toe aan de set van pakketten die toegankelijk zijn voor diepe reflectie, waardoor het framework in staat wordt gesteld om private veldwaarden te veranderen.