Asinxron so'rovlar

All lectures for UZ purposes
Daraja, Dars
Mavjud

Spring MVC Servlet 3.0 asinxron so'rovlarini keng qo'llab-quvvatlaydi:

  • Kontroller metodlaridagi DeferredResult va Callable qaytish qiymatlari bitta asinxron qaytish qiymatini qo'llab-quvvatlashni ta'minlaydi.

  • Kontrollerlar bir nechta qiymatlarni, shu jumladan SSE va xom ma'lumotlarni uzatishi mumkin.

  • Kontrollerlar reaktiv mijozlarni ishlatishi va javoblarni qayta ishlash uchun reaktiv turlarni qaytarishi mumkin.

DeferredResult

Servlet konteynerida asinxron so'rovlarni qayta ishlash funktsiyasi faollashgandan so'ng, kontroller metodlari DeferredResult yordamida har qanday qo'llab-quvvatlangan kontroller metod qaytish qiymatini o'rashi mumkin, bu quyidagi misolda ko'rsatilgan:

Java
@GetMapping("/quotes")
@ResponseBody
public DeferredResult<String> quotes() {
    DeferredResult<String> deferredResult = new DeferredResult<String>();
    // Boshqa bir oqimdan...
    return deferredResult;
}
// deferredResultni qayerdadir saqlaymiz...
deferredResult.setResult(result);
Kotlin
@GetMapping("/quotes")
@ResponseBody
fun quotes(): DeferredResult<String> {
    val deferredResult = DeferredResult<String>()
    // Boshqa bir oqimdan...
    return deferredResult
}
// deferredResultni qayerdadir saqlaymiz...
deferredResult.setResult(result)

Kontroller qaytish qiymatini asinxron tarzda, boshqa bir oqimdan olayotishi mumkin - masalan, tashqi voqea (JMS xabari), belgilangan vazifa yoki boshqa bir voqea javobida.

Callable

Kontroller java.util.concurrent.Callable yordamida har qanday qo'llab-quvvatlangan qaytish qiymatini o'rashi mumkin, bu quyidagi misolda ko'rsatilgan:

Java
@PostMapping
public Callable<String> processUpload(final MultipartFile file) {
    return new Callable<String>() {
        public String call() throws Exception {
            // ...
            return "someView";
        }
    };
}
Kotlin
@PostMapping
fun processUpload(file: MultipartFile) = Callable<String> {
    // ...
    "someView"
}

Qaytish qiymatini belgilangan TaskExecutor orqali ushbu vazifani bajargandan so'ng olish mumkin.

Qayta ishlash

Quyidagi kichik ko'rinish servlet so'rovlarini asinxron qayta ishlashni tavsiflaydi:

  • ServletRequestni request.startAsync()ni chaqirish orqali asinxron rejimga o'tkazish mumkin. Asosiy natija shundaki, servlet (shuningdek, har qanday filtrlar) bajarishni tugatishi mumkin, lekin javob ochiq qoladi, shunda qayta ishlash keyinchalik tugatiladi.

  • request.startAsync() chaqiruvi AsyncContextni qaytaradi, uni asinxron qayta ishlash ustidan yanada ko'proq nazorat qilish uchun ishlatish mumkin. Masalan, u dispatch metodini ta'minlaydi, bu Servlet API-dan yuborishga o'xshaydi, lekin u ilova uchun so'rovni servlet konteyner oqimidagi qayta ishlashni davom ettirish imkonini beradi.

  • ServletRequest joriy DispatcherTypega kirish imkoniyatini beradi, bu birlamchi so'rovni qayta ishlash, asinxron dispetcherlash, qayta yo'naltirish va boshqa turdagi dispetcherlar orasida farqlash uchun ishlatilishi mumkin.

DeferredResultni qayta ishlash quyidagicha ishlaydi:

  • Kontroller DeferredResultni qaytaradi va uni xotirada kirish mumkin bo'lgan navbat yoki ro'yxatda saqlaydi.

  • Spring MVC request.startAsync()ni chaqiradi.

  • Ayni paytda DispatcherServlet va barcha sozlangan filtrlar so'rov oqimidan chiqib ketadi, ammo javob ochiq qoladi.

  • Ilova DeferredResultni qandaydir oqimdan o'rnatadi va Spring MVC so'rovni servlet konteyneriga qayta yuboradi.

  • DispatcherServlet yana chaqiriladi va qayta ishlash asinxron tarzda olingan qaytish qiymati yordamida qayta boshlanadi.

