Gün 1 / Spring Core / Modül 01 — Internal Mechanics
Ana Portal
Gün 1 Modül 01 Developer 3 / Senior Rehberli MiniBank Labı

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.

Anlatım
40 dk
Case
15 dk
Kapanış
5 dk
Başlangıç
lab-01-start

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.

B

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.

D

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.

C

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.

AC

ApplicationContext

Spring’in runtime merkezidir. Bean tanımlarını, bean instance’larını, environment bilgilerini, event mekanizmasını ve lifecycle sürecini yönetir.

CP

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.

ST

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.

AU

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.

CS

Component Scan

Spring’in belirli paketleri tarayıp @Service, @Repository, @Controller gibi class’ları bean olarak keşfetmesidir.

LC

Lifecycle

Bir bean’in oluşturulma, dependency alma, initialize olma, kullanıma hazır hale gelme ve kapanırken temizlenme sürecidir.

DI

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.

01
Dependency gelir
Starter veya library projeye eklenir.
02
Classpath okunur
Spring Boot hangi teknolojilerin var olduğunu anlar.
03
Bean tanımları oluşur
Component scan ve auto configuration devreye girer.
04
Context kurulur
Bean’ler oluşturulur, dependency’ler bağlanır.
05
Uygulama hazırdır
Web server açılır, request almaya 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.

Ne işe yarar?

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.

01
Dependency eklendi
Starter projeye girdi.
02
Classpath kontrolü
İlgili class’lar var mı?
03
Property kontrolü
Özellik açık mı?
04
Missing bean kontrolü
Developer zaten yazmış mı?
05
Default bean
Koşullar uygunsa oluşur.
Default bean mantığı
@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.
Senior dikkat noktası

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.

MiniBank için doğru paket yerleşimi
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ır

Neden 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.

Senior dikkat noktası

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.

01
run()
SpringApplication.run çağrılır.
02
Environment
yml, profile, env var okunur.
03
BeanDefinition
Scan + auto config ile tanımlar oluşur.
04
Bean creation
Constructor ve dependency injection çalışır.
05
Ready
Lifecycle tamamlanır, uygulama hazırdır.
Neleri yönetir?

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.

01
Constructor
Bean instance oluşur.
02
Dependency
Constructor/setter/field injection yapılır.
03
@PostConstruct
Dependency sonrası init kodu çalışır.
04
Proxy
AOP/transactional davranışlar eklenebilir.
05
@PreDestroy
Kapanışta cleanup çalışır.
Lifecycle örneği
@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ı");
    }
}
Senior dikkat noktası

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

Kaçınalım: field injection
@Service
public class AccountService {

    @Autowired
    private AccountRepository accountRepository;

    // Dependency gizli.
    // final kullanılamaz.
    // Spring olmadan test zorlaşır.
}
Tercih: constructor injection
@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.

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

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

1
Spring Boot çok sayıda default bean’i otomatik oluşturur.

Bu hız kazandırır ama nelerin otomatik geldiğini okuyabilmek gerekir.

2
Component scanning bizim class’larımızı Spring bean’i haline getirir.

Root package tasarımı yanlışsa Spring class’ı görmez.

3
ApplicationContext Spring’in runtime merkezidir.

Bean creation, dependency resolution, lifecycle ve environment bilgisi burada yönetilir.

4
Bean lifecycle initialization kodunun doğru yerini belirler.

Constructor, @PostConstruct ve @PreDestroy farklı amaçlara hizmet eder.

5
Constructor injection dependency’leri açık ve test edilebilir yapar.

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.