Ga naar hoofdinhoud

Namespaces

Open in ChatGPT
24.22
Java API

Namespaces in webforJ bieden een mechanisme voor het opslaan en ophalen van gedeelde gegevens over verschillende scopes in een webapplicatie. Ze stellen inter-component en cross-sessie datacommunicatie mogelijk zonder afhankelijk te zijn van traditionele opslagtechnieken zoals sessie-attributen of statische velden. Deze abstractie stelt ontwikkelaars in staat om toestand op een gecontroleerde, thread-veilige manier te encapculeren en toegang te krijgen. Namespaces zijn ideaal voor het bouwen van samenwerkingshulpmiddelen voor meerdere gebruikers of simpelweg voor het behouden van consistente globale instellingen, en laten je gegevens veilig en efficiënt coördineren.

Wat is een namespace?

Een namespace is een benoemde container die sleutel-waarde paren opslaat. Deze waarden kunnen in verschillende delen van je app worden benaderd en gewijzigd, afhankelijk van het type namespace dat je gebruikt. Je kunt het beschouwen als een thread-veilige, gedistribueerde map met ingebouwde gebeurtenishandling en vergrendelmechanismen.

Wanneer namespaces gebruiken

Gebruik namespaces wanneer:

  • Je waarden moet delen over gebruikerssessies of app-componenten.
  • Je wilt reageren op waardewijzigingen via luisteraars.
  • Je fijne vergrendeling nodig hebt voor kritieke secties.
  • Je efficiënt toestand moet persistent maken en ophalen in je app.

Typen namespaces

webforJ biedt drie typen namespaces:

TypeScopeTypisch gebruik
PrivéGemeenschappelijk onder cliënten die dezelfde prefix en naam gebruiken. Geheugen wordt automatisch vrijgegeven wanneer er geen verwijzingen meer zijn.Gedeelde toestand tussen verwante gebruikerssessies.
GroepGedeeld door alle threads die zijn gestart vanuit dezelfde bovenliggende thread.Coördineren van toestand binnen een threadgroep.
GlobaalToegankelijk over alle serverthreads (JVM-breed). Geheugen wordt behouden totdat sleutels expliciet worden verwijderd.Toegankelijkheid bredere gedeelde toestand binnen de applicatie.
Kiezen van een standaard - Geef de voorkeur aan PrivateNamespace

Wanneer je twijfelt, gebruik een PrivateNamespace. Het biedt veilige, scoped sharing tussen verwante sessies zonder de globale of serverbrede toestand te beïnvloeden. Dit maakt het een betrouwbare standaard voor de meeste applicaties.

Een namespace maken en gebruiken

Namespaces worden gemaakt door een van de beschikbare typen te instantieren. Elk type definieert hoe en waar de gegevens worden gedeeld. De onderstaande voorbeelden demonstreren hoe je een namespace maakt en interacteert met zijn waarden.

Privé namespace

De naam van de privé namespace bestaat uit twee delen:

  • Prefix: Een door de ontwikkelaar gedefinieerde identifier die uniek moet zijn voor je app of module om conflicten te voorkomen.
  • Basisnaam : De specifieke naam voor de gedeelde context of gegevens die je wilt beheren.

Samen vormen ze de volledige naam van de namespace met het formaat:

prefix + "." + baseName

Bijvoorbeeld, "myApp.sharedState".

Namespaces die zijn gemaakt met dezelfde prefix en basisnaam verwijzen altijd naar de zelfde onderliggende instantie. Dit zorgt voor consistente gedeelde toegang bij alle aanroepen van PrivateNamespace die dezelfde identificators gebruiken.

// Maak of haal een privé namespace op
PrivateNamespace ns = new PrivateNamespace("myApp", "sharedState");

Je kunt controleren op bestaan voordat je deze maakt:

if (PrivateNamespace.isPresent("myApp.sharedState")) {
PrivateNamespace ns = PrivateNamespace.ofExisting("myApp.sharedState");
}
Richtlijnen voor namen

Volg deze regels bij het benoemen van een PrivateNamespace:

  • Beide delen moeten niet leeg zijn.
  • Elk moet beginnen met een letter.
  • Alleen afdrukbare karakters zijn toegestaan.
  • Witruimten zijn niet toegestaan.

Voorbeelden:

  • ✓ mycrm.sessionData
  • ✓ acme.analytics
  • X shared.data (te algemeen, waarschijnlijk conflicterend)

Groep en Globaal namespaces

Naast PrivateNamespace biedt webforJ twee andere typen voor bredere deelcontexten. Deze zijn nuttig wanneer toestand moet persistent zijn voorbij een enkele sessie of threadgroep.

  • Globale Namespace: Toegankelijk over alle serverthreads (JVM-breed).
  • Groepsnamespace: Gedeeld tussen threads die afkomstig zijn van dezelfde bovenliggende.
// Globale gedeelde toestand, toegankelijk binnen de hele applicatie
GlobalNamespace globalNs = new GlobalNamespace();
globalNs.put("globalTheme", "dark");

// Groep-specifieke toestand, beperkt tot threads die een gemeenschappelijke oorsprong delen
GroupNamespace groupNs = new GroupNamespace();
groupNs.put("localCache", new HashMap<>());

Werken met waarden

Namespaces bieden een consistente interface voor het beheren van gedeelde gegevens via sleutel-waarde paren. Dit omvat het instellen, ophalen, verwijderen van waarden, synchroniseren van toegang en het observeren van veranderingen in realtime.