Callableni qayta ishlash shunday bo'ladi:

  • Kontroller Callableni qaytaradi.

  • Spring MVC request.startAsync()ni chaqiradi va TaskExecutordan alohida oqimda Callableni qayta ishlash uchun uni topshiradi.

  • Ayni paytda DispatcherServlet va barcha filtrlar servlet konteyner oqimidan chiqib ketadi, lekin javob ochiq qoldiriladi.

  • Natijada Callable natijani beradi va Spring MVC so'rovni qayta ishlashni tugatish uchun servlet konteyneriga qayta yuboradi.

  • DispatcherServlet yana chaqiriladi va qayta ishlash asinxron tarzda Callabledan olingan qaytish qiymati yordamida qayta boshlanadi.

Qo'shimcha ma'lumot va kontekst uchun Spring MVC 3.2 asinxron so'rovlar qo'llab-quvvatlashi haqida e'lon qilingan blog postlarini o'qib chiqishingiz mumkin.

Istisnolarni qayta ishlash

Agar siz DeferredResultdan foydalanayotgan bo'lsangiz, siz setResult yoki setErrorResultni istisno bilan chaqirasizmi, tanlash imkoniga egasiz. Har ikki holatda ham Spring MVC so'rovni qayta ishlashni tugatish uchun servlet konteyneriga qaytaradi. Keyin u kontroller metodi belgilangan qiymatni qaytarganday yoki belgilangan istisnoni qaytarganday ko'rib chiqiladi. Keyin istisno odatdagi istisno qayta ishlash mexanizmi (masalan, @ExceptionHandler bilan anotatsiyalangan metodlarni chaqirish) orqali o'tadi.

Callabledan foydalanganda, shunga o'xshash qayta ishlash mantiqi sodir bo'ladi, lekin asosiy farq natija Callabledan qaytariladi yoki unda istisno sodir bo'ladi.

Interceptor

HandlerInterceptor nusxalari AsyncHandlerInterceptor tipiga ega bo'lishi mumkin, bu asinxron qayta ishlashni boshlaydigan birlamchi so'rovda (instead of postHandle va afterCompletion) afterConcurrentHandlingStartedqaytarmasini olish uchun.

HandlerInterceptor implementatsiyalari CallableProcessingInterceptor yoki DeferredResultProcessingInterceptorni asinxron so'rovning hayotiy tsikliga chuqur integratsiya qilish uchun (masalan, vaqt tugash voqeasini qayta ishlash uchun) ham ro'yxatdan o'tkazishi mumkin. Qo'shimcha ma'lumot uchun AsyncHandlerInterceptor bo'limini qarang.

DeferredResult onTimeout(Runnable) va onCompletion(Runnable) qaytarmalarini taqdim etadi. Batafsil ma'lumotlar uchun DeferredResult uchun javadocni qarang. Callableni WebAsyncTaskga almashtirishingiz mumkin, bu esa kutish vaqti va yakuniy qaytarmalar uchun qo'shimcha metodlarni ochadi.

WebFlux bilan taqqoslash

Servlet API dastlab "filtr-servlet" zanjiridan o'tish uchun ishlab chiqilgan. Servlet 3.0 da qo'shilgan asinxron so'rovlarni qayta ishlash ilovalarga "filtr-servlet" zanjiridan chiqish imkonini beradi, lekin keyinchalik qayta ishlash uchun javobni ochiq qoldiradi. Spring MVCda asinxronlik qo'llab-quvvatlashi ushbu mexanizm asosida yaratilgan. Kontroller DeferredResultni qaytarganda, "filtr-servlet" zanjirining bajarilishi yakunlanadi va servlet konteyner oqimi bo'shatiladi. Keyinchalik, DeferredResultni o'rnatishda, ASYNC jo'natiladi (shu URL-manzilda), shu vaqt ichida kontroller yana moslashadi, lekin instead of istisno natijaviy DeferredResult qiymati ishlatiladi (negadir kontroller uni qaytib berganday) qayta ishlashni davom ettirish uchun.

