Spring MVC — Bir İstek Controller'a Nasıl Ulaşır?
Önce şunu oku: IoC ve Dependency Injection
30 saniyede özet
Her HTTP isteği önce filter'lardan geçer, sonra tek kapıdan, DispatcherServlet'ten girer. İçeride doğru metot bulunur, JSON nesneye çevrilir ve cevap yine JSON'a yazılır. Hatanın nerede çıktığı, onu kimin göreceğini belirler.
Büyük bir şirkete mektup gönderdiğini düşün. Mektup doğrudan ilgili kişinin masasına düşmez; önce kapıdan, sonra resepsiyondan geçer.
Adım adım oku
- Kapıdaki görevli mektubu kontrol eder ve içeri alır.
- Resepsiyon mektubun hangi masaya gideceğini bulur.
- Sekreter not alır, masadaki memur işi yapar.
- Tercüman cevabı gönderenin okuyacağı dile çevirir.
-
Bayt: Denetim ekibi soruyor: bir havale isteği uygulamaya girince tam olarak nerelerden geçiyor?
-
Sen: Controller'a gidiyor işte. Başka nereye gidecek?
-
Bayt: Controller'dan önce ve sonra epey durak var bence...
-
Bayt: Bir mektup gibi takip edelim. Durak durak.
Tek kapı: DispatcherServlet
Spring MVC uygulamasında bütün istekler aynı kapıdan girer: DispatcherServletSpring MVC uygulamasının tek giriş kapısı. Gelen her isteği handler mapping ile doğru controller metoduna yollar, dönen cevabı istemciye yazar ve hataları hata işleyicilere verir.Sözlükte gör →. Kendisi iş yapmaz; isteği doğru yere yollar.
Önce handler mapping’e sorar: “POST /transfers kimin işi?” Cevap bir controller metodudur. DispatcherServlet o metodu çağırır, dönen cevabı da istemciye geri yazar.
istek → filter → filter → DispatcherServlet → handler mapping → interceptor → controllercevap ← filter ← filter ← DispatcherServlet ← JSON'a çevir ← interceptor ←Kafam karıştı, daha basit anlat
Binaya tek kapıdan girilir. Resepsiyon mektubun hangi masaya gideceğini bulur ve oraya götürür.
Spring MVC uygulamasına gelen bir HTTP isteğini doğru controller metoduna kim yönlendirir?
Kapının dışında, kapının içinde
Kapının dışında servlet filter’ları durur. Filter her isteği görür, ama hangi controller’ın çalışacağını bilmez; o karar henüz verilmedi. Spring Security de bir filter zinciridir.
İçeride ise HandlerInterceptorDispatcherServlet içinde, controller metodundan önce ve sonra çalışan kanca. Filter'dan farkı, hangi metodun çalışacağını bilmesidir.Sözlükte gör → çalışır. Handler mapping’den sonra devreye girdiği için hangi metodun çağrılacağını bilir. Üç anı vardır: preHandle metottan önce, postHandle metot başarıyla dönünce, afterCompletion her şey bitince.
Audit kaydını bir HandlerInterceptor'ın preHandle metodunda tutuyorsun. Token'ı olmayan biri istek atıyor ve Spring Security 401 dönüyor. Bu istek audit kaydında görünür mü? Cevabı göster
Görünmez. Spring Security bir filter; DispatcherServlet’ten önce çalışır ve isteği kapıda geri çevirir. Interceptor binanın içindedir, kapıda dönen mektubu hiç görmez.
Kural basit. Her isteği görmesi gereken iş filter’a gider: correlation id, token kontrolü. Hangi metodun çalıştığını bilmesi gereken iş interceptor’a gider: audit, metoda özel ek kontrol.
Kafam karıştı, daha basit anlat
Filter kapıdaki görevli: herkesi görür, kimin nereye gideceğini bilmez. Interceptor içerideki sekreter: yalnızca içeri girenleri görür ama hangi masaya gittiklerini bilir.
Her isteğe bir correlation id vermek ve bunu loglara yazmak istiyorsun. Security'nin 401 ile geri çevirdiği istekler de dahil olmalı. Bu kodu nereye koyarsın?
Her işi yapılacağı yere göre ayır.
Satır satır: JSON’dan nesneye, nesneden JSON’a
Controller JSON görmez, Java nesnesi görür. Aradaki çeviriyi bir HttpMessageConverterHTTP gövdesini Java nesnesine, Java nesnesini de gövdeye çeviren bileşen. @RequestBody ve @RestController dönüşleri bunu kullanır; JSON için varsayılanı Jackson'dır.Sözlükte gör → yapar; Spring Boot’ta varsayılanı Jackson kullanır. Aynı çevirmen dönüşte nesneyi JSON’a yazar.
Bir havale isteğini, bakiye yetmediği gün izleyelim.
Bir havale isteği ve bir hata
@RestController@RequestMapping("/transfers")class TransferController { private final TransferService transfers; // comes from the constructor @PostMapping ResponseEntity<TransferResponse> create(@Valid @RequestBody TransferRequest request) { TransferResponse done = transfers.send(request); return ResponseEntity.status(HttpStatus.CREATED).body(done); }} @RestControllerAdviceclass ApiErrors { @ExceptionHandler(InsufficientFundsException.class) ProblemDetail insufficient(InsufficientFundsException e) { return ProblemDetail.forStatusAndDetail(HttpStatus.UNPROCESSABLE_ENTITY, "Bakiye yetersiz"); }}Debug
DispatcherServlet Handler mapping, POST /transfers için bu metodu buldu.
- istek
- = POST /transfers
Sol/sağ ok tuşlarıyla da gezebilirsin.
Hatayı yakalayan sınıf bir @RestControllerAdviceBütün controller'lardan çıkan istisnaları tek yerde HTTP cevabına çeviren sınıf. İçindeki @ExceptionHandler metotları hangi istisnanın hangi koda eşleneceğini söyler.Sözlükte gör →. Ayrıntıları Validation ve hata yönetimi dersinde gördün; burada önemli olan yeri: DispatcherServlet’in içi.
GET /balance çağrıldı. Konsolda ne yazar?
Bir @RestController metodu TransferResponse nesnesi döndürüyor. İstemciye giden JSON'u kim üretir?
Kendin gör
Bir havale isteği yola çıkıyor. Neyin ters gideceğini ve advice olup olmadığını sen seçiyorsun.
Bir istek controller'a nasıl ulaşır?
Tohum 1- Filter: correlation idbekliyor
- Filter: Spring Securitybekliyor
- DispatcherServletbekliyor
- Handler mappingiçeride · bekliyor
- Interceptor: preHandleiçeride · bekliyor
- JSON → nesne, @Validiçeride · bekliyor
- Controlleriçeride · bekliyor
- Nesne → JSONiçeride · bekliyor
- Interceptor: afterCompletioniçeride · bekliyor
- Hata işleyicibekliyor
Oynat ya da adımla.
Şu an ne oldu?
Bir havale isteği yola çıktı
POST /transfers geldi. Hangi duraklardan geçecek ve cevabı kim yazacak?
Görevler0/3
Bir isteğin DispatcherServlet'e hiç ulaşmadan cevap almasını sağlaaçık
İpucu
Kapıdaki görevli token sorar.
Bir iş kuralı hatası 422 ve ProblemDetail ile dönsünaçık
İpucu
Bakiye yetmesin, advice açık olsun.
Advice açıkken bile 500 dönen bir hata bulaçık
İpucu
Advice yalnızca DispatcherServlet'in içini görür.
Olay günlüğü (0)
Henüz olay yok. Oynat veya adımla.
- Varsayılanı oynat. Geçerli istek bütün duraklardan geçer ve 201 alır. Audit kaydı var.
- “Token yok” seç. Yolculuk ikinci durakta biter. Audit kaydı yok, çünkü interceptor isteği hiç görmedi.
- “Bakiye yetersiz” seç ve advice’ı kapatıp aç. Kapalıyken 500, açıkken 422 ve ProblemDetail.
- “Bizim filtremiz hata fırlatıyor” seç. Advice açıkken bile 500. Neden?
Uygulamada bütün hataları ProblemDetail'e çeviren bir @RestControllerAdvice var. Hangisi yine de Boot'un varsayılan 500 cevabıyla döner?
Tuzaklar
Filter’daki hatayı advice görmez. Filter DispatcherServlet’ten önce çalışır. Orada fırlayan hata advice’a hiç ulaşmaz ve Boot’un varsayılan 500 cevabına düşer.
postHandle, JSON cevabını değiştiremez. @RestController’da gövde, metot döner dönmez yazılır. postHandle geldiğinde cevap yola çıkmıştır; başlık eklemek için filter ya da ResponseBodyAdvice kullanılır.
Filter’da gövdeyi okumak onu tüketir. İstek gövdesi bir kez okunabilen bir akıştır. Filter onu okursa controller boş bulur; ContentCachingRequestWrapper bir kopya tutar.
Kafam karıştı, daha basit anlat
Kapıdaki hatayı içerideki kimse görmez. Cevap bir kez gönderildikten sonra değiştirilemez. Mektubu kapıda açıp okursan içeriye boş zarf gider.
Bu filtre eklendikten sonra POST /transfers istekleri 400 dönmeye başladı: "Required request body is missing". Sorun hangi satırda?
Derinleş · Bankacılık API'si: kapıda kimlik, içeride audit, tek yerde hata 5 dosya · ~94 satır · ilk okumada atlayabilirsin
Kendini sına
Önce hızlı bir ısınma: puan yok, kayıt yok. Sonra asıl sorular.
Spring Security, DispatcherServlet'ten önce çalışan bir filter zinciridir.
Bir HandlerInterceptor'ın postHandle metodunda response'a X-Processing-Time başlığı ekliyorsun. @RestController cevaplarında başlık istemciye hiç ulaşmıyor. Neden?
Aklında kalacak üç şey
- 1 Bütün istekler tek kapıdan, DispatcherServlet'ten girer. Handler mapping doğru metodu bulur, HttpMessageConverter JSON'u nesneye ve nesneyi JSON'a çevirir.
- 2 Filter kapının dışında durur ve her isteği görür; Spring Security de bir filter zinciridir. Interceptor içeridedir ve hangi metodun çalışacağını bilir.
- 3 @RestControllerAdvice yalnızca DispatcherServlet'in içinde çıkan hataları görür. Filter'da çıkan hata ona hiç ulaşmaz.
5 kart sonraki derste seni bekliyor