Waarden instellen en verwijderen

Gebruik put() om een waarde op te slaan onder een specifieke sleutel. Als de sleutel momenteel vergrendeld is, wacht de methode totdat de vergrendeling is opgeheven of de tijdsduur verstrijkt.

// Wacht tot 20ms (standaard) om de waarde in te stellen
ns.put("username", "admin");

// Specificeer een aangepaste time-out in milliseconden
ns.put("config", configObject, 100);

Om een sleutel uit de namespace te verwijderen:

ns.remove("username");

Zowel put() als remove() zijn blokkeringen als de doelsleutel vergrendeld is. Als de time-out verstrijkt voordat de vergrendeling is opgeheven, wordt er een NamespaceLockedException opgegooid.

Voor veilige concurrerende updates waar je alleen de waarde wilt overschrijven, gebruik atomicPut(). Het vergrendelt de sleutel, schrijft de waarde en geeft de vergrendeling in één stap vrij:

ns.atomicPut("counter", 42);

Dit voorkomt race-condities en vermijdt de noodzaak voor handmatige vergrendeling in eenvoudige updatescenario's.

Waarden ophalen

Om een waarde op te halen, gebruik je get():

Object value = ns.get("username");

Als de sleutel niet bestaat, gooit dit een NoSuchElementException. Om uitzonderingen te vermijden, gebruik getOrDefault():

Object value = ns.getOrDefault("username", "guest");

Om te controleren of een sleutel gedefinieerd is:

if (ns.contains("username")) {
// sleutel bestaat
}

Als je een waarde alleen wilt lazy initialiseren wanneer deze ontbreekt, gebruik dan computeIfAbsent():

Object token = ns.computeIfAbsent("authToken", key -> generateToken());

Dit is nuttig voor gedeelde waarden die eenmaal worden aangemaakt en hergebruikt, zoals sessietokens, configuratieblokken of gecachte gegevens.

Handmatige vergrendeling

Als je meerdere bewerkingen op dezelfde sleutel moet uitvoeren of moet coördineren over meerdere sleutels, gebruik dan handmatige vergrendeling.

ns.setLock("flag", 500); // Wacht tot 500ms voor de vergrendeling

// Kritieke sectie begint
Object existing = ns.get("flag");
ns.put("flag", "in-progress");
// Kritieke sectie eindigt

ns.removeLock("flag");

Gebruik dit patroon wanneer een reeks bewerkingen atomisch moet worden uitgevoerd over lezen en schrijven. Zorg altijd ervoor dat de vergrendeling wordt opgeheven om te voorkomen dat andere threads worden geblokkeerd.

Luisteren naar veranderingen

Namespaces ondersteunen gebeurtenisluiters waarmee je kunt reageren op toegang of wijziging van waarden. Dit is nuttig voor scenario's zoals:

  • Loggen of auditen van toegang tot gevoelige sleutels
  • Triggers voor updates wanneer een configuratiewaarde verandert
  • Monitoren van gedeelde toestand veranderingen in multi-gebruikersapps

Beschikbare listener-methoden

MethodeTriggerBereik
onAccessElke sleutel wordt gelezenHele namespace
onChangeElke sleutel wordt gewijzigdHele namespace
onKeyAccess("key")Een specifieke sleutel wordt gelezenPer sleutel
onKeyChange("key")Een specifieke sleutel wordt gewijzigdPer sleutel

Elke listener ontvangt een gebeurtenisobject met:

  • De sleutels naam
  • De oude waarde
  • De nieuwe waarde
  • Een verwijzing naar de namespace

Voorbeeld: Reageer op elke sleutelwijziging

ns.onChange(event -> {
System.out.println("Sleutel gewijzigd: " + event.getVariableName());
System.out.println("Oude waarde: " + event.getOldValue());
System.out.println("Nieuwe waarde: " + event.getNewValue());
});

Voorbeeld: Toegang tot een specifieke sleutel volgen

ns.onKeyAccess("sessionToken", event -> {
System.out.println("Token is geopend: " + event.getNewValue());
});

Listeners geven een ListenerRegistration object terug dat je kunt gebruiken om de listener later te annuleren:

ListenerRegistration<NamespaceKeyChangeEvent> reg = ns.onKeyChange("status", event -> {
// logica
});
reg.remove();

Voorbeeld: Delen van speltoestand in Tic-Tac-Toe

De webforJ Tic-Tac-Toe demo biedt een eenvoudig twee-spelersspel waar beurten worden gedeeld tussen gebruikers. Het project demonstreert hoe Namespace kan worden gebruikt om toestand te coördineren zonder afhankelijk te zijn van externe tools zoals databases of API's.

In dit voorbeeld wordt een gedeeld Java-spelobject opgeslagen in een PrivateNamespace, zodat meerdere cliënten met dezelfde spel logica kunnen interageren. De namespace dient als een centrale container voor de speltoestand, wat zorgt voor:

  • Beide spelers zien consistente bordupdates
  • Beurten zijn gesynchroniseerd
  • De spel logica wordt over sessies gedeeld

Geen externe services (zoals REST of WebSockets) zijn nodig. Alle coördinatie gebeurt via namespaces, wat hun vermogen benadrukt om gedeelde toestand in realtime te beheren met minimale infrastructuur.

Verken de code: webforj/webforj-tictactoe