Aksincha, Spring WebFlux Servlet API asosida qurilmagan va u asinxron so'rovlarni qayta ishlash kabi xususiyatga muhtoj emas, chunki u asinxronlikni ta'minlaydi. Asinxron qayta ishlash butun framework kontraktlarida o'rnatilgan va so'rovni qayta ishlashning barcha qadamlarida qo'llab-quvvatlanadi.

Dasturlash modeliga ko'ra, ham Spring MVC, ham Spring WebFlux kontroller metodlari uchun qaytish qiymatlari sifatida asinxron va reaktiv turlarni qo'llab-quvvatlaydi. Spring MVC hatto oqimli uzatishni, shu jumladan reaktiv pushbackni qo'llab-quvvatlaydi. Biroq, javobdagi alohida yozuvlar bloklanadi (va alohida oqimda amalga oshiriladi), WebFlux esa bloklanmaydigan kirish-chiqishni talab qiladi va har bir yozuv uchun qo'shimcha oqimga muhtoj emas.

Yana bir asosiy farq shundaki, Spring MVC kontroller metodlari argumentlarida (masalan, @RequestBody, @RequestPart va boshqa) asinxron yoki reaktiv turlarni qo'llab-quvvatlamaydi va model atributlari sifatida asinxron va reaktiv turlar uchun aniq qo'llab-quvvatlash mavjud emas. Spring WebFlux bularning hammasini qo'llab-quvvatlaydi.

HTTP stream uzatish

Bitta asinxron qaytish qiymatlari uchun DeferredResult va Callabledan foydalanishingiz mumkin. Bu qanday qilib bir nechta asinxron qiymatlarni olishingiz va javobga yozishingiz mumkin? Ushbu bo'limda buni qanday qilish mumkinligi haqida ma'lumot berilgan.

Ob'ektlar

Har bir ob'ekt HttpMessageConverter tomonidan seriallanib va javobga yozilib, ResponseBodyEmitter qaytish qiymati orqali ob'ektlar oqimi yaratishingiz mumkin, bu quyidagi misolda ko'rsatilgan:

Java
@GetMapping("/events")
public ResponseBodyEmitter handle() {
    ResponseBodyEmitter emitter = new ResponseBodyEmitter();
    // Jo'natuvchini (emitter) qayerdadir saqlaymiz...
    return emitter;
}
// Boshqa bir oqimda
emitter.send("Hello bir marta");
// va keyin yana
emitter.send("Hello yana");
// va bir paytda tayyor bo'lasiz
emitter.complete();
Kotlin
@GetMapping("/events")
fun handle() = ResponseBodyEmitter().apply {
    // Jo'natuvchini (emitter) qayerdadir saqlaymiz...
}
// Boshqa bir oqimda
emitter.send("Hello bir marta")
// va keyin yana
emitter.send("Hello yana")
// va bir paytda tayyor bo'lasiz
emitter.complete()

Siz ResponseEntityda ResponseBodyEmitterni tanadan foydalangan holda javob statusi va sarlavhalarini sozlashingiz mumkin.

Agar emitter IOException ishlab chiqarsa (masalan, masofaviy mijoz yo'qolgan bo'lsa), ilovalar aloqani tozalash uchun javobgar emas va ular emitter.complete yoki emitter.completeWithErrorni chaqirish kerak emas. Buning o'rniga, servlet konteyneri avtomatik ravishda AsyncListenerdagi xato haqida bildirishnoma boshlaydi, unda Spring MVC completeWithErrorni chaqiradi. Ushbu chaqiruv, o'z navbatida, oxirgi ASYNC jo'natishni ilovaga amalga oshiradi, bu vaqt ichida Spring MVC sozlangan istisno tegishli organlarini chaqiradi va so'rovni tugatadi.

SSE

SseEmitter (ResponseBodyEmitterning bir qismi) Server-Sent Eventsni qo'llab-quvvatlashni taqdim etadi, agar server tomonidan yuborilgan voqealar W3C SSE spetsifikatsiyasiga muvofiq formatlangan bo'lsa. Kontrollerdan SSE oqimini yaratish uchun, SseEmitterni quyidagi misolda ko'rsatilganidek qaytaring:

