Komplett guide til Spring Boot 4 · leksjon

Abonnementer, feil og skjemasikkerhet

Strøm oppdateringer i sanntid gjennom abonnementer og herd skjemaet med autorisasjon på feltnivå.

Leksjon 4 av 413 trinn

Abonnementer, feil og skjemasikkerhet er en gratis leksjon i Komplett guide til Spring Boot 4 på CoddyKit. Dette er leksjon 4 av 4. Du kan lese valgfritt 3 leksjoner fra denne læringsstien gratis i sin helhet – deretter låser CoddyKit PRO opp alle leksjoner, samt praktisk øving med en innebygd kodeeditor og en AI-veileder som er tilgjengelig døgnet rundt. Den er en del av læringsløpet i Komplett guide til Spring Boot 4, og fremdriften din synkroniseres mellom nettet og CoddyKit-appen. Kurset i Komplett guide til Spring Boot 4 inneholder totalt 4 leksjoner.

Utover Query og Mutation

Spring for GraphQL støtter tre typer rotoperasjoner. Så langt har De brukt Query (lesing) og Mutation (skriving). Den tredje er Subscription — en langvarig datastrøm som sender data til klienten hver gang en hendelse oppstår på serversiden.

  • Query/Mutation: én forespørsel, ett svar.
  • Subscription: én forespørsel, mange svar over tid.

I denne leksjonen skal De strømme direkteoppdateringer med subscriptions, utforme GraphQL-feil på en ryddig måte og sikre individuelle felt med metodesikkerhet.

Deklarere en Subscription i skjemaet

Subscriptions deklareres i GraphQL-skjemaet på samme måte som spørringer. Hvert subscription-felt beskriver en datastrøm av en bestemt type. Nedenfor strømmer messageAdded en Message for et bestemt rom.

Transporten for subscriptions er vanligvis WebSocket (graphql-ws-protokollen), så sørg for at klienten kobler til via ws:// i stedet for vanlig HTTP POST.

type Subscription {
  messageAdded(roomId: ID!): Message!
}

type Message {
  id: ID!
  roomId: ID!
  text: String!
  author: String!
}

En Subscription returnerer en reaktiv datastrøm

I Spring for GraphQL må en metode med annotasjonen @SubscriptionMapping returnere en Publisher fra Reactive Streams — vanligvis en Project Reactor-Flux. Hvert element som Flux sender ut, leveres til den abonnerende klienten som et separat GraphQL-svar.

  • Metodenavnet kobles til subscription-feltet (messageAdded).
  • Argumenter kommer fra @Argument, akkurat som for spørringer.
  • Flux forblir åpen til den fullføres, får en feil eller klienten kobler fra.
@Controller
public class ChatSubscriptionController {

    private final ChatService chatService;

    public ChatSubscriptionController(ChatService chatService) {
        this.chatService = chatService;
    }

    @SubscriptionMapping
    public Flux<Message> messageAdded(@Argument String roomId) {
        return chatService.streamMessages(roomId);
    }
}

Understøtte datastrømmen med en Sink

Hvor kommer Flux fra? Et vanlig mønster er å bruke en Reactor-Sinks.Many som en varm multicast-kilde. Mutasjoner sender nye hendelser inn i sinken med tryEmitNext, og alle aktive abonnenter mottar dem.

Bruk multicast().onBackpressureBuffer() slik at flere abonnenter kan dele én kilde, og filtrer per rom slik at hver klient bare får sine egne meldinger.

@Service
public class ChatService {

    private final Sinks.Many<Message> sink =
            Sinks.many().multicast().onBackpressureBuffer();

    public Message publish(Message message) {
        sink.tryEmitNext(message);
        return message;
    }

    public Flux<Message> streamMessages(String roomId) {
        return sink.asFlux()
                .filter(m -> m.roomId().equals(roomId));
    }
}

Koble en Mutation til datastrømmen

En subscription sender bare ut data når noe mater den. Her oppretter en @MutationMapping en melding og publiserer den i sinken. Alle klienter som abonnerer på rommet, mottar den nye meldingen umiddelbart.

Denne frakoblingen — mutasjonen skriver, mens subscription leser fra den samme sinken — er grunnlaget for GraphQL i sanntid.

@Controller
public class ChatMutationController {

    private final ChatService chatService;

    public ChatMutationController(ChatService chatService) {
        this.chatService = chatService;
    }

