Spring MVC Internals
Bu modülde bir HTTP isteğinin controller metoduna nasıl ulaştığını, controller’dan dönen cevabın JSON response’a nasıl dönüştüğünü ve Spring MVC’nin bu akışı hangi parçalarla yönettiğini göreceğiz. Amaç sadece @GetMapping kullanmak değil; request geldiğinde arkada çalışan zinciri okuyabilmek.
Controller metodunun arkasındaki gerçek web akışını parçalarına ayırabileceksin.
Request akışını okuyacaksın
Bir isteğin DispatcherServlet üzerinden nasıl doğru controller metoduna yönlendirildiğini anlayacaksın.
JSON dönüşümünü göreceksin
Request body’nin Java objesine, Java objesinin JSON response’a nasıl dönüştüğünü öğreneceksin.
Custom argument resolver yazacaksın
MiniBank’ta CurrentUser bilgisini controller parametresine otomatik vereceksin.
Önce kelimeleri sadeleştirelim.
Spring MVC anlatımında çok fazla teknik terim var. Bu terimleri baştan sadeleştirirsek, akış daha rahat oturur.
DispatcherServlet
Basit: Spring MVC’nin ana giriş kapısıdır.
Neden önemli? HTTP request önce buraya gelir; sonra doğru controller metoduna gönderilir.
Controller
Basit: HTTP isteğini karşılayan sınıftır.
Neden önemli? API endpoint’lerimizin dış dünyaya açılan yüzüdür.
Handler
Basit: İsteği işleyecek controller metodu gibi düşünebilirsin.
Neden önemli? Spring MVC request için uygun handler’ı bulmaya çalışır.
HandlerMapping
Basit: Hangi URL hangi metoda gider, bunu bulur.
Neden önemli? /accounts/{iban} isteğinin hangi metoda düşeceği burada çözülür.
HandlerAdapter
Basit: Bulunan handler’ı çalıştıran adaptördür.
Neden önemli? Controller metodunun parametrelerini hazırlar ve metodu çağırır.
HttpMessageConverter
Basit: JSON ↔ Java obje dönüşümünü yapar.
Neden önemli? @RequestBody ve @ResponseBody davranışının merkezindedir.
Argument Resolver
Basit: Controller method parametrelerinin nasıl üretileceğini belirler.
Neden önemli? @PathVariable, @RequestBody, Principal gibi parametreler bu mantıkla çözülür.
ModelAndView
Basit: Klasik MVC’de model + view bilgisidir.
Neden önemli? REST API’de daha az görünür, ama Spring MVC’nin tarihsel ve mimari temelinde vardır.
Filter vs Interceptor
Basit: İkisi de request akışına araya girer.
Neden önemli? Filter servlet seviyesinde, Interceptor Spring MVC seviyesinde çalışır.
Bir request controller metoduna nasıl ulaşır?
Spring MVC’yi anlamanın en iyi yolu şu sorudan başlamak: Tarayıcıdan veya Postman’den bir istek gönderdim. Bu istek benim controller metoduma kadar hangi adımlardan geçiyor?
4.1 Spring MVC nedir?
Spring MVC, HTTP isteklerini Java metodlarına bağlayan web framework katmanıdır. Biz çoğu zaman @RestController, @GetMapping, @PostMapping, @RequestBody gibi annotation’ları görürüz. Bunlar kullanım tarafıdır. Bu modülde kullanımın arkasındaki mekanizmaya bakacağız.
En basit haliyle Spring MVC şunu yapar: Gelen isteğin URL ve HTTP method bilgisine bakar, uygun controller methodunu bulur, method parametrelerini hazırlar, methodu çağırır ve dönen cevabı HTTP response’a çevirir.
Controller metodu doğrudan çağrılan basit bir Java metodu gibi görünür; ama aslında öncesinde URL eşleştirme, parametre çözme, body parse etme, validation ve response dönüştürme gibi birçok adım çalışır.
4.2 DispatcherServlet — ana giriş kapısı
DispatcherServlet, Spring MVC’nin merkezindeki servlet’tir. Servlet container, yani çoğunlukla embedded Tomcat, HTTP isteğini alır ve Spring MVC’ye teslim eder. Spring MVC tarafında bu isteği ilk karşılayan parça DispatcherServlet olur.
Bu sınıfın görevini bir resepsiyon görevlisi gibi düşünebilirsin. İstek gelir, resepsiyon isteğin ne olduğunu anlamaya çalışır, doğru kişiye yönlendirir, cevap gelince de client’a geri döndürür. Kendisi business logic yazmaz; koordinasyon yapar.
DispatcherServlet ne yapar?
- Request’i Spring MVC akışına alır.
- Uygun handler’ı bulmak için HandlerMapping’lere sorar.
- Handler’ı çalıştırmak için uygun HandlerAdapter’ı kullanır.
- Exception oluşursa exception resolver zincirine devreder.
- Response üretildikten sonra sonucu client’a döndürür.
Spring Boot kullandığımızda DispatcherServlet bean’ini genellikle biz oluşturmayız. spring-boot-starter-web dependency’si geldiğinde Spring Boot MVC auto-configuration üzerinden gerekli web altyapısını hazırlar. Modül 01’de gördüğümüz auto-configuration burada gerçek karşılığını bulur.
4.3 HandlerMapping — hangi URL hangi metoda gider?
Bir request geldiğinde Spring’in ilk kritik sorusu şudur: Bu URL ve HTTP method hangi controller metoduna ait? İşte HandlerMapping bu soruyu cevaplar.
Mesela aşağıdaki controller metodunu düşünelim:
@RestController
@RequestMapping("/api/accounts")
public class AccountController {
@GetMapping("/{iban}")
public AccountResponse getAccount(@PathVariable String iban) {
return accountService.getAccount(iban);
}
}
GET /api/accounts/TR001 isteği geldiğinde Spring MVC bu mapping bilgisini önceden çıkarmış olur. Runtime’da URL ve HTTP method bilgisine bakarak bu request’in getAccount metoduna gitmesi gerektiğini bulur.
Mapping çakışmaları production’da can yakabilir. Aynı path’i birden fazla controller’ın karşılamaya çalışması, belirsiz mapping hatalarına yol açar. Büyük projelerde URL tasarımı, controller paketlemesi ve endpoint standardı bu yüzden önemlidir.
4.4 HandlerAdapter — bulunan handler nasıl çalıştırılır?
HandlerMapping doğru handler’ı bulur, ama onu doğrudan çalıştırmaz. Çünkü Spring MVC’de farklı handler türleri olabilir. Controller methodu, functional endpoint, eski tip controller yapıları gibi farklı modelleri çalıştırmak için araya HandlerAdapter girer.
Günlük Spring Boot REST API geliştirmesinde en çok gördüğümüz model annotation tabanlı controller methodlarıdır. Bu durumda Spring, controller metodunun parametrelerini tek tek çözmek zorundadır. @PathVariable, @RequestParam, @RequestBody, @RequestHeader gibi parametrelerin her biri farklı kaynaklardan gelir.
@PathVariable
URL içindeki değişken parçadan değer alır. Örnek: /{iban}
@RequestParam
Query string üzerinden değer alır. Örnek: ?page=0
@RequestBody
HTTP body içindeki JSON verisini Java objesine çevirir.
@RequestHeader
HTTP header içinden değer okur. Örnek: X-Correlation-Id
4.5 HttpMessageConverter — JSON nerede devreye girer?
Spring MVC’de request body’nin Java objesine dönüşmesi ve Java objesinin JSON response’a dönüşmesi HttpMessageConverter mekanizmasıyla yapılır. REST API geliştirirken en sık kullanılan converter genellikle Jackson tabanlı JSON converter’dır.
Örneğin client aşağıdaki JSON’u gönderir:
{
"iban": "TR001",
"initialBalance": 1000
}
Controller metodunda ise bunu Java objesi olarak görürüz:
@PostMapping
public ResponseEntity<AccountResponse> createAccount(
@RequestBody AccountCreateRequest request) {
return ResponseEntity.ok(accountService.createAccount(request));
}
Bu dönüşümün manuel olarak bizim tarafımızdan yapılmaması büyük kolaylıktır. Ama bu kolaylık görünmez olduğunda bazı problemleri anlamak zorlaşır: yanlış tarih formatı, beklenmeyen enum değeri, eksik field, büyük/küçük harf farkı, response’ta istenmeyen alanların çıkması gibi konular çoğu zaman JSON dönüşüm katmanıyla ilgilidir.
MiniBank’ta Account, Customer ve Transfer API’leri yazarken request/response DTO’larını JSON’a çevirmeyi Spring MVC ve Jackson üstlenecek. Biz bu modülde dönüşümün nerede gerçekleştiğini netleştiriyoruz; sonraki modülde DTO ve validation tarafını büyüteceğiz.
4.6 Argument Resolver — controller parametresini kim hazırlıyor?
Spring MVC’nin en güçlü ama çoğu zaman fark edilmeyen parçalarından biri HandlerMethodArgumentResolver mekanizmasıdır. Controller metoduna yazdığımız parametrelerin nasıl üretileceğini bu mekanizma belirler.
Örneğin @PathVariable String iban parametresi URL’den gelir. @RequestBody AccountCreateRequest request parametresi JSON body’den gelir. Peki kendi özel parametremizi üretmek istersek ne olur?
MiniBank’ta her request’te header’dan gelen kullanıcı bilgisini controller metodunda doğrudan almak isteyebiliriz:
@GetMapping("/me/accounts")
public List<AccountResponse> myAccounts(CurrentUser currentUser) {
return accountQueryService.findAccountsOf(currentUser.customerNo());
}
Burada CurrentUser nesnesini controller içinde manuel header okuyarak oluşturmak istemiyoruz. Bu iş her controller’da tekrarlanırsa kod kirlenir. Bunun yerine Spring MVC’ye şunu öğretiriz: “Eğer controller metodunda CurrentUser parametresi görürsen, bunu request header’dan üret.”
MiniBank için custom argument resolver yazacağız. Böylece web layer’da tekrarlayan header okuma kodunu merkezi hale getireceğiz.
MiniBank Case 04 — CurrentUser Argument Resolver
Bu case’de MiniBank controller’larında tekrar eden “header’dan kullanıcı bilgisi okuma” ihtiyacını Spring MVC’nin argument resolver mekanizmasıyla merkezi hale getireceğiz. Görev listesi, doğrulama komutları, kod adımları ve kabul kriterleri Lab Dashboard'da.
Lab sırasında geride kalırsan lab-04-complete branch'ine geçip herkesle aynı checkpoint'ten devam edebilirsin.
Bu modülden akılda kalması gerekenler
Request önce DispatcherServlet’e gelir; sonra mapping, adapter, argument resolver ve converter gibi parçalar devreye girer.
Parametrelerin hazırlanması, body dönüşümü ve response üretimi Spring MVC altyapısı tarafından yönetilir.
JSON request ve response dönüşümünün büyük kısmını bizim yerimize yapar.
Current user, tenant, correlation id gibi request context bilgileri merkezi olarak üretilebilir.
DTO, validation, exception handling ve security konuları bu MVC akışının üzerine oturur.
5 kısa kontrol sorusu
1. DispatcherServlet’in temel görevi nedir?
Spring MVC’ye gelen request’i karşılamak, doğru handler’ı buldurmak, handler’ı çalıştırmak ve response akışını koordine etmektir.
2. HandlerMapping hangi soruyu cevaplar?
“Bu URL ve HTTP method hangi controller metoduna gitmeli?” sorusunu cevaplar.
3. HttpMessageConverter ne işe yarar?
HTTP body ile Java objeleri arasında dönüşüm yapar. REST API’de JSON ↔ Java obje dönüşümü çoğunlukla bu mekanizmayla yapılır.
4. Argument Resolver neden kullanılır?
Controller method parametrelerinin nasıl oluşturulacağını merkezi hale getirmek için kullanılır. Örneğin CurrentUser parametresini header’dan üretmek için.
5. Custom argument resolver yazmak controller kodunu nasıl iyileştirir?
Header okuma, request context oluşturma veya tekrar eden parametre hazırlama kodlarını controller’dan çıkarır; daha temiz ve tutarlı bir web layer sağlar.