Java
@GetMapping(path="/events", produces=MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter handle() {
    SseEmitter emitter = new SseEmitter();
    // Jo'natuvchini (emitter) qayerdadir saqlaymiz...
    return emitter;
}
// Boshqa bir oqimda
emitter.send("Hello bir marta");
// va keyin yana
emitter.send("Hello yana");
// va bir paytda tayyor bo'lasiz
emitter.complete();
Kotlin
@GetMapping("/events", produces = [MediaType.TEXT_EVENT_STREAM_VALUE])
fun handle() = SseEmitter().apply {
    // Jo'natuvchini (emitter) qayerdadir saqlaymiz...
}
// Boshqa bir oqimda
emitter.send("Hello bir marta")
// va keyin yana
emitter.send("Hello yana")
// va bir paytda tayyor bo'lasiz
emitter.complete()

SSE brauzerlarga oqim uzatishning asosiy tanlovi bo'lsa-da, shuni ta'kidlash kerakki, Internet Explorer server tomonidan yuborilgan voqealarni (Server-Sent Events) qo'llab-quvvatlamaydi. Springdan WebSocket orqali xabarlar almashinishni va SockJS protokoli orqali yetkazish mexanizmlarining zaxira variantlarini qo'llash imkoniyatini ko'rib chiqing (SSE ni o'z ichiga olgan) turli xil brauzerlarga mos keladi.

Xom ma'lumotlar

Ba'zan xabarlarni aylantirishni aylanib o'tish va javobdagi OutputStreamga to'g'ridan-to'g'ri oqim uzatish foydali bo'lishi mumkin (masalan, fayl yuklashda). Buning uchun StreamingResponseBody qaytish qiymat turidan foydalanishingiz mumkin, bu quyidagi misolda ko'rsatilgan:

Java
@GetMapping("/download")
public StreamingResponseBody handle() {
    return new StreamingResponseBody() {
        @Override
        public void writeTo(OutputStream outputStream) throws IOException {
            // yozish...
        }
    };
}
Kotlin
@GetMapping("/download")
fun handle() = StreamingResponseBody {
    // yozish...
}

Javob statusi va sarlavhalarini sozlash uchun StreamingResponseBodyni ResponseEntityda tanadan foydalangan holda ishlatishingiz mumkin.

Reaktiv turlar

Spring MVC kontrollerda reaktiv mijoz kutubxonalaridan foydalanishni qo'llab-quvvatlaydi. Bunday kutubxonalardan biri WebClient spring-webflux dan va boshqa Spring Data loyihasining reaktiv ma'lumotlar repozitoriylari kabi kutubxonalar. Shu kabi stsenariylarda kontroller metodlaridan reaktiv turlarni qaytarish imkoniga ega bo'lish yaxshi bo'ladi.

Reaktiv qaytish qiymatlari quyidagicha qayta ishlanadi:

  • Yagona qiymatga ega bo'lgan promis DeferredResult dan foydalanganda moslashtiriladi. Misol uchun, Mono (Reactor) yoki Single (RxJava).

  • Bir nechta qiymatli oqim ma'lumotlarini uzatish uchun (masalan, application/x-ndjson yoki text/event-stream) ResponseBodyEmitter yoki SseEmitterdan foydalanganda moslashtiriladi. Bunga misollar Flux (Reactor) yoki Observable (RxJava) bo'lishi mumkin. Dasturlar Flux<ServerSentEvent> yoki Observable<ServerSentEvent>ni ham qaytarishi mumkin.

  • Har qanday boshqa turdagi ma'lumotlar muhitining bir nechta qiymatli oqimi (masalan, application/json) DeferredResult<List<?>>dan foydalanganda moslashtiriladi.

Spring MVC Reactor va RxJava ni spring-coredan ReactiveAdapterRegistry orqali qo'llab-quvvatlaydi, bu esa bir nechta reaktiv kutubxonalar o'rtasida o'tish paytida moslashishga imkon beradi.

