Panduan untuk DeferredResult di Spring

1. Ikhtisar

Dalam tutorial ini, kita akan melihat bagaimana kita dapat menggunakan kelas DeferredResult di Spring MVC untuk melakukan pemrosesan permintaan asinkron .

Dukungan asinkron diperkenalkan di Servlet 3.0 dan, sederhananya, ini memungkinkan pemrosesan permintaan HTTP di utas lain selain utas penerima permintaan.

DeferredResult, tersedia mulai Spring 3.2 dan seterusnya, membantu memindahkan komputasi yang berjalan lama dari thread http-worker ke thread terpisah.

Meskipun utas lain akan mengambil beberapa sumber daya untuk komputasi, utas pekerja tidak diblokir untuk sementara dan dapat menangani permintaan klien yang masuk.

Model pemrosesan permintaan asinkron sangat berguna karena membantu menskalakan aplikasi dengan baik selama beban tinggi, terutama untuk operasi intensif IO.

2. Penyiapan

Untuk contoh kami, kami akan menggunakan aplikasi Spring Boot. Untuk detail lebih lanjut tentang cara mem-bootstrap aplikasi, lihat artikel kami sebelumnya.

Selanjutnya, kami akan mendemonstrasikan komunikasi sinkron dan asinkron menggunakan DeferredResult dan juga membandingkan bagaimana skala asinkron lebih baik untuk kasus penggunaan intensif beban tinggi dan IO.

3. Memblokir Layanan REST

Mari kita mulai dengan mengembangkan layanan REST pemblokiran standar:

@GetMapping("/process-blocking") public ResponseEntity handleReqSync(Model model) { // ... return ResponseEntity.ok("ok"); }

Masalahnya di sini adalah bahwa utas pemrosesan permintaan diblokir sampai permintaan lengkap diproses dan hasilnya dikembalikan. Dalam kasus komputasi yang berjalan lama, ini adalah solusi yang kurang optimal.

Untuk mengatasi ini, kita dapat memanfaatkan thread container dengan lebih baik untuk menangani permintaan klien seperti yang akan kita lihat di bagian selanjutnya.

4. Non-Blocking REST Menggunakan DeferredResult

Untuk menghindari pemblokiran, kita akan menggunakan model pemrograman berbasis callback di mana alih-alih hasil sebenarnya, kita akan mengembalikan DeferredResult ke wadah servlet.

@GetMapping("/async-deferredresult") public DeferredResult
    
      handleReqDefResult(Model model) { LOG.info("Received async-deferredresult request"); DeferredResult
     
       output = new DeferredResult(); ForkJoinPool.commonPool().submit(() -> { LOG.info("Processing in separate thread"); try { Thread.sleep(6000); } catch (InterruptedException e) { } output.setResult(ResponseEntity.ok("ok")); }); LOG.info("servlet thread freed"); return output; }
     
    

Pemrosesan permintaan dilakukan di utas terpisah dan setelah selesai kami menjalankan operasi setResult pada objek DeferredResult .

Mari kita lihat keluaran log untuk memeriksa apakah utas kita berperilaku seperti yang diharapkan:

[nio-8080-exec-6] com.baeldung.controller.AsyncDeferredResultController: Received async-deferredresult request [nio-8080-exec-6] com.baeldung.controller.AsyncDeferredResultController: Servlet thread freed [nio-8080-exec-6] java.lang.Thread : Processing in separate thread

Secara internal, thread container diberi tahu dan respons HTTP dikirim ke klien. Koneksi akan tetap terbuka oleh kontainer (servlet 3.0 atau yang lebih baru) hingga respons tiba atau habis waktu.

5. Panggilan Balik DeferredResult

Kita bisa mendaftarkan 3 jenis callback dengan DeferredResult: callback penyelesaian, batas waktu, dan error.

Mari gunakan metode onCompletion () untuk menentukan blok kode yang dieksekusi ketika permintaan async selesai:

deferredResult.onCompletion(() -> LOG.info("Processing complete"));

Demikian pula, kita bisa menggunakan onTimeout () untuk mendaftarkan kode kustom yang akan dipanggil setelah waktu tunggu habis. Untuk membatasi waktu pemrosesan permintaan, kita dapat meneruskan nilai batas waktu selama pembuatan objek DeferredResult :

DeferredResult
    
      deferredResult = new DeferredResult(500l); deferredResult.onTimeout(() -> deferredResult.setErrorResult( ResponseEntity.status(HttpStatus.REQUEST_TIMEOUT) .body("Request timeout occurred.")));
    

Jika terjadi waktu tunggu, kami menyetel status tanggapan yang berbeda melalui penangan batas waktu yang terdaftar dengan DeferredResult .

Mari kita picu kesalahan waktu tunggu dengan memproses permintaan yang membutuhkan lebih dari nilai batas waktu yang ditentukan selama 5 detik:

ForkJoinPool.commonPool().submit(() -> { LOG.info("Processing in separate thread"); try { Thread.sleep(6000); } catch (InterruptedException e) { ... } deferredResult.setResult(ResponseEntity.ok("OK"))); });

Mari kita lihat lognya:

[nio-8080-exec-6] com.baeldung.controller.DeferredResultController: servlet thread freed [nio-8080-exec-6] java.lang.Thread: Processing in separate thread [nio-8080-exec-6] com.baeldung.controller.DeferredResultController: Request timeout occurred

Akan ada skenario di mana komputasi yang berjalan lama gagal karena beberapa kesalahan atau pengecualian. Dalam kasus ini, kita juga bisa mendaftarkan callback onError () :

deferredResult.onError((Throwable t) -> { deferredResult.setErrorResult( ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body("An error occurred.")); });

Jika terjadi kesalahan, saat menghitung tanggapan, kami menetapkan status tanggapan dan badan pesan yang berbeda melalui penanganan kesalahan ini.

6. Kesimpulan

Dalam artikel singkat ini, kita melihat bagaimana Spring MVC DeferredResult memudahkan pembuatan titik akhir asinkron.

Seperti biasa, kode sumber lengkap tersedia di Github.