    @MutationMapping
    public Message postMessage(@Argument String roomId,
                               @Argument String text,
                               @Argument String author) {
        Message message = new Message(
                UUID.randomUUID().toString(), roomId, text, author);
        return chatService.publish(message);
    }
}

Aktivere WebSocket-transporten

Subscriptions krever at WebSocket-endepunktet er aktivert. Med Spring Boot 4 og GraphQL-starteren angir De banen i application.yml. HTTP-endepunktet (/graphql) brukes fortsatt for spørringer og mutasjoner, mens WebSocket-endepunktet (/graphql over ws) håndterer subscriptions.

Uten denne egenskapen registreres ikke WebSocket-behandleren, og subscription-klienter klarer ikke å koble til.

spring:
  graphql:
    websocket:
      path: /graphql
      connection-init-timeout: 60s
    schema:
      printer:
        enabled: true

Hvorfor GraphQLs standardfeil lekker detaljer

Når et unntak slipper ut av en kontrollermetode, gjør Spring for GraphQL det om til en GraphQL-feil. Som standard vises mange unntak som INTERNAL_ERROR med en generell melding, men stack traces og uventede unntaksmeldinger kan lekke implementasjonsdetaljer hvis De ikke er forsiktig.

Løsningen er en DataFetcherExceptionResolver: koble domeneunntak til ryddige, riktig klassifiserte GraphQLError-objekter med riktig ErrorType.

Koble unntak med @GraphQlExceptionHandler

Den enkleste fremgangsmåten er en metode med @GraphQlExceptionHandler inne i en @Controller (eller en @ControllerAdvice for globalt omfang). Den fungerer som unntakshåndtering i Spring MVC: fang et bestemt unntak og returner en GraphQLError.

  • ErrorType.NOT_FOUND → ressursen mangler.
  • ErrorType.BAD_REQUEST → ugyldige inndata.
  • ErrorType.FORBIDDEN → autorisasjonsfeil.

extensions-mappet inneholder maskinlesbare detaljer som klienten kan reagere på.

@ControllerAdvice
public class GraphQlExceptionAdvice {

    @GraphQlExceptionHandler
    public GraphQLError handleNotFound(MessageNotFoundException ex) {
        return GraphQLError.newError()
                .errorType(ErrorType.NOT_FOUND)
                .message(ex.getMessage())
                .extensions(Map.of("code", "MESSAGE_NOT_FOUND"))
                .build();
    }
}

Autorisasjon på feltnivå med @PreAuthorize

GraphQL eksponerer ett enkelt endepunkt, så De kan ikke basere sikkerheten på URL-er. Sikre i stedet individuelle felt på metodenivå. Når Spring Securitys metodesikkerhet er aktivert (@EnableMethodSecurity), kan De annotere kontrollermetoder med @PreAuthorize.

Hvis kontrollen mislykkes, blir feltet løst til null, og en autorisasjonsfeil legges til i GraphQLs errors-array — søskenfelter løses fortsatt som normalt.

@Controller
public class AdminController {

    @QueryMapping
    @PreAuthorize("hasRole('ADMIN')")
    public List<AuditEntry> auditLog() {
        return auditService.findAll();
    }

    @SchemaMapping(typeName = "Message", field = "author")
    @PreAuthorize("isAuthenticated()")
    public String author(Message message) {
        return message.author();
    }
}

Videreføre sikkerhet til reaktive subscriptions

Subscriptions kjører på den reaktive WebSocket-transporten, så SecurityContext må følge den reaktive kjeden. Spring Security fyller ut Reactor-konteksten; hent den autentiserte brukeren med ReactiveSecurityContextHolder i stedet for den trådlokale SecurityContextHolder.

Dette gjør det mulig å filtrere en subscription-strøm etter gjeldende bruker — for eksempel ved bare å sende meldinger fra rom brukeren er medlem av.

@SubscriptionMapping
@PreAuthorize("isAuthenticated()")
public Flux<Message> messageAdded(@Argument String roomId) {
    return ReactiveSecurityContextHolder.getContext()
            .map(ctx -> ctx.getAuthentication().getName())
            .flatMapMany(user ->
                    chatService.streamMessages(roomId, user));
}

En frittstående demonstrasjon av en Flux-strøm

De trenger ikke en kjørende server for å forstå hvordan en abonnementstrøm oppfører seg. Eksempelet nedenfor bruker en vanlig Reactor Flux med et filter – nøyaktig samme form som streamMessages returnerer – og skriver ut hvert element som sendes ut, slik at det etterligner det en abonnert klient ville mottatt.

Legg merke til at bare meldinger som samsvarer med rommet, slipper gjennom, akkurat som ved filtrering per rom på serversiden.

import reactor.core.publisher.Flux;

public class StreamDemo {
    record Message(String roomId, String text) {}

    public static void main(String[] args) {
        Flux<Message> source = Flux.just(
                new Message("general", "hi"),
                new Message("random", "noise"),
                new Message("general", "streaming works"));

        source.filter(m -> m.roomId().equals("general"))
              .subscribe(m -> System.out.println("Push -> " + m.text()));
    }
}

Hurtigsjekk: Sikring av et felt

De ønsker at bare brukere med rollen ADMIN skal kunne lese spørringsfeltet auditLog, mens alle andre felt i det samme svaret fortsatt skal løses normalt for alle brukere. Hvilken tilnærming passer best med GraphQL?

Oppsummering

De har nå verktøyene for sanntid og sikkerhet i Spring for GraphQL:

  • Abonnementer deklareres i skjemaet og implementeres med @SubscriptionMapping som returnerer en reaktiv Flux/Publisher.
  • En varm Sinks.Many-kilde lar mutasjoner sende hendelser som abonnenter strømmer, filtrert per rom.
  • Aktiver WebSocket-transport via spring.graphql.websocket.path.
  • Gjør domenekonsept unntak om til ryddige GraphQLError-er med @GraphQlExceptionHandler og riktig ErrorType.
  • Sikre individuelle felt med @PreAuthorize; feil setter feltet til null og legger til en feil uten å påvirke søskenfeltene.
  • For abonnementer henter De principal-objektet fra den reaktive ReactiveSecurityContextHolder.
Gratis å komme i gang

Lær deg Java med en AI-veileder – gratis

Skriv og kjør ekte kode i nettleseren, få umiddelbar hjelp fra en AI-veileder som er tilgjengelig døgnet rundt, og fortsett der du slapp – på nettet eller i appen.

Kurs
21
Leksjoner
84

Ofte stilte spørsmål

Er leksjonen «Abonnementer, feil og skjemasikkerhet» gratis?

Ja – du kan lese valgfritt 3 av leksjonene i læringsstien Komplett guide til Spring Boot 4, inkludert «Abonnementer, feil og skjemasikkerhet», gratis i sin helhet her på nettet. Deretter låser CoddyKit PRO opp alle leksjoner, samt interaktiv øving med en innebygd kodeeditor og en AI-veileder som er tilgjengelig døgnet rundt. Kurset i Komplett guide til Spring Boot 4 inneholder totalt 4 leksjoner.

Hva lærer jeg i «Abonnementer, feil og skjemasikkerhet»?

Strøm oppdateringer i sanntid gjennom abonnementer og herd skjemaet med autorisasjon på feltnivå. Du øver på Komplett guide til Spring Boot 4 med praktisk kode som du kjører direkte i nettleseren, mens en AI-veileder som er tilgjengelig døgnet rundt, svarer på spørsmålene dine mens du jobber deg gjennom leksjonen.

Trenger jeg erfaring for å begynne med Komplett guide til Spring Boot 4?

Ingen tidligere erfaring er nødvendig. Komplett guide til Spring Boot 4 på CoddyKit er lagt opp for både nybegynnere og viderekomne, så De kan begynne her eller helt fra start og lære i Deres eget tempo. Dette er leksjon 4 av 4.

Hvor lang tid tar leksjonen «Abonnementer, feil og skjemasikkerhet»?

De fleste CoddyKit-leksjoner tar omtrent 5–10 minutter. Hver leksjon er kort og interaktiv, slik at De gjør jevne fremskritt og kan fortsette akkurat der De slapp – både på nettet og i appen.

Kan jeg skrive og kjøre kode i denne Komplett guide til Spring Boot 4-leksjonen?

Ja. Alle Komplett guide til Spring Boot 4-leksjoner har en innebygd kodeeditor, slik at De kan skrive og kjøre ekte kode direkte i nettleseren og få umiddelbar tilbakemelding fra AI – uten lokal konfigurering.

Alle leksjonene i dette kurset

  1. Skjemaførst-design og typetilordning
  2. Datahentere og binding av argumenter
  3. Løse N+1 med batchinnlastere
  4. Abonnementer, feil og skjemasikkerhet
← Tilbake til Komplett guide til Spring Boot 4