Javobda ma'lumotlarni oqim uzatish uchun reaktiv pushback qo'llaniladi, lekin javobga yozish bloklanib qoladi va maxsus oqimdan kelib chiqadigan xizmatning bloklanishidan qochish uchun maxsus TaskExecutor orqali amalga oshiriladi (masalan, WebClientdan qaytarilgan Flux). Bloklangan yozish default SimpleAsyncTaskExecutor orqali amalga oshiriladi, lekin yuklash uchun bu mos emas. Agar siz reaktiv turdagi oqimlardan foydalanishni rejalashtirmoqchi bo'lsangiz, ishlovchidan foydalanishni sozlash uchun MVC konfiguratsiyasi orqali maxsus xizmatchi sozlashingiz kerak.

Aloqa yo'qolishi

Servlet API masofadagi mijozlarni yo'qolganida hech qanday bildirishnoma bermaydi. Shuning uchun agar SseEmitter yoki reaktiv turlarni ishlatsangiz, javob oqimini uzatishda, muntazam ravishda ma'lumotlarni yuborish muhim, chunki agar mijoz uzilib qolsa, yozuv amalga oshirilmaydi. Yuborish bo'sh (faqat izohlar) SSE hodisasi yoki boshqa ma'lumotlar shaklida bo'lishi mumkin, ikkinchi tomon ularni heartbeat xabari sifatida talqin qilishi va e'tiborsiz qoldirishi kerak.

Bundan tashqari, veb-xabarlar almashinuvi uchun yechimlardan foydalangan holda (masalan, STOMP orqali WebSocket yoki SockJS bilan WebSocket) foydalanishingiz mumkin, ular heartbeats xabarlarini yuborish mexanizmiga ega.

Konfiguratsiya

Asinxron so'rovlarni qayta ishlash funktsiyasini servlet konteyner darajasida yoqish kerak. MVC konfiguratsiyasi shuningdek asinxron so'rovlar uchun bir nechta opsiyalarni taqdim etadi.

Servlet konteyneri

Filtr va servletlar deklaratsiyalarida asinxron so'rovlarni qayta ishlashni yoqish uchun to'g'ri o'rnatilishi kerak bo'lgan asyncSupported flagi mavjud. Shuningdek, filtrning ko'rinishlari ASYNC javax.servlet.DispatchType ni qayta ishlash uchun e'lon qilinishi kerak.

Java konfiguratsiyada AbstractAnnotationConfigDispatcherServletInitializerni servlet konteynerini ishga tushirish uchun foydalanganda bu avtomatik amalga oshiriladi.

web.xml konfiguratsiyasida siz <async-supported>true</async-supported>ni DispatcherServlet va Filter deklaratsiyalariga qo'shishingiz mumkin, shuningdek, filtr teginishlariga <dispatcher>ASYNC</dispatcher>ni qo'shishingiz mumkin.

Spring MVC

MVC konfiguratsiyasi asinxron so'rovlarni qayta ishlash bilan bog'liq quyidagi opsiyalarni taqdim etadi:

  • Java konfiguratsiyasi: WebMvcConfigurer uchun configureAsyncSupport qaytarmasidan foydalaning.

  • XML namespace: <mvc:annotation-driven> bo'limida <async-support> elementidan foydalaning.

Quyidagi parametrlarning konfiguratsiyasi mumkin:

  • Asinxron so'rovlar uchun default kutish vaqti qiymati, agar o'rnatilmagan bo'lsa, servlet konteynerining asosiy qismi bog'liq.

  • Reaktiv turlarni ishlatganda ma'lumotlarni oqim uzatish uchun bloklanish yozuvlari uchun foydalaniladigan AsyncTaskExecutor va kontroller metodlaridan qaytarilgan Callable ni bajarish. Agar siz reaktiv turdagi bo'lgan oqimlardan foydalanmoqchi bo'lsangiz yoki Callable qaytargan kontroller metodlari bo'lsa, bu xususiyatni sozlash tavsiya etiladi, chunki SimpleAsyncTaskExecutor default qilingan.

  • Implementatsiyalar DeferredResultProcessingInterceptor va implementatsiyalar CallengeProcessingInterceptor.

Shuni esda tutingki, DeferredResult, ResponseBodyEmitter va SseEmitter uchun default kutish vaqtini ham o'rnatishingiz mumkin. Callable uchun kutish vaqtini belgilash uchun WebAsyncTaskni ishlatishingiz mumkin.

Izohlar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION