Gün 2 / Web Layer & Runtime / Modül 04 — Spring MVC Internals
Ana Portal
Modül 04 Gün 2 Web Layer ~75 dk

Spring MVC Internals

Bir HTTP isteği controller metoduna nasıl ulaşır? Dönen cevap nasıl JSON response’a çevrilir? Spring MVC bu akışı hangi parçalarla yönetir? Bu modül bu üç soruya odaklanıyor. Amaç sadece @GetMapping kullanmak değil; request geldiğinde arkada çalışan zinciri okuyabilmek.

Anlatım
45 dk
MiniBank Case
25 dk
Kapanış
5 dk
Checkpoint
lab-04

Controller metodunun arkasındaki gerçek web akışını parçalarına ayırabileceksin.

1

Request akışını okuyacaksın

Bir isteğin DispatcherServlet üzerinden nasıl doğru controller metoduna yönlendirildiğini anlayacaksın.

2

JSON dönüşümünü göreceksin

Request body’nin Java objesine, Java objesinin JSON response’a nasıl dönüştüğünü öğreneceksin.

3

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.

01
Request gelir
Client HTTP isteği gönderir.
02
DispatcherServlet alır
Spring MVC’nin merkezi kapısıdır.
03
Handler bulunur
URL hangi controller metoduna ait?
04
Parametreler hazırlanır
Path, query, body, header okunur.
05
Controller çalışır
Business akış service katmanına gider.
06
Response döner
Java obje JSON’a çevrilir.
Mental model

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:

AccountController.java
@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.

Üretim notu

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:

HTTP Request Body
{
  "iban": "TR001",
  "initialBalance": 1000
}

Controller metodunda ise bunu Java objesi olarak görürüz:

AccountController.java
@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 bağlantısı

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:

Hedef kullanım
@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.”

Bu modülün case fikri

MiniBank için custom argument resolver yazacağız. Böylece web layer’da tekrarlayan header okuma kodunu merkezi hale getireceğiz.

Bu CurrentUser deseni şimdilik header’dan okuyor; Modül 08’de security devreye girdiğinde aynı parametre JWT’den doğrulanmış kullanıcıyla beslenecek, yani controller kodu hiç değişmeden gerçek kimliğe bağlanacak.

4.7 Filter, Interceptor ve Argument Resolver: request akışında kim nerede?

Şimdiye kadar baktığımız parçalar (HandlerMapping, HandlerAdapter, converter, argument resolver) hep DispatcherServlet’in içinde çalıştı. Ama request controller’a ulaşana kadar iki ek katmandan daha geçer: servlet Filter ve HandlerInterceptor. Mini Sözlük’te bunlara değindik; burada akıştaki yerlerini netleştiriyoruz.

Üçünün arasındaki temel fark, request hattındaki konumlarıdır. Filter en dışta, servlet seviyesinde durur ve DispatcherServlet’i bile sarar. Interceptor ise DispatcherServlet’in içinde, ama handler çalışmadan hemen önce ve sonra devreye girer. Argument resolver ise en içeride, tam olarak handler parametreleri hazırlanırken çalışır.

01
Servlet Filter
DispatcherServlet’ten önce, en dış katman. Tüm request/response’u sarar. MiniBank’ta CorrelationIdFilter burada çalışır.
02
HandlerInterceptor
DispatcherServlet içinde, handler bulunduktan sonra ama çalışmadan önce (preHandle) ve sonra (postHandle).
03
Argument Resolver
Handler parametreleri hazırlanırken. CurrentUser nesnesi tam burada üretilir.
04
Controller metodu
Parametreler hazır olunca handler çalışır, business akış başlar.

MiniBank’ta her ikisinin de gerçek örneği var. CorrelationIdFilter bir servlet Filter’dır; DispatcherServlet’e gelmeden önce her request’e bir correlation id atar. CurrentUser ise argument resolver ile handler parametresi olarak üretilir:

CorrelationIdFilter.java
@Component
public class CorrelationIdFilter extends OncePerRequestFilter {

    public static final String CORRELATION_ID_HEADER = "X-Correlation-Id";

    @Override
    protected void doFilterInternal(
            HttpServletRequest request,
            HttpServletResponse response,
            FilterChain filterChain) throws ServletException, IOException {
        // DispatcherServlet'ten ÖNCE: en dış katman
        String correlationId = resolveCorrelationId(request);
        response.setHeader(CORRELATION_ID_HEADER, correlationId);
        filterChain.doFilter(request, response);
    }
}
Hangisini ne zaman seçersin?

Tüm request/response’a, hatta static resource ve hata sayfalarına dokunması gereken altyapı işleri (correlation id, gzip, CORS, request/response loglama) için Filter kullan. Sadece controller handler’larına özgü, mapping bilgisine veya handler’a erişim isteyen işler (yetki ön kontrolü, handler’a özel metrik) için HandlerInterceptor kullan. Controller parametresini üretmek istiyorsan doğru araç argument resolver’dır.

Buraya kadar konuştuğumuz akış REST tarafıdır: handler bir DTO döner, @ResponseBody ve HttpMessageConverter bunu JSON’a çevirir (4.5). Klasik MVC’de ise handler bir ModelAndView döner; yani hangi view’ın render edileceği ve hangi model verisinin kullanılacağı bilgisini taşır. Sözlükte gördüğümüz ModelAndView tam olarak burada yaşar. MiniBank REST API olduğu için biz ModelAndView yerine converter yolunu kullanıyoruz, ama ikisi de aynı DispatcherServlet akışının iki ucudur.

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.

Başlangıç
lab-04-start
Tamamlanmış
lab-04-complete
Checkpoint kuralı

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

1
Spring MVC request akışı merkezi bir zincirdir.

Request önce DispatcherServlet’e gelir; sonra mapping, adapter, argument resolver ve converter gibi parçalar devreye girer.

2
Controller metodu tek başına çalışmaz.

Parametrelerin hazırlanması, body dönüşümü ve response üretimi Spring MVC altyapısı tarafından yönetilir.

3
HttpMessageConverter REST API’nin görünmeyen kahramanıdır.

JSON request ve response dönüşümünün büyük kısmını bizim yerimize yapar.

4
Argument resolver tekrar eden controller kodunu azaltır.

Current user, tenant, correlation id gibi request context bilgileri merkezi olarak üretilebilir.

5
Web layer tasarımı sonraki modüllerin temelidir.

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.