2. Madde 1 - Spring IoC
Bu belgenin amaçları:
- Spring çerçevesinin (http://www.springframework.org) yapılandırma ve entegrasyon olanaklarını keşfetmek
- IoC (Kontrolün Tersine Çevrilmesi) kavramını tanımlamak ve kullanmak; bu kavram, bağımlılık enjeksiyonu (Dependency Injection) olarak da bilinir
2.1. Spring ile 3-katmanlı bir uygulama yapılandırmak
Klasik bir 3-katmanlı uygulamayı ele alalım:
![]() |
İş katmanına ve DAO'e erişimin Java arayüzleri tarafından kontrol edildiğini varsayalım:
- veri erişim katmanı için [IArticlesDao] arayüzü
- iş katmanı için [IArticlesManager] arayüzü
Veri erişim katmanında veya DAO katmanında (Veri Erişim Nesnesi), genellikle bir SGBD ile ve dolayısıyla bir JDBC sürücüsü ile çalışılır. Bir SGBD'teki ürün tablosuna erişen bir sınıfın iskeletini ele alalım:
SGBD üzerinde bir işlem yapmak için, her yöntemin, veritabanıyla Java kodu arasındaki veri alışverişinin gerçekleşeceği veritabanı bağlantısını temsil eden bir [Connection] nesnesine ihtiyacı vardır. Bu nesneyi oluşturmak için dört bilgiye ihtiyaç vardır:
JDBC sürücüsünün sınıf adı SGBD | |
kullanılacak veritabanının URL'si JDBC | |
bağlantının oluşturulacağı kimlik | |
bu kimliğin şifresi |
Yukarıdaki [ArticlesDaoPlainJdbc] sınıfımız bu bilgileri nasıl elde edebilir? Bunun için çeşitli seçenekler vardır:
Çözüm 1 - Bilgiler sınıfa sabit olarak kodlanmıştır:
Bu çözümün dezavantajı, bu bilgilerde herhangi bir değişiklik olduğunda (örneğin şifre değiştiğinde) Java kodunu değiştirmek zorunda kalınmasıdır.
Çözüm 2 - Bilgiler, nesnenin oluşturulması sırasında nesneye aktarılır:
Burada nesne, oluşturulurken çalışması için ihtiyaç duyduğu bilgileri alır. Bu durumda sorun, bu dört bilgiyi nesneye ileten koda kayar. Bu bilgileri nasıl elde etmiştir? İş mantığı katmanındaki aşağıdaki [ArticlesManagerWithDataBase] sınıfı, veri erişim katmanındaki [ArticlesDaoPlainJdbc] nesnesini oluşturabilir:
![]() |
Burada da, [ArticlesDaoPlainJdbc] nesnesinin oluşturulması için gerekli bilgilerin, [ArticlesManagerWithDataBase] nesnesinin oluşturucusuna sağlandığı görülmektedir. Bu bilgilerin, kullanıcı arayüzü katmanı gibi bir üst katman tarafından kendisine aktarıldığını varsayabiliriz. Böylece adım adım uygulamanın en üst katmanına ulaşılır. Konumu gereği, bu katman, ihtiyaç duyduğu yapılandırma bilgilerini kendisine iletebilecek bir katman tarafından çağrılmaz. Bu nedenle, oluşturucu aracılığıyla yapılandırma dışında başka bir çözüm bulunmalıdır. Bir uygulamayı en üst katmanında yapılandırmak için yaygın olarak kullanılan çözüm, zaman içinde değişebilecek tüm bilgilerin yer aldığı bir dosyanın kullanılmasıdır. Bu türden birden fazla dosya olabilir. Uygulamanın başlatılması sırasında, bir başlatma katmanı, uygulamanın farklı katmanları için gerekli olan nesnelerin tamamını veya bir kısmını oluşturur.
Çok çeşitli yapılandırma dosyaları mevcuttur. Güncel eğilim, XML dosyalarının kullanılması yönündedir. Spring de bu seçeneği benimsemiştir. Bir [ArticlesDaoPlainJdbc] nesnesini yapılandıran dosya şu şekilde olabilir:
Bir uygulama, Spring'in "bean" olarak adlandırdığı nesnelerden oluşan bir kümedir; çünkü bunlar, bir nesnenin özel alanlarının erişim yöntemleri (getter/setter) için JavaBean adlandırma standardını izler. Bir uygulamada hizmet sunma rolünü üstlenen nesneler genellikle tek bir kopya olarak oluşturulur. Bunlara singleton denir. Dolayısıyla, burada incelediğimiz çok katmanlı uygulama örneğimizde, ürün veritabanına erişim, [ArticlesDaoPlainJdbc] sınıfının tek bir örneği tarafından sağlanacaktır. Bir web uygulamasında, bu hizmet nesneleri aynı anda birden fazla müşteriye hizmet eder. Her müşteri için ayrı bir hizmet nesnesi oluşturulmaz.
Yukarıdaki Spring yapılandırma dosyası, [istia.st.articles.dao] adlı bir pakette [ArticlesDaoPlainJdbc] türünde tek bir hizmet nesnesi oluşturulmasına olanak tanır. Bu nesnenin oluşturucusu için gerekli dört bilgi, bir <bean>...</bean> etiketi içinde tanımlanır. Oluşturulacak tekil nesne sayısı kadar <bean> etiketi bulunur.
Spring dosyasında tanımlanan nesnelerin oluşturulması ne zaman gerçekleşir? Bir uygulamanın başlatılması, eğer varsa, aynı uygulamanın main yöntemine devredilebilir. Bir web uygulaması için bu, ana servletin [init] yöntemi olabilir. Her uygulamada, ilk olarak çalıştırılacağı kesin olan bir yöntem bulunur. Genellikle singletonların oluşturulması bu yöntem içinde gerçekleşir.
Bir örnek verelim. Önceki [ArticlesDaoPlainJdbc] sınıfını bir JUnit testi kullanarak test etmek istediğimizi varsayalım. JUnit test sınıfında, diğer tüm yöntemlerden önce çalıştırılan bir [setUp] yöntemi bulunur. [ArticlesDaoPlainJdbc] singleton'u işte burada oluşturulur.
Yapılandırma bilgilerini yapıcı aracılığıyla aktarma çözümünü izlersek, aşağıdaki test sınıfını elde ederiz:
Çağıran sınıf [TestArticlesPlainJdbc], oluşturulacak [ArticlesDaoPlainJdbc] singletonunun başlatılması için gerekli dört bilgiyi bilmelidir.
Yapılandırma bilgilerinin yapılandırma dosyası yoluyla aktarılmasına dayalı çözümü izlersek, yukarıda açıklanan Spring dosyasını kullanarak aşağıdaki test sınıfını elde edebiliriz.
Burada, [TestSpringArticlesPlainJdbc] çağrı sınıfının, oluşturulacak singleton'ın başlatılması için gerekli bilgileri bilmesi gerekmez. Sadece şunları bilmesi yeterlidir:
- [springArticlesPlainJdbc.xml]: Yukarıda açıklanan Spring yapılandırma dosyasının adı
- [articlesDao]: oluşturulacak singleton'ın adı
Yukarıda belirtilen bu iki öğe dışında yapılandırma dosyasında yapılan herhangi bir değişiklik, Java kodunu etkilemez. Uygulamadaki nesnelerin bu şekilde yapılandırılması oldukça esnektir. Uygulamanın yapılandırılması için sadece iki şeye ihtiyaç vardır:
- oluşturulacak singletonların tanımını içeren Spring dosyasının adı
- bu singletonların adları; bunlar, Java kodunun yapılandırma dosyası sayesinde kendileriyle ilişkilendirilen nesnelere referans alabilmesi için kullanılır
2.2. Bağımlılık Enjeksiyonu ve Kontrolün Tersine Çevrilmesi
Şimdi, Spring'in uygulamaları yapılandırmak için kullandığı bağımlılık enjeksiyonu (Dependency Injection) kavramını ele alalım. Kontrol tersine çevirme (IoC, Inversion of Control) terimi de kullanılır. Uygulamamızın iş katmanındaki [ArticlesManagerWithDataBase] singleton'ının oluşturulmasını ele alalım:
![]() |
SGBD'teki verilere erişmek için, iş katmanı [IArticlesDao] arayüzünü uygulayan bir nesnenin hizmetlerini kullanmalıdır; örneğin, [ArticlesDaoPlainJdbc] türünde bir nesne. [ArticlesManagerWithDataBase] sınıfının kodu aşağıdaki gibi olabilir:
public class ArticlesManagerWithDataBase implements IArticlesManager {
// bir veri erişim örneği
private IArticlesDao articlesDao;
....
public ArticlesManagerWithDataBase (String driverClassName, String url, String user, String pwd, ...) {
...
// veri erişim hizmetinin oluşturulması
articlesDao =(IArticlesDao)new ArticlesDaoPlainJdbc(driverClassName,url,user,pwd);
...
}
public ... doSomething(...){
...
}
}
Burada [ArticlesDaoPlainJdbc] sınıfının, [IArticlesDao] arayüzünü uyguladığı varsayılmaktadır:
Sınıfın çalışması için gerekli olan [IArticlesDao] türündeki tekil nesneyi oluşturmak üzere, sınıfın oluşturucusu [IArticlesDao] arayüzünün uygulama sınıfının adını açıkça kullanır:
Dolayısıyla kodda sınıf adına sabit bir bağımlılık bulunmaktadır. [IArticlesDao] arayüzünün uygulama sınıfı değişirse, yukarıdaki oluşturucunun kodu da değiştirilmelidir. Nesneler arasında aşağıdaki ilişkiler vardır:
![]() |
[ArticlesManagerWithDataBase] sınıfı, ihtiyaç duyduğu [ArticlesDaoPlainJdbc] nesnesini kendi inisiyatifiyle oluşturur. "Kontrolün tersine çevrilmesi" terimine geri dönersek, ihtiyaç duyduğu nesneyi oluşturma "kontrolü"nün bu sınıfa ait olduğunu söyleyebiliriz.
[ArticlesManagerWithDataBase] sınıfı için JUnit adlı bir test sınıfı yazmak gerekirse, sonuç aşağıdaki gibi olabilir:
Test sınıfı, iş sınıfı [ArticlesManagerWithDataBase]'in bir örneğini oluşturur; bu sınıf da kendi yapıcı işlevinde, veri erişim sınıfı [ArticlesDaoPlainJdbc]'in bir örneğini oluşturur.
Spring ile elde edilecek çözüm, iş sınıfı [ArticlesManagerWithDataBase]'in ihtiyaç duyduğu veri erişim sınıfının adı olan [ArticlesDaoPlainJdbc]'i bilme gerekliliğini ortadan kaldıracaktır. Bu sayede, iş sınıfının Java koduna dokunmadan bu adı değiştirmek mümkün olacaktır. Spring, veri erişim katmanına ait olan ve iş katmanına ait olan iki singletonu aynı anda oluşturmaya olanak sağlayacaktır. Spring yapılandırma dosyası yeni bir bean tanımlayacaktır:
Yenilik, oluşturulacak iş sınıfının singleton'ını tanımlayan bean'de yatmaktadır:
<bean id="articlesManager" class="istia.st.articles.domain.ArticlesManagerWithDataBase">
<property name="articlesDao">
<ref bean="articlesDao"/>
</property>
</bean>
- [articlesManager] bean'ini uygulayan sınıf tanımlanmıştır: [ArticlesManagerWithDataBase]
- bean'in [articlesDao] alanı, <property name="articlesDao"> etiketiyle bir değer alır. Bu, [ArticlesManagerWithDataBase] sınıfında tanımlanan alandır:
[articlesDao] alanının Spring ve <property> etiketiyle başlatılabilmesi için, alanın JavaBean standardına uyması ve [articlesDao] alanını başlatmak için [setArticlesDao] adlı bir yöntemin mevcut olması gerekir. Yöntemin adının, alan adından çok kesin bir şekilde türetildiğine dikkat edilmelidir. Buna paralel olarak, genellikle alanın değerini almak için bir [get...] yöntemi bulunur. Burada bu yöntem [getArticlesDao]'tir. Bu yeni sürümde, [ArticlesManagerWithDataBase] sınıfının artık bir oluşturucusu yoktur. Artık buna ihtiyacı kalmamıştır.
- Spring tarafından [articlesDao] alanına atanacak değer, yapılandırma dosyasında tanımlanan [articlesDao] bean’ine aittir:
<bean id="articlesManager" class="istia.st.articles.domain.ArticlesManagerWithDataBase">
<property name="articlesDao">
<ref bean="articlesDao"/>
</property>
</bean>
<bean id="articlesDao" class="istia.st.articles.dao.ArticlesDaoPlainJdbc">
<constructor-arg index="0">
.............
</bean>
- Spring, [ArticlesManagerWithDataBase] singletonunu oluştururken, [ArticlesDaoPlainJdbc] singletonunu da oluşturmak zorunda kalacaktır:
- Spring, bean'lerin bağımlılık grafiğini oluşturacak ve [articlesManager] bean'inin [articlesDao] bean'ine bağımlı olduğunu görecektir
- [articlesDao] bean'ini, yani [ArticlesDaoPlainJdbc] türünde bir nesneyi oluşturacak
- ardından [ArticlesManagerWithDataBase] türünde bir [articlesManager] bean'i oluşturacaktır
Şimdi, [ArticlesManagerWithDataBase] sınıfı için bir JUnit testi düşünelim. Bu test aşağıdaki gibi olabilir:
[springArticlesManagerWithDataBase.xml] adlı Spring dosyasında tanımlanan iki singletonun oluşturulma sürecini takip edelim.
- Yukarıdaki [setUp] yöntemi, [articlesManager] adlı bean'e bir referans ister
- Spring, yapılandırma dosyasını inceler ve [articlesManager] adlı bean'i bulur. Eğer bean zaten oluşturulmuşsa, Spring sadece nesneye (singleton) bir referans döndürür; aksi takdirde bean'i oluşturur.
- Spring, [articlesManager] bean'inin [articlesDao] bean'ine olan bağımlılığını algılar. Bu nedenle, [ArticlesDaoPlainJdbc] türündeki [articlesDao] singleton'ı, henüz oluşturulmamışsa (singleton) oluşturur.
- [ArticlesManagerWithDataBase] türündeki [articlesManager] singleton'ını oluşturur
Bu mekanizma şu şekilde şematize edilebilir:
![]() |
[ArticlesManagerWithDataBase] sınıfının iskeletini hatırlayalım:
Spring tarafından singletonların oluşturulması tamamlandığında, [ArticlesManagerWithDataBase] türünde bir nesne elde edilir; bu nesnenin [articlesDao] alanı, nasıl olduğu bilinmeden başlatılmıştır. Buna, [ArticlesManagerWithDataBase] nesnesine bağımlılık enjekte edildi denir. Ayrıca kontrolün tersine çevrildiği de söylenir: Artık [ArticlesManagerWithDataBase] nesnesi, ihtiyaç duyduğu [IArticlesDao] arayüzünü uygulayan nesneyi kendi başına oluşturma inisiyatifini almamaktadır; artık en üst düzeydeki uygulama (başlatılırken), ihtiyaç duyduğu tüm nesneleri oluşturmayı ve bunlar arasındaki bağımlılıkları yönetmeyi üstlenir.
[ArticlesManagerWithDataBase] singleton'ının bir Spring dosyası aracılığıyla yapılandırılmasının temel avantajı, artık [ArticlesManagerWithDataBase] sınıfının [articlesDao] alanına karşılık gelen uygulama sınıfını, bu sınıfın kodunda herhangi bir değişiklik yapılmasına gerek kalmadan değiştirebilmemizdir. Bunun için Spring dosyasındaki [articlesDao] bean tanımında sınıf adını değiştirmek yeterlidir:
örneğin şöyle olacaktır:
[ArticlesManagerWithDataBase] bean'i, farkında bile olmadan bu yeni veri erişim sınıfıyla çalışacaktır.
2.3. Spring IoC'in uygulamaya geçirilmesi
2.3.1. Örnek 1
Aşağıdaki sınıfı ele alalım:
Sınıfta şunlar bulunur:
- "nom" ve "age" adlı iki özel alan
- bu iki alanın okuma (get) ve yazma (set) yöntemleri
- toString nesnesinin değerini bir karakter dizisi biçiminde almak için [Personne] yöntemi
- nesnenin oluşturulmasında Spring tarafından çağrılacak bir init yöntemi, nesnenin yok edilmesinde çağrılacak bir close yöntemi
[Personne] türünde nesneler oluşturmak için aşağıdaki Spring dosyasını kullanacağız:
Bu dosyanın adı config.xml olacaktır.
- Bu dosya, sırasıyla "personne1" ve "personne2" anahtarlarına sahip, [Personne] türünde iki bean tanımlar
- Her bir kişinin [nom, age] alanlarını başlatır
- [init-method] nesnesinin ilk oluşturulması sırasında ve [destroy-method] nesnesinin yok edilmesi sırasında çağrılacak yöntemleri tanımlar
Testlerimiz için, üzerine sırayla yöntemler ekleyeceğimiz tek bir JUnit test sınıfı kullanacağız. Bu sınıfın ilk sürümü şu şekilde olacaktır:
Açıklamalar:
- [config.xml] dosyasında tanımlanan bean'leri elde etmek için [ListableBeanFactory] türünde bir nesne kullanıyoruz. Bean'lere erişim sağlayan başka nesne türleri de mevcuttur. [ListableBeanFactory] nesnesi, test sınıfının [setUp] yönteminde elde edilir ve özel bir değişkende saklanır. Böylece, tüm test yöntemleri tarafından kullanılabilir hale gelir.
- [config.xml] dosyası, uygulamanın [ClassPath]'i olan c.a.d'e yerleştirilecektir. Bu, Java sanal makinesinin uygulama tarafından referans verilen bir sınıfı ararken taradığı dizinlerden birinde bulunur. [ClassPathResource] nesnesi, bir uygulamanın [ClassPath] dizininde bir kaynağı aramak için kullanılır; buradaki kaynak, [config.xml] dosyasıdır.
- Spring, çeşitli formatlardaki yapılandırma dosyalarını kullanabilir. [XmlBeanFactory] nesnesi, XML formatındaki bir yapılandırma dosyasını analiz etmeye olanak tanır.
- Bir Spring dosyasının işlenmesi sonucunda [ListableBeanFactory] türünde bir nesne elde edilir; burada bu nesne bf'dir. Bu nesne ile, C anahtarıyla tanımlanan bir bean, bf.getBean(C) ifadesiyle elde edilir.
- [test1] yöntemi, "personne1" ve "personne2" anahtarlarına sahip bean'lerin değerlerini ister ve görüntüler.
Uygulamamızın Eclipse projesinin yapısı şu şekildedir:

Açıklamalar:
- [src] klasörü kaynak kodları içerir. Derlenen kodlar, burada gösterilmeyen [bin] klasörüne yerleştirilecektir.
- [config.xml] dosyası, [src] klasörünün kök dizininde yer alır. Projenin derlenmesi sırasında bu dosya otomatik olarak, uygulamanın [ClassPath] klasörünün bir parçası olan [bin] klasörüne kopyalanır. [ClassPathResource] nesnesi bu klasörde bu dosyayı arar.
- [lib] klasörü, uygulama için gerekli olan üç Java kütüphanesini içerir:
- Spring sınıfları için commons-logging.jar ve spring-core.jar
- junit.jar, JUnit sınıfları içindir
- [lib] dosyası da uygulamanın [ClassPath] dosyasının bir parçasıdır
JUnit testindeki [test1] yönteminin çalıştırılması aşağıdaki sonuçları verir:
Yorumlar:
- Spring, [commons-logging.jar] kütüphanesi aracılığıyla bir dizi olayı günlüğe kaydeder. Bu günlükler, Spring'in işleyişini daha iyi anlamamızı sağlar.
- [config.xml] dosyası yüklendi ve ardından
- işlem*
işlemi, [personne1] bean'inin oluşturulmasını tetiklemiştir. Bu konuyla ilgili Spring günlüğünü görebiliriz. [personne1] bean'inin tanımında [init-method="init"] yazılmış olduğu için, oluşturulan [Personne] nesnesinin [init] yöntemi çalıştırıldı. İlgili mesaj görüntülenir.
```java
System.out.println("personne1=" + personne1.toString());
```
işlemi, oluşturulan [Personne] nesnesinin değerini görüntüledi.
- Aynı durum, [personne2] anahtar bean'i için de tekrarlanır.
- Son işlem
personne2 = (Personne) bf.getBean("personne2");
System.out.println("personne2=" + personne2.toString());
, [Personne] türünde yeni bir nesnenin oluşturulmasına yol açmamıştır. Eğer böyle olsaydı, [init] yönteminin çıktısı görüntülenirdi, ancak burada böyle bir durum söz konusu değildir. Bu, singleton ilkesidir. Spring, varsayılan olarak, yapılandırma dosyasındaki bean'lerin yalnızca tek bir örneğini oluşturur. Bu, bir nesne referans hizmetidir. Henüz oluşturulmamış bir nesnenin referansı istenirse, Spring onu oluşturur ve bir referans döndürür. Nesne zaten oluşturulmuşsa, Spring sadece bir referans verir.
- [destroy-method=close] bean tanımında yazmış olsak da, [Personne] nesnesinin [close] yöntemine dair hiçbir iz bulunmadığı görülebilir. Bu yöntemin, nesnenin kapladığı bellek çöp toplayıcı (garbage collector) tarafından geri kazanıldığında yürütülmesi mümkündür. Bu gerçekleştiğinde, uygulama çoktan sona ermiştir ve ekrana yazdırma işleminin hiçbir etkisi olmaz. Kontrol edilmesi gerekir.
Artık Spring yapılandırmasının temellerini öğrendiğimize göre, bundan sonra açıklamalarımızı biraz daha hızlı yapabileceğiz.
2.3.2. Örnek 2
Aşağıdaki yeni [Voiture] sınıfını ele alalım:
Sınıfta şunlar bulunur:
- tür, marka ve sahibi adlı üç özel alan içerir. Bu alanlar, bean'lerin get ve set adlı genel yöntemleriyle başlatılabilir ve okunabilir. Ayrıca Voiture(String, String, Personne) yapıcı kullanılarak da başlatılabilirler. Sınıf, JavaBean standardına uymak için argüman içermeyen bir yapıcıya da sahiptir.
- toString nesnesinin değerini bir karakter dizesi biçiminde almak için bir toString yöntemi
- nesnenin oluşturulmasından hemen sonra Spring tarafından çağrılacak bir init yöntemi, nesnenin yok edilmesinde çağrılacak bir close yöntemi
[Voiture] türünde nesneler oluşturmak için aşağıdaki Spring [config.xml] dosyasını kullanacağız:
Bu dosya, önceki tanımlara "voiture1" anahtarına sahip [Voiture] türünde bir bean ekler. Bu bean'i başlatmak için şunu yazabilirdik:
Daha önce tanıtılan bu yöntemi seçmek yerine, burada sınıfın Voiture(String, String, Personne) oluşturucusunu kullanmayı tercih ettik. Ayrıca, [voiture1] bean'i, [init-method] nesnesinin ilk oluşturulması sırasında çağrılacak yöntemi ve [destroy-method] nesnesinin yok edilmesi sırasında çağrılacak yöntemi tanımlar.
Testlerimiz için, daha önce tanıtılan JUnit test sınıfını kullanacağız ve ona aşağıdaki [test2] yöntemini ekleyeceğiz:
[test2] yöntemi, [voiture1] bean'ini alır ve görüntüler.
Eclipse projesinin yapısı, önceki testtekiyle aynı kalır. JUnit testindeki [test2] yönteminin çalıştırılması aşağıdaki sonuçları verir:
Açıklamalar:
- [test2] yöntemi, [voiture1] bean'ine bir referans talep eder
- 4. satır: Spring, [voiture1] bean'inin oluşturulmasına başlar, çünkü bu bean henüz oluşturulmamıştır (singleton)
- 6. satır: [voiture1] bean'i, [personne2] bean'ine referans verdiği için, bu son bean da sırayla oluşturulur
- 7. satır: [personne2] bean'i oluşturuldu. Ardından, bu bean'in [init] yöntemi yürütülür.
- 9. satır: Spring, [voiture1] bean'ini oluşturmak için bir oluşturucu kullanacağını belirtir
- 10. satır: [voiture1] bean'i oluşturuldu. Ardından [init] yöntemi çalıştırıldı.
- 11. satır: [test2] yöntemi, [voiture1] bean'inin değerini görüntüler
2.3.3. Örnek 3
Aşağıdaki yeni [GroupePersonnes] sınıfını ekliyoruz:
Bu sınıfın iki özel üyesi şunlardır:
üyeler: gruba üye kişilerin bulunduğu bir dizi
groupesDeTravail: bir kişiyi bir çalışma grubuna atayan bir sözlük
Burada, [GroupePersonnes] sınıfının, JavaBean standardına uymak için argüman içermeyen bir yapıcı tanımlamadığına dikkat edilmelidir. Herhangi bir yapıcı bulunmadığında, argüman içermeyen ve hiçbir işlem yapmayan bir "varsayılan" yapıcı olduğu hatırlatılmalıdır.
Burada, Spring'in dizi veya sözlük türünde alanlara sahip nesneler gibi karmaşık nesneleri nasıl başlatabileceğini göstermeye çalışıyoruz. Önceki Spring dosyası [config.xml]'e yeni bir bean ekliyoruz:
- <list> etiketi, dizi türünde olan veya List arayüzünü uygulayan bir alanı farklı değerlerle başlatmaya olanak tanır.
- <map> etiketi, Map arayüzünü uygulayan bir alan için de aynı işlemi yapmamızı sağlar
Testlerimiz için, daha önce tanıtılan JUnit test sınıfını kullanacağız ve ona aşağıdaki [test3] yöntemini ekleyeceğiz:
[test3] yöntemi, [groupe1] bean'ini alır ve görüntüler.
Eclipse projesinin yapısı, önceki testtekiyle aynı kalır. JUnit testindeki [test3] yönteminin çalıştırılması aşağıdaki sonuçları verir:
Açıklamalar:
- [test3] yöntemi, [groupe1] bean'ine bir referans ister
- 4. satır: Spring, bu bean'in oluşturulmasına başlar
- çünkü [groupe1] bean'i, [personne1] ve [personne2] bean'lerine referans verdiğinden, bu iki bean oluşturulur (6. ve 9. satırlar) ve init yöntemleri yürütülür (7. ve 10. satırlar)
- 11. satır: [groupe1] bean'i oluşturuldu. [init] yöntemi şimdi yürütülüyor.
- 12. satır: [test3] yöntemi tarafından görüntüleme talebi gönderildi.
2.4. Üç katmanlı web uygulamalarını yapılandırmak için Spring
2.4.1. Uygulamanın genel mimarisi
Aşağıdaki yapıya sahip bir 3 katmanlı uygulama oluşturmak istiyoruz:
![]() |
- Java arayüzleri kullanılarak üç katman birbirinden bağımsız hale getirilecektir
- Üç katmanın entegrasyonu Spring ile gerçekleştirilecektir
- Üç katmanın her biri için ayrı paketler oluşturulacak ve bunlara Control, Domain ve Dao isimleri verilecektir. Test uygulamaları ise ayrı bir pakette yer alacaktır.
Eclipse ortamındaki uygulamanın yapısı şu şekilde olabilir:

2.4.2. Veri erişim katmanı DAO
DAO katmanı aşağıdaki arayüzü uygulayacaktır:
package istia.st.demo.dao;
public interface IDao1 {
public int doSometingInDaoLayer(int a, int b);
}
- Dao1Impl1 ve Dao1Impl2 adlı iki sınıf yazın; bu sınıflar IDao1 arayüzünü uygulayacaktır. Dao1Impl1. doSomethingInDaoLayer yöntemi a+b sonucunu verecek ve Dao1Impl2. doSomethingInDaoLayer yöntemi a-b sonucunu verecektir.
- Yukarıdaki iki sınıfı test eden JUnit adlı bir test sınıfı yazın
2.4.3. İş mantığı katmanı
İş katmanı aşağıdaki arayüzü uygulayacaktır:
package istia.st.demo.domain;
public interface IDomain1 {
public int doSomethingInDomainLayer(int a, int b);
}
- IDomain1 arayüzünü uygulayan Domain1Impl1 ve Domain1Impl2 adlı iki sınıf yazın. Bu sınıfların, IDao1 türünde bir parametre alan bir yapıcı işlevi olacaktır. Domain1Impl1.doSomethingInDomainLayer yöntemi, a ve b değerlerini bir birim artıracak ve ardından bu iki parametreyi, alınan IDao1 türündeki nesnenin doSomethingInDaoLayer yöntemine aktaracaktır. Domain1Impl2.doSomethingInDomainLayer yöntemi ise, a ve b değerlerini bir birim azaltır ve ardından aynı işlemi gerçekleştirir.
- Yukarıdaki iki sınıfı test eden bir JUnit test sınıfı yazın
2.4.4. Kullanıcı arayüzü katmanı
Kullanıcı arayüzü katmanı aşağıdaki arayüzü uygulayacaktır:
package istia.st.demo.control;
public interface IControl1 {
public int doSometingInControlLayer(int a, int b);
}
- Control1Impl1 ve Control1Impl2 adlı iki sınıf yazın; bu sınıflar IControl1 arayüzünü uygulayacaktır. Bu sınıfların, IDomain1 türünde bir parametre alan bir yapıcı işlevi olacaktır. Control1Impl1.doSomethingInControlLayer yöntemi, a ve b değerlerini bir birim artıracak ve ardından bu iki parametreyi, alınan IDomain1 türündeki nesnenin doSomethingInDomainLayer yöntemine aktaracaktır. Control11Impl2.doSomethingInControlLayer yöntemi ise, a ve b değerlerini bir birim azaltır ve ardından aynı işlemi gerçekleştirir.
- Yukarıdaki iki sınıfı test eden JUnit adlı bir test sınıfı yazın
2.4.5. Spring ile entegrasyon
- Önceki üç katmanın her birinin hangi sınıfları kullanacağına karar verecek bir Spring yapılandırma dosyası yazın
- Yazılan uygulamanın esnekliğini ortaya çıkarmak için farklı Spring yapılandırmaları kullanan bir JUnit test sınıfı yazın
- IControl1 arayüzüne iki parametre veren ve arayüzün döndürdüğü sonucu görüntüleyen bağımsız bir uygulama (main yöntemi) yazın.
2.4.6. Bir çözüm
2.4.6.1. Eclipse projesi

[lib] klasöründeki dosyalar, projenin [ClassPath] klasörüne eklenmiştir.
2.4.6.2. [istia.st.demo.dao] paketi
Arayüz:
İlk uygulama sınıfı:
İkinci sınıf uygulama:
2.4.6.3. [istia.st.demo.domain] paketi
Arayüz:
Birinci uygulama sınıfı:
İkinci sınıf uygulama:
2.4.6.4. [istia.st.demo.control] paketi
Arayüz
Birinci uygulama sınıfı:
İkinci sınıf bir uygulama:
2.4.6.5. [Spring] yapılandırma dosyaları
İlk [springMainTest1.xml] dosyası:
İkinci bir [springMainTest2.xml]:
2.4.6.6. [istia.st.demo.tests] test paketi
[main] türünde bir test:
Eclipse konsolunda elde edilen sonuçlar:
İkinci yapılandırma dosyasını kullanan başka bir test: [Spring]:
Eclipse konsolunda elde edilen sonuçlar:
Son olarak, bir Junit testi:
2.5. Conclusion
Spring çerçevesi, hem uygulama mimarisinde hem de yapılandırmalarında gerçek bir esneklik sağlar. Spring'in iki temel unsurundan biri olan IoC kavramını kullandık. Diğer temel unsur ise, burada ele almadığımız AOP (Aspect Oriented Programming) dir. Bu, sınıf kodunu değiştirmeden, yapılandırma yoluyla bir sınıf yöntemine "davranış" eklemeye olanak tanır. Şematik olarak, AOP belirli yöntemlere yapılan çağrıları filtrelemeye olanak tanır:
![]() |
- Filtre, hedef M yönteminden önce, sonra veya her ikisinde de çalıştırılabilir.
- M yöntemi bu filtrelerin varlığını göz ardı eder. Bu filtreler Spring yapılandırma dosyasında tanımlanır.
- M yönteminin kodu değiştirilmez. Filtreler, oluşturulacak Java sınıflarıdır. Spring, özellikle SGBD işlemlerini yönetmek için önceden tanımlanmış filtreler sağlar.
- Filtreler, bean'lerdir ve bu nedenle Spring yapılandırma dosyasında bean'ler olarak tanımlanırlar.
Yaygın olarak kullanılan bir filtre, işlem filtresidir. İş katmanındaki M yöntemini ele alalım; bu yöntem, veriler üzerinde birbirinden ayrılamayan iki işlem gerçekleştirir (iş birimi). Bu iki işlemi gerçekleştirmek için DAO katmanındaki M1 ve M2 yöntemlerini çağırır.
![]() |
İş katmanında yer aldığı için M yöntemi, bu verilerin depolandığı ortamı göz ardı eder. Örneğin, verilerin bir SGBD'te olduğunu varsaymak zorunda değildir ve M1 ile M2 yöntemlerine yapılan iki çağrıyı bir SGBD işlemi içine yerleştirmesi gerekmez. Bu ayrıntılarla ilgilenmek DAO katmanının görevidir. Bu soruna bir çözüm olarak, DAO katmanında, M1 ve M2 yöntemlerini kendisi çağıran bir yöntem oluşturulabilir; ve bu çağrıları SGBD işlemine dahil etmesi.
![]() |
AOP filtreleme çözümü daha esnektir. Bu çözüm, M çağrısından önce bir işlem başlatacak ve çağrıdan sonra duruma göre bir commit veya rollback gerçekleştirecek bir filtre tanımlamaya olanak tanır.
![]() |
Bu yaklaşımın birçok avantajı vardır:
- filtre bir kez tanımlandıktan sonra, örneğin işlem gerektiren tüm yöntemler gibi birden fazla yönteme uygulanabilir
- bu şekilde filtrelenen yöntemlerin yeniden yazılmasına gerek kalmaz
- kullanılacak filtreler yapılandırma yoluyla tanımlandığından, bunlar değiştirilebilir
IoC ve AOP kavramlarının yanı sıra, Spring üç katmanlı uygulamalar için birçok destek sınıfı sunar:
- JDBC için SqlMap (iBatis), Hibernate için JDO (Java Data Object) ve Kullanıcı Arayüzü katmanında DAO
- Kullanıcı Arayüzü katmanında MVC modeli
Daha fazla bilgi için: http://www.springframework.org.









