Spring Boot Internal Mechanics
Bu modülde Spring Boot uygulaması başlarken içeride neler olduğunu temelden kuracağız. Önce “Spring Boot neyi bizim yerimize yapıyor?” sorusunu netleştireceğiz; sonra component scanning, ApplicationContext, bean lifecycle ve constructor injection mantığını MiniBank üzerinde gerçek koda dönüştüreceğiz.
Mini sözlük: Aynı dili konuşalım
Spring anlatımında bazı kelimeler çok sık geçer. Bu kelimeleri en başta sadeleştirirsek, birazdan göreceğimiz iç mekanikler çok daha rahat oturur.
Bean
Spring tarafından oluşturulan ve yönetilen Java objesidir. Önemlidir çünkü Spring sadece bildiği bean’leri enjekte eder, lifecycle’a sokar ve gerektiğinde proxy ile sarar.
Dependency
Bir class’ın çalışmak için ihtiyaç duyduğu başka objedir. Örneğin AccountService, hesap verisine ulaşmak için AccountRepository dependency’sine ihtiyaç duyabilir.
Container
Bean’leri oluşturan, saklayan, birbirine bağlayan Spring çalışma ortamıdır. Spring Boot uygulamasında bu rolün ana karşılığı çoğunlukla ApplicationContexttir.
ApplicationContext
Spring’in runtime merkezidir. Bean tanımlarını, bean instance’larını, environment bilgilerini, event mekanizmasını ve lifecycle sürecini yönetir.
Classpath
Uygulama çalışırken JVM’in görebildiği class ve library alanıdır. Spring Boot, auto configuration kararlarının önemli bir kısmını “classpath’te ne var?” sorusuyla verir.
Starter Dependency
Bir özellik ailesini tek dependency ile projeye eklememizi sağlar. spring-boot-starter-web eklediğinde web, JSON ve embedded server tarafındaki birçok dependency birlikte gelir.
Auto Configuration
Spring Boot’un uygun koşullar oluştuğunda default bean’leri bizim yerimize hazırlamasıdır. Hız kazandırır ama nelerin otomatik geldiğini bilmek gerekir.
Component Scan
Spring’in belirli paketleri tarayıp @Service, @Repository, @Controller gibi class’ları bean olarak keşfetmesidir.
Lifecycle
Bir bean’in oluşturulma, dependency alma, initialize olma, kullanıma hazır hale gelme ve kapanırken temizlenme sürecidir.
Injection
Bir dependency’nin class içine dışarıdan verilmesidir. Bu eğitimde varsayılan tercihimiz constructor injection olacak çünkü dependency’leri açık, test edilebilir ve güvenli yapar.
Önce büyük resmi kuralım
Spring Boot’un temel vaadi şudur: “Sen business koduna odaklan, ben uygulamayı ayağa kaldırmak için gereken standart parçaları hazırlarım.”
Bir web uygulaması yazmak aslında sadece controller yazmak değildir. HTTP request’i karşılayacak bir altyapı, request’i doğru method’a yönlendirecek bir mapping sistemi, JSON’u Java objesine çevirecek converter’lar, validation mekanizması, hata cevabı üretimi, embedded server, configuration okuma, dependency injection, bean lifecycle ve daha birçok parça gerekir. Spring Boot bu parçaların çoğunu yaygın ve güvenli default’larla hazırlar.
Başlangıç seviyesinde bu “kolaylık” olarak görülür. Senior seviyede ise mesele biraz değişir: Spring Boot’un bizim yerimize ne oluşturduğunu, neden oluşturduğunu, ne zaman oluşturmadığını ve gerektiğinde nasıl override edeceğimizi bilmemiz gerekir. Çünkü production problemleri genellikle “kod yazmadığım halde bir şey devreye girdi” veya “beklediğim bean neden oluşmadı?” sorularıyla başlar.
Auto Configuration nedir?
Spring Boot’un “sen yazmadan ben default bean’i oluştururum” mekanizmasıdır.
En basit haliyle şöyle düşünebiliriz: Sen projeye bir özellik eklersin, Spring Boot da o özelliğin çalışması için genelde gereken altyapı bean’lerini hazırlar. Örneğin projeye spring-boot-starter-web eklediğinde Spring Boot “bu uygulama HTTP request alacak” sonucuna varır. Bu durumda embedded Tomcat, DispatcherServlet, JSON dönüşümü için Jackson, controller method mapping altyapısı ve default hata yönetimi gibi parçaları sen tek tek tanımlamadan kurar.
Bu mekanizmanın çözdüğü problem tekrar eden konfigürasyondur. Spring Boot öncesinde her projede benzer altyapı ayarları tekrar tekrar yazılırdı. Bu hem zaman kaybettirir hem de ekipler arasında tutarsızlık oluştururdu. Spring Boot burada “yaygın senaryolar için mantıklı default’ları ben sağlayayım, sen sadece farklı davranmasını istediğin yerleri değiştir” yaklaşımını kullanır.
Projeyi hızlı ayağa kaldırır, standart davranış sağlar ve geliştiricinin business koduna odaklanmasını kolaylaştırır. Ama aynı zamanda görünmeyen davranış ürettiği için, neyin otomatik geldiğini okuyabilmek gerekir.
spring-boot-starter-web eklenince neler olabilir?
Embedded Server
Uygulama harici Tomcat kurmadan kendi içinde web server ile ayağa kalkabilir.
DispatcherServlet
Gelen HTTP request’leri Spring MVC akışına sokan ana servlet hazırlanır.
Jackson ObjectMapper
JSON request/response dönüşümleri için default mapper oluşturulur.
HandlerMapping
URL ve HTTP method bilgisi doğru controller method’una bağlanır.
MessageConverter
Java objesi JSON’a, JSON da Java objesine çevrilir.
Error Handling
Default hata cevapları ve temel error endpoint davranışı hazır gelir.
Bir tık derin: Spring Boot karar verirken neye bakar?
Auto configuration rastgele çalışmaz. Spring Boot, bazı koşulları değerlendirerek hangi configuration class’larının aktif olacağına karar verir. Bu sayede hem default davranış sağlanır hem de developer’ın açıkça yazdığı bean’ler ezilmez.
@ConditionalOnClass
Classpath’te belirli bir class varsa ilgili configuration devreye girer. Örneğin web class’ları varsa web auto configuration aday olur.
@ConditionalOnMissingBean
Uygulamada aynı tipte bean yoksa default bean oluşturulur. Developer kendi bean’ini yazdıysa Spring Boot geri çekilir.
@ConditionalOnProperty
Belirli bir property açık veya kapalıysa configuration çalışır. Böylece davranış application.yml üzerinden yönetilebilir.
@Bean
@ConditionalOnMissingBean
public ObjectMapper objectMapper() {
return new ObjectMapper();
}
// Anlamı:
// Uygulamada ObjectMapper bean’i yoksa default bir ObjectMapper oluştur.
// Ama developer kendi ObjectMapper bean’ini yazdıysa ona dokunma.Auto configuration çok faydalıdır ama görünmez davranış üretir. Örneğin projeye spring-boot-starter-security eklemek, herhangi bir controller kodunu değiştirmeden endpoint’lerin güvenlik filtresinden geçmesine neden olabilir. Bu yüzden senior developer için önemli olan sadece “starter eklemek” değil, starter’ın hangi auto configuration’ları tetiklediğini anlayabilmektir.
MiniBank bağlantısı
MiniBank geliştikçe Spring Boot’un default MVC, JSON, validation, JPA, security ve actuator davranışlarından faydalanacağız. İlerleyen aşamalarda ortak audit/logging/tracing gibi konular için kendi MiniBank starter mantığımızı da düşünebiliriz. Bu modülde amaç bu mekanizmanın temelini okumayı öğrenmek.
Component Scanning nasıl çalışır?
Component scanning, Spring’in belirli paketleri tarayıp annotation’lı class’ları bean olarak keşfetmesidir.
Normal Java’da bir class yazmak, o class’ın uygulama tarafından otomatik kullanılacağı anlamına gelmez. Bir objenin oluşması için ya sen new ile oluşturursun ya da bir framework onu oluşturur. Spring tarafında @Service, @Repository, @Controller, @Configuration gibi annotation’lar Spring’e “bu class uygulama context’inde yönetilmeli” sinyali verir.
@SpringBootApplication içinde @ComponentScan vardır. Varsayılan davranış şudur: application class’ın bulunduğu paketten başlar ve alt paketleri tarar. Bu nedenle ana application class’ı genellikle projenin root package’ında durmalıdır.
com.definex.minibank
├── MiniBankApplication.java ← @SpringBootApplication burada
├── account
│ ├── AccountService.java ← taranır
│ └── AccountRepository.java ← taranır
├── customer
│ ├── CustomerService.java ← taranır
│ └── CustomerRepository.java ← taranır
├── transaction
│ └── TransactionService.java ← taranır
└── config
└── WebConfig.java ← taranırNeden var?
Her bean’i manuel register etmek sürdürülebilir değildir. Component scan sayesinde class’a doğru annotation’ı koyarak Spring’in onu bulmasını sağlarız.
Nerede hata olur?
Application class yanlış pakete konursa bazı service/repository class’ları scan kapsamı dışında kalır. Class doğru yazılmış olsa bile Spring onu görmez.
Component scanning kolaylık sağlar ama mimari sınırları belirsizleştirebilir. Paket yapısı sadece dosya düzeni değildir; Spring’in hangi class’ları göreceğini de belirler. Büyük projelerde root package ve module package tasarımı bilinçli yapılmalıdır.
ApplicationContext nasıl oluşur?
ApplicationContext, Spring’in runtime container’ıdır. Bean’leri oluşturur, saklar, birbirine bağlar ve yaşam döngüsünü yönetir.
Saf Java’da objeleri çoğunlukla biz oluştururuz. Örneğin new AccountService(new AccountRepository()) gibi bir zincir kurarız. Küçük uygulamalarda bu yönetilebilir görünebilir, ama gerçek projede onlarca service, repository, controller, configuration ve external client olduğunda obje yaratma sırası, dependency ilişkileri ve lifecycle karmaşıklaşır.
Spring bu kontrolü üzerine alır. Sen class’ları ve dependency ilişkilerini tanımlarsın; Spring hangi bean’in önce oluşacağını, hangi bean’in hangi constructor’a verileceğini, hangi profile’a göre hangi bean’in aktif olacağını ve uygulama kapanırken hangi cleanup adımlarının çalışacağını yönetir. Bu yüzden Spring dünyasında “kontrol framework’e geçti” deriz. Bu, IoC’nin pratik karşılığıdır.
Bean definition’ları, bean instance’ları, dependency resolution, lifecycle hook’ları, application event’leri, environment/property bilgisi ve resource loading gibi runtime ihtiyaçları ApplicationContext üzerinden yönetilir.
Production’da bu bilgi çok değerlidir. Uygulama neden başlamadı, bean neden bulunamadı, belirli bir configuration neden devreye girdi, yanlış profile mı aktif oldu, startup neden yavaşladı gibi soruların çoğu ApplicationContext oluşum süreciyle ilgilidir.
MiniBank bağlantısı
MiniBank içindeki AccountService, CustomerService, repository’ler, controller’lar, configuration class’ları ve ileride ekleyeceğimiz security, JPA, actuator parçaları bu context içinde yaşayacak. Bu yüzden ilk case’te context’in bean’leri nasıl keşfettiğini ve oluşturduğunu küçük servis katmanı üzerinden gözlemleyeceğiz.
Bean lifecycle
Bean lifecycle, Spring’in bir bean’i doğumundan kapanışına kadar yönettiği süreçtir.
Bir bean sadece “oluşturulmuş obje” değildir. Spring önce instance’ı oluşturur, dependency’leri bağlar, gerekli initialization hook’larını çalıştırır, bazı bean’leri proxy ile sarabilir ve uygulama kapanırken cleanup hook’larını çağırabilir. Bu akış özellikle startup işlemleri, cache warmup, health check hazırlığı, resource cleanup, AOP ve transaction davranışlarını anlamak için önemlidir.
Basit kural şu: Constructor, objenin zorunlu dependency’lerini almak içindir. Dependency’ler bağlandıktan sonra yapılması gereken başlatma işleri için @PostConstruct daha uygun bir noktadır. Uygulama kapanırken kaynakları temizlemek için @PreDestroy kullanılabilir.
@Service
public class StartupReporter {
public StartupReporter() {
System.out.println("1 - constructor çalıştı");
}
@PostConstruct
public void init() {
System.out.println("2 - dependency injection sonrası init çalıştı");
}
@PreDestroy
public void destroy() {
System.out.println("3 - uygulama kapanırken cleanup çalıştı");
}
}@Transactional, @Async ve AOP davranışları çoğu zaman proxy üzerinden çalışır. Proxy’nin lifecycle içinde ne zaman oluştuğunu bilmek, self-invocation gibi konuları anlamanın temelidir. Bu detay AOP modülünde daha net şekilde göreceğimiz önemli bir bağlantıdır.
Dependency Injection Best Practices
Dependency Injection, bir class’ın ihtiyaç duyduğu objeleri kendi oluşturması yerine dışarıdan almasıdır.
Bu yaklaşım class’ları daha test edilebilir, daha esnek ve daha düşük bağımlı hale getirir. Örneğin AccountService, repository’yi kendi içinde new ile oluşturursa testte onu değiştirmek zorlaşır. Ama repository constructor’dan verilirse testte fake veya mock repository vermek kolaylaşır.
Neden constructor injection?
Dependency açık görünür
Class’ın neye ihtiyaç duyduğu constructor imzasından anlaşılır.
final field kullanılır
Dependency sonradan değişmez; bean daha güvenli ve anlaşılır olur.
Test yazmak kolaylaşır
Spring context açmadan class’ı doğrudan test edebilirsin.
Fail-fast sağlar
Eksik dependency varsa uygulama başlarken hata verir.
Circular dependency görünür
Tasarım problemlerini saklamak yerine erken fark ettirir.
Framework bağımlılığı azalır
Class Spring dışında da daha kolay kullanılabilir.
@Service
public class AccountService {
@Autowired
private AccountRepository accountRepository;
// Dependency gizli.
// final kullanılamaz.
// Spring olmadan test zorlaşır.
}@Service
public class AccountService {
private final AccountRepository accountRepository;
public AccountService(AccountRepository accountRepository) {
this.accountRepository = accountRepository;
}
}Setter injection tamamen kötü değildir; özellikle optional dependency gibi nadir durumlarda kullanılabilir. Ama business service, repository, controller ve application service gibi ana bileşenlerde varsayılan tercih constructor injection olmalıdır. MiniBank boyunca bu standardı kullanacağız.
MiniBank Case 01 — Core Service Layer & Bean Discovery
Bu case bir hata avı değil. Bu konu boyunca öğrendiğimiz mekanikleri MiniBank’ın ilk servis katmanına kontrollü şekilde uygulayacağız. Görev listesi, doğrulama komutları, kod adımları ve kabul kriterleri Lab Dashboard'da.
Lab sırasında geride kalırsan lab-01-complete branch'ine geçip herkesle aynı checkpoint'ten devam edebilirsin.
Bu modülden akılda kalması gerekenler
Bu hız kazandırır ama nelerin otomatik geldiğini okuyabilmek gerekir.
Root package tasarımı yanlışsa Spring class’ı görmez.
Bean creation, dependency resolution, lifecycle ve environment bilgisi burada yönetilir.
Constructor, @PostConstruct ve @PreDestroy farklı amaçlara hizmet eder.
MiniBank boyunca field injection yerine constructor injection kullanacağız.
5 kısa kontrol sorusu
1. Auto configuration ne yapar?
Spring Boot’un uygun koşullar oluştuğunda default bean’leri bizim yerimize oluşturmasını sağlar.
2. Component scanning varsayılan olarak nereden başlar?
@SpringBootApplication class’ının bulunduğu paketten başlar ve alt paketleri tarar.
3. Constructor injection neden tercih edilir?
Dependency’leri açık gösterir, final field kullanımına izin verir, test yazmayı kolaylaştırır ve eksik dependency problemini startup sırasında görünür yapar.
4. @PostConstruct ne zaman çalışır?
Bean oluşturulup dependency injection tamamlandıktan sonra, bean kullanıma hazır hale gelmeden önce çalışır.
5. ApplicationContext’in temel sorumluluğu nedir?
Bean’leri oluşturmak, yönetmek, dependency’leri bağlamak, lifecycle süreçlerini işletmek ve runtime environment bilgisini sağlamaktır.