Cachekoppen in Spring MVC

1. Overzicht

In deze zelfstudie leren we over HTTP-caching. We zullen ook kijken naar verschillende manieren om dit mechanisme tussen een client en een Spring MVC-applicatie te implementeren.

2. Introductie van HTTP-caching

Wanneer we een webpagina in een browser openen, worden er meestal veel bronnen van de webserver gedownload:

In dit voorbeeld moet een browser bijvoorbeeld drie bronnen downloaden voor één /Log in bladzijde. Het is normaal dat een browser meerdere HTTP-verzoeken doet voor elke webpagina. Als we dergelijke pagina's nu heel vaak opvragen, veroorzaakt dit veel netwerkverkeer en duurt het langer om deze pagina's weer te geven.

Om de netwerkbelasting te verminderen, stelt het HTTP-protocol browsers in staat om een ​​aantal van deze bronnen in het cachegeheugen te plaatsen. Indien ingeschakeld, kunnen browsers een kopie van een bron in de lokale cache opslaan. Als gevolg hiervan kunnen browsers deze pagina's vanuit de lokale opslag bedienen in plaats van deze via het netwerk op te vragen:

Een webserver kan de browser opdracht geven een bepaalde bron in het cachegeheugen op te slaan door een Cache-controle koptekst in het antwoord.

Omdat de bronnen in de cache worden opgeslagen als een lokale kopie,het risico bestaat dat verouderde inhoud vanuit de browser wordt weergegeven. Daarom voegen webservers meestal een vervaltijd toe aan het Cache-controle koptekst.

In de volgende secties zullen we deze header toevoegen in een reactie van de Spring MVC-controller. Later zullen we ook Spring API's zien om de bronnen in de cache te valideren op basis van de vervaltijd.

3. Cache-controle in het antwoord van de controller

3.1. Gebruik makend van ResponseEntity

De meest eenvoudige manier om dit te doen, is doorgebruik de CacheControl builder-klasse geleverd door Spring:

@GetMapping ("/ hallo / {naam}") @ResponseBody openbare ResponseEntity hallo (@PathVariable String naam) {CacheControl cacheControl = CacheControl.maxAge (60, TimeUnit.SECONDS) .noTransform () .mustRevalidate (); return ResponseEntity.ok () .cacheControl (cacheControl) .body ("Hallo" + naam); }

Dit zal een Cache-controle koptekst in het antwoord:

@Test void whenHome_thenReturnCacheHeader () gooit uitzondering {this.mockMvc.perform (MockMvcRequestBuilders.get ("/ hallo / baeldung")) .andDo (MockMvcResultHandlers.print ()) .andExpect (MockMockMvc.perform (MockMvcResultMatchers). IsOkatus ()). IsOkatus (). andExpect (MockMvcResultMatchers.header () .string ("Cache-Control", "max-age = 60, must-revalidate, no-transform")); }

3.2. Gebruik makend van HttpServletResponse

Vaak moeten de controllers de weergavenaam van de handlermethode retourneren. echter, deResponseEntity class staat ons niet toe om de viewnaam te retourneren en tegelijkertijd de hoofdtekst van het verzoek te behandelen.

Als alternatief kunnen we voor dergelijke controllers de Cache-controle koptekst in het HttpServletResponse direct:

@GetMapping (value = "/ home / {name}") public String home (@PathVariable String-naam, laatste HttpServletResponse-reactie) {response.addHeader ("Cache-Control", "max-age = 60, moet opnieuw worden gevalideerd, nee -transformeren"); terug naar huis"; }

Dit zal ook een Cache-controle header in het HTTP-antwoord vergelijkbaar met de laatste sectie:

@Test void whenHome_thenReturnCacheHeader () gooit uitzondering {this.mockMvc.perform (MockMvcRequestBuilders.get ("/ home / baeldung")) .andDo (MockMvcResultHandlers.print ()) .andExpect (MockMockMvc.perform (MockMvcResultMatchers) isOkatus (). andExpect (MockMvcResultMatchers.header () .string ("Cache-Control", "max-age = 60, must-revalidate, no-transform")) .andExpect (MockMvcResultMatchers.view (). name ("home")); }

4. Cache-controle voor statische bronnen

Over het algemeen bedient onze Spring MVC-applicatie veel statische bronnen zoals HTML-, CSS- en JS-bestanden. Omdat dergelijke bestanden veel netwerkbandbreedte verbruiken, is het belangrijk dat browsers ze in het cachegeheugen opslaan. We zullen dit weer inschakelen met de Cache-controle koptekst in het antwoord.

Spring stelt ons in staat om dit cachegedrag bij het in kaart brengen van bronnen te beheersen:

@Override public void addResourceHandlers (laatste ResourceHandlerRegistry-register) {registry.addResourceHandler ("/ resources / **"). AddResourceLocations ("/ resources /") .setCacheControl (CacheControl.maxAge (60, TimeUnit.SECONDS) .noTransform (). mustRevalidate ()); }

Dit zorgt ervoor dat alle middelengedefinieerd onder/middelen worden geretourneerd met een Cache-controle koptekst in het antwoord.

5. Cache-controle in Interceptors

We kunnen interceptors gebruiken in onze Spring MVC-applicatie om voor elk verzoek wat pre- en post-processing uit te voeren. Dit is een andere tijdelijke aanduiding waar we het cachegedrag van de applicatie kunnen regelen.

In plaats van een aangepaste interceptor te implementeren, gebruiken we nu de WebContentInterceptor geleverd door Spring:

@Override public void addInterceptors (InterceptorRegistry-register) {WebContentInterceptor interceptor = nieuwe WebContentInterceptor (); interceptor.addCacheMapping (CacheControl.maxAge (60, TimeUnit.SECONDS) .noTransform () .mustRevalidate (), "/ login / *"); registry.addInterceptor (interceptor); }

Hier hebben we het WebContentInterceptor en voegde het Cache-controle koptekst vergelijkbaar met de laatste paar secties. We kunnen met name verschillende toevoegen Cache-controle headers voor verschillende URL-patronen.

In het bovenstaande voorbeeld, voor alle verzoeken die beginnen met /Log in, we zullen deze koptekst toevoegen:

@Test void whenInterceptor_thenReturnCacheHeader () gooit Uitzondering {this.mockMvc.perform (MockMvcRequestBuilders.get ("/ login / baeldung")) .andDo (MockMvcResultHandlers.print ()) .andExpect (MockMvcResult (). IsOstatus (). IsOstatus (). andExpect (MockMvcResultMatchers.header () .string ("Cache-Control", "max-age = 60, must-revalidate, no-transform")); }

6. Cachevalidatie in Spring MVC

Tot nu toe hebben we verschillende manieren besproken om een Cache-controle koptekst in het antwoord. Dit geeft de clients of browsers aan die de bronnen in het cachegeheugen moeten opslaan op basis van configuratie-eigenschappen zoals max-leeftijd.

Het is over het algemeen het is een goed idee om bij elke resource een verlooptijd van de cache toe te voegen. Als gevolg hiervan kunnen browsers voorkomen dat verlopen bronnen uit de cache worden weergegeven.

Hoewel browsers altijd moeten controleren op vervallen, is het misschien niet nodig om de bron elke keer opnieuw op te halen. Als een browser kan valideren dat een bron niet is gewijzigd op de server, kan deze de gecachte versie ervan blijven leveren. En voor dit doel biedt HTTP ons twee antwoordheaders:

  1. Etag - een HTTP-antwoordheader die een unieke hash-waarde opslaat om te bepalen of een bron in de cache op de server is gewijzigd - een overeenkomstig If-None-Match request header moet de laatste Etag-waarde bevatten
  2. Laatst gewijzigd - een HTTP-antwoordheader die een tijdseenheid opslaat wanneer de bron voor het laatst is bijgewerkt - een overeenkomstig If-Unmodified-Since verzoekheader moet de datum van de laatste wijziging bevatten

We kunnen een van deze headers gebruiken om te controleren of een verlopen bron opnieuw moet worden opgehaald. Na validatie van de headers,de server kan de bron opnieuw verzenden of een 304 HTTP-code verzenden om aan te geven dat er geen verandering is. Voor het laatste scenario kunnen browsers de bron in de cache blijven gebruiken.

De Laatst gewijzigd header kan alleen tijdsintervallen tot op seconden nauwkeurig opslaan. Dit kan een beperking zijn in gevallen waarin een kortere vervaldatum vereist is. Om deze reden wordt het aanbevolen om te gebruiken Etag in plaats daarvan. Sinds Etag header slaat een hash-waarde op, het is mogelijk om een ​​unieke hash te maken met fijnere intervallen zoals nanoseconden.

Dat gezegd hebbende, laten we eens kijken hoe het eruit ziet om te gebruiken Laatst gewijzigd.

Spring biedt enkele hulpprogramma-methoden om te controleren of het verzoek een verloopheader bevat of niet:

@GetMapping (value = "/ productInfo / {name}") openbare ResponseEntity validate (@PathVariable String-naam, WebRequest-verzoek) {ZoneId zoneId = ZoneId.of ("GMT"); long lastModifiedTimestamp = LocalDateTime.of (2020, 02, 4, 19, 57, 45) .atZone (zoneId) .toInstant (). toEpochMilli (); if (request.checkNotModified (lastModifiedTimestamp)) {return ResponseEntity.status (304) .build (); } return ResponseEntity.ok (). body ("Hallo" + naam); }

De lente biedt de checkNotModified () methode om te controleren of een bron is gewijzigd sinds het laatste verzoek:

@Test ongeldig whenValidate_thenReturnCacheHeader () gooit Uitzondering {HttpHeaders headers = nieuwe HttpHeaders (); headers.add (IF_UNMODIFIED_SINCE, "Tue, 04 feb 2020 19:57:25 GMT"); this.mockMvc.perform (MockMvcRequestBuilders.get ("/ productInfo / baeldung"). headers (headers)) .andDo (MockMvcResultHandlers.print ()) .andExpect (MockMvcResultMatchers.status (). is (304)); }

7. Conclusie

In dit artikel hebben we geleerd over HTTP-caching door de Cache-controle antwoordheader in Spring MVC. We kunnen ofwel de header toevoegen aan het antwoord van de controller met behulp van de ResponseEntity klasse of via resourcetoewijzing voor statische bronnen.

We kunnen deze koptekst ook toevoegen voor bepaalde URL-patronen met behulp van Spring-interceptors.

Zoals altijd is de code beschikbaar op GitHub.