Panduan untuk DeferredResult pada Musim Bunga

1. Gambaran keseluruhan

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

Sokongan asynchronous diperkenalkan di Servlet 3.0 dan, secara sederhana, ia membolehkan memproses permintaan HTTP di thread lain daripada thread penerima permintaan.

DeferredResult, tersedia dari Spring 3.2 dan seterusnya, membantu memuatkan pengiraan jangka panjang dari utas pekerja http ke utas yang terpisah.

Walaupun utas lain akan mengambil beberapa sumber untuk pengiraan, utas pekerja tidak disekat sementara waktu dan dapat menangani permintaan pelanggan masuk.

Model pemprosesan permintaan async sangat berguna kerana dapat membantu menskalakan aplikasi dengan baik semasa beban tinggi, terutama untuk operasi intensif IO.

2. Persediaan

Sebagai contoh, kami akan menggunakan aplikasi Spring Boot. Untuk maklumat lebih lanjut mengenai cara boot aplikasi, rujuk artikel kami sebelumnya.

Seterusnya, kami akan menunjukkan komunikasi segerak dan tidak segerak menggunakan DeferredResult dan juga membandingkan bagaimana skala tidak segerak lebih baik untuk kes penggunaan intensif beban tinggi dan IO.

3. Menyekat Perkhidmatan REST

Mari mulakan dengan membangunkan perkhidmatan REST sekatan standard:

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

Masalahnya di sini ialah urutan pemprosesan permintaan disekat sehingga permintaan lengkap diproses dan hasilnya dikembalikan. Sekiranya pengiraan berjalan lama, ini adalah penyelesaian yang tidak optimum.

Untuk mengatasi hal ini, kita dapat menggunakan utas kontena dengan lebih baik untuk menangani permintaan pelanggan seperti yang akan kita lihat di bahagian seterusnya.

4. REST Tidak Menyekat Menggunakan DeferredResult

Untuk mengelakkan penyekat, kami akan menggunakan model pengaturcaraan berasaskan panggilan balik di mana bukannya hasil yang sebenarnya, kami 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; }
     
    

Pemprosesan permintaan dilakukan dalam utas yang berasingan dan setelah selesai kami memanggil operasi setResult pada objek DeferredResult .

Mari lihat output log untuk memeriksa bahawa benang kami berkelakuan 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 dalaman, utas kontena diberitahu dan respons HTTP dihantar kepada klien. Sambungan akan tetap dibuka oleh kontena (servlet 3.0 atau lebih baru) sehingga tindak balas tiba atau habis masa.

5. Panggilan Balik DeferredResult

Kami boleh mendaftarkan 3 jenis panggilan balik dengan DeferredResult: penyelesaian, tamat masa dan ralat panggilan balik.

Mari gunakan kaedah onCompletion () untuk menentukan sekatan kod yang dilaksanakan apabila permintaan async selesai:

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

Begitu juga, kita dapat menggunakan onTimeout () untuk mendaftarkan kod tersuai untuk dipanggil setelah tamat masa berlaku. Untuk mengehadkan masa pemprosesan permintaan, kita dapat melewati nilai waktu tunggu semasa pembuatan objek DeferredResult :

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

Sekiranya tamat masa, kami menetapkan status respons yang berbeza melalui pengendali timeout yang berdaftar dengan DeferredResult .

Mari kita mencetuskan ralat masa tamat dengan memproses permintaan yang memerlukan lebih daripada nilai masa tamat yang ditentukan selama 5 saat:

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

Mari lihat log:

[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 senario di mana pengiraan jangka panjang gagal kerana beberapa kesalahan atau pengecualian. Dalam kes ini, kita juga dapat mendaftarkan panggilan balik onError () :

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

Sekiranya berlaku ralat, semasa mengira respons, kami menetapkan status respons dan isi mesej yang berbeza melalui pengendali ralat ini.

6. Kesimpulannya

Dalam artikel ringkas ini, kami melihat bagaimana Spring MVC DeferredResult mempermudah penciptaan titik akhir tak segerak.

Seperti biasa, kod sumber lengkap boleh didapati di Github.