Header Cache di Spring MVC

1. Gambaran keseluruhan

Dalam tutorial ini, kita akan belajar mengenai cache HTTP. Kami juga akan melihat pelbagai cara untuk menerapkan mekanisme ini antara pelanggan dan aplikasi Spring MVC.

2. Memperkenalkan HTTP Caching

Apabila kita membuka laman web pada penyemak imbas, biasanya memuat turun banyak sumber dari pelayan web:

Sebagai contoh, dalam contoh ini, penyemak imbas perlu memuat turun tiga sumber untuk satu / halaman log masuk . Adalah biasa bagi penyemak imbas untuk membuat beberapa permintaan HTTP untuk setiap laman web. Sekarang, jika kita meminta halaman seperti itu dengan kerap, ini menyebabkan banyak lalu lintas rangkaian dan memerlukan masa lebih lama untuk melayani halaman ini .

Untuk mengurangkan beban rangkaian, protokol HTTP membolehkan penyemak imbas menyimpan beberapa sumber ini. Sekiranya diaktifkan, penyemak imbas boleh menyimpan salinan sumber dalam cache tempatan. Akibatnya, penyemak imbas dapat melayani halaman ini dari storan tempatan dan bukannya memintanya melalui rangkaian:

Pelayan web boleh mengarahkan penyemak imbas untuk menyimpan sumber daya tertentu dengan menambahkan tajuk Cache-Control dalam respons.

Oleh kerana sumber daya di-cache sebagai salinan tempatan, ada risiko menyajikan kandungan basi dari penyemak imbas . Oleh itu, pelayan web biasanya menambah masa tamat pada tajuk Cache-Control .

Pada bahagian berikut, kami akan menambahkan tajuk ini sebagai tindak balas dari pengawal Spring MVC. Kemudian, kami juga akan melihat Spring API untuk mengesahkan sumber yang disimpan dalam cache berdasarkan masa tamat.

3. Cache-Control dalam Respons Pengawal

3.1. Menggunakan ResponseEntity

Cara paling mudah untuk melakukan ini adalah dengan menggunakan kelas pembangun CacheControl yang disediakan oleh Spring :

@GetMapping("/hello/{name}") @ResponseBody public ResponseEntity hello(@PathVariable String name) { CacheControl cacheControl = CacheControl.maxAge(60, TimeUnit.SECONDS) .noTransform() .mustRevalidate(); return ResponseEntity.ok() .cacheControl(cacheControl) .body("Hello " + name); }

Ini akan menambahkan tajuk Cache-Control dalam tindak balas:

@Test void whenHome_thenReturnCacheHeader() throws Exception { this.mockMvc.perform(MockMvcRequestBuilders.get("/hello/baeldung")) .andDo(MockMvcResultHandlers.print()) .andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(MockMvcResultMatchers.header() .string("Cache-Control","max-age=60, must-revalidate, no-transform")); }

3.2. Menggunakan HttpServletResponse

Selalunya, pengawal perlu mengembalikan nama pandangan dari kaedah pengendali. Walau bagaimanapun, kelas ResponseEntity tidak membenarkan kami mengembalikan nama pandangan dan berurusan dengan badan permintaan pada masa yang sama .

Sebagai alternatif, untuk pengawal seperti itu kita dapat mengatur header Cache-Control di HttpServletResponse secara langsung:

@GetMapping(value = "/home/{name}") public String home(@PathVariable String name, final HttpServletResponse response) { response.addHeader("Cache-Control", "max-age=60, must-revalidate, no-transform"); return "home"; }

Ini juga akan menambahkan header Cache-Control dalam respons HTTP yang serupa dengan bahagian terakhir:

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

4. Cache-Control untuk Sumber Statik

Secara amnya, aplikasi Spring MVC kami menyajikan banyak sumber statik seperti fail HTML, CSS dan JS. Oleh kerana fail seperti itu menggunakan banyak lebar jalur rangkaian, jadi penting bagi penyemak imbas untuk menyimpannya dalam cache. Kami akan mengaktifkannya lagi dengan tajuk Cache-Control sebagai tindak balas.

Spring membolehkan kita mengawal tingkah laku cache ini dalam pemetaan sumber:

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

Ini memastikan bahawa semua sumber yang ditentukan di bawah / sumber dikembalikan dengan header Cache-Control sebagai tindak balas .

5. Cache-Control dalam Pemintas

Kami boleh menggunakan pemintas dalam aplikasi Spring MVC kami untuk melakukan beberapa pra dan pasca pemprosesan untuk setiap permintaan. Ini adalah tempat letak lain di mana kita dapat mengawal tingkah laku cache aplikasi.

Sekarang daripada menerapkan pencegat khas, kami akan menggunakan WebContentInterceptor yang disediakan oleh Spring :

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

Di sini, kami mendaftarkan WebContentInterceptor dan menambahkan tajuk Cache-Control yang serupa dengan beberapa bahagian terakhir. Terutama, kita dapat menambahkan tajuk Cache-Control yang berbeza untuk corak URL yang berbeza.

Dalam contoh di atas, untuk semua permintaan bermula dengan / login , kami akan menambahkan tajuk ini:

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

6. Pengesahan Cache pada MVC Spring

Sejauh ini, kami telah membincangkan pelbagai cara termasuk tajuk Cache-Control sebagai tindak balas. Ini menunjukkan klien atau penyemak imbas menyimpan sumber daya berdasarkan sifat konfigurasi seperti usia maksimum .

Pada amnya adalah idea yang baik untuk menambahkan masa tamat cache dengan setiap sumber . Akibatnya, penyemak imbas dapat mengelakkan daripada menyajikan sumber yang telah habis tempohnya dari cache.

Walaupun penyemak imbas harus selalu memeriksa tarikh luput, mungkin tidak perlu mengambil sumber semula setiap masa. Sekiranya penyemak imbas dapat mengesahkan bahawa sumber daya tidak berubah pada pelayan, ia dapat terus menyajikan versi cache itu. Dan untuk tujuan ini, HTTP memberi kami dua tajuk respons:

  1. Etag - header respons HTTP yang menyimpan nilai hash unik untuk menentukan sama ada sumber cache telah berubah di pelayan - header permintaan If-None-Match yang sesuai mesti membawa nilai Etag terakhir
  2. LastModified - header respons HTTP yang menyimpan satuan masa ketika sumber terakhir dikemas kini - header permintaan If-Unmodified-Sejak yang sesuai mesti membawa tarikh terakhir yang diubah

Kami dapat menggunakan salah satu daripada tajuk ini untuk memeriksa apakah sumber daya yang telah habis tempohnya perlu diambil semula. Setelah mengesahkan tajuk, pelayan boleh menghantar semula sumber atau menghantar kod HTTP 304 untuk menandakan tidak ada perubahan . Untuk senario terakhir, penyemak imbas boleh terus menggunakan sumber cache.

The LastModified header can only store time intervals up to seconds precision. This can be a limitation in cases where a shorter expiry is required. For this reason, it's recommended to use Etag instead. Since Etag header stores a hash value, it's possible to create a unique hash up to more finer intervals like nanoseconds.

That said, let's check out what it looks like to use LastModified.

Spring provides some utility methods to check if the request contains an expiration header or not:

@GetMapping(value = "/productInfo/{name}") public ResponseEntity validate(@PathVariable String name, WebRequest request) { 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("Hello " + name); }

Spring provides the checkNotModified() method to check if a resource has been modified since the last request:

@Test void whenValidate_thenReturnCacheHeader() throws Exception { HttpHeaders headers = new 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. Conclusion

Dalam artikel ini, kami mengetahui tentang HTTP caching dengan menggunakan header respons Cache-Control di Spring MVC. Kita boleh menambahkan header dalam respons pengawal menggunakan kelas ResponseEntity atau melalui pemetaan sumber untuk sumber statik.

Kami juga boleh menambahkan tajuk ini untuk corak URL tertentu menggunakan pemintas Spring.

Seperti biasa, kodnya tersedia di GitHub.