Skip to content

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:

  1. veri erişim katmanı için [IArticlesDao] arayüzü
  2. 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:

public class ArticlesDaoPlainJdbc implements IArticlesDao {

     // veri kaynağına bağlantı
    private String driverClassName=null;
    private Connection connexion=null;
    private String url = null;
    private String user = null;
    private String pwd = null;
 ....

    public List getAllArticles() {
        // ürün listesi isteniyor
        try {
             // JDBC sürücüsü yükleniyor
            Class.forName(driverClassName);
            // BD'e bağlantı oluşturuluyor
            connexion = DriverManager.getConnection(url, user, pwd);
            ...
        } catch (SQLException ex) {
            ...
        } finally {
            ...
        }
    }

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:

String driverClassName
JDBC sürücüsünün sınıf adı SGBD
String url
kullanılacak veritabanının URL'si JDBC
String user
bağlantının oluşturulacağı kimlik
String pwd
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:

1
2
3
4
5
6
7
8
public class ArticlesDaoPlainJdbc implements IArticlesDao {

     // veri kaynağına bağlantı kuruluyor
    private final String driverClassName = "org.firebirdsql.jdbc.FBDriver";
    private String url = "jdbc:firebirdsql:localhost/3050:d:/databases/dbarticles.gdb";
    private String user = "someone";
    private String pwd = "somepassword";
 ....

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:

public class ArticlesDaoPlainJdbc implements IArticlesDao {

     // veri kaynağına bağlantı
    private final String driverClassName;
    private String url;
    private String user;
    private String pwd;
 ....
    public ArticlesDaoPlainJdbc(String driverClassName,String url,String user,String pwd) {
      this.driverClassName=driverClassName;
    this.url=url;
    this.user=user;
    this.pwd=pwd;
    ...
    }

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:

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 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:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
     <!-- veri erişim sınıfı -->
    <bean id="articlesDao" class="istia.st.articles.dao.ArticlesDaoPlainJdbc">
        <constructor-arg index="0">
            <value>org.firebirdsql.jdbc.FBDriver</value>
        </constructor-arg>
        <constructor-arg index="1">
            <value>jdbc:firebirdsql:localhost/3050:d:/databases/dbarticles.gdb</value>
        </constructor-arg>
        <constructor-arg index="2">
            <value>someone</value>
        </constructor-arg>
        <constructor-arg index="3">
            <value>somepassword</value>
        </constructor-arg>
    </bean>
</beans>

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:

public class TestArticlesPlainJdbc extends TestCase {
    // malzeme erişim sınıfını test eder ArticlesDaoPlainJdbc
     // veri kaynağı sprintest'te tanımlanmıştır

     // test edilen sınıfın bir örneği
    private IArticlesDao articlesDao;

    protected void setUp() throws Exception{
        // bir veri erişim örneğini alır
        articlesDao =
            (IArticlesDao) new ArticlesDaoPlainJdbc("org.firebirdsql.jdbc.FBDriver",
                "jdbc:firebirdsql:localhost/3050:d:/databases/dbarticles.gdb","someone","somepassword");
    }

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

public class TestSpringArticlesPlainJdbc extends TestCase {
    // ürün erişim sınıfını test eder ArticlesDaoJdbc
     // veri kaynağı sprintest'te tanımlanmıştır

     // test edilen sınıfın bir örneği
    private IArticlesDao articlesDao;

    protected void setUp() throws Exception {
      // bir veri erişim örneğini alır
      articlesDao = (IArticlesDao) (new XmlBeanFactory(new ClassPathResource(
          "springArticlesPlainJdbc.xml"))).getBean("articlesDao");
    }

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:

  1. [springArticlesPlainJdbc.xml]: Yukarıda açıklanan Spring yapılandırma dosyasının adı
  2. [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:

public class ArticlesDaoPlainJdbc implements IArticlesDao {...}

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:

articlesDao =(IArticlesDao) new ArticlesDaoPlainJdbc(...);

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:

public class TestArticlesManagerWithDataBase extends TestCase {
    // test edilen iş sınıfının bir örneği
    private IArticlesManager articlesManager;

    protected void setUp() throws Exception {
        // test edilen iş sınıfının bir örneğini oluşturur
        articlesManager =
            (IArticlesManager) new ArticlesManagerWithDataBase("org.firebirdsql.jdbc.FBDriver",
                "jdbc:firebirdsql:localhost/3050:d:/databases/dbarticles.gdb","someone","somepassword");
    }

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:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
     <!-- veri erişim sınıfı -->
    <bean id="articlesDao" class="istia.st.articles.dao.ArticlesDaoPlainJdbc">
        <constructor-arg index="0">
            <value>org.firebirdsql.jdbc.FBDriver</value>
        </constructor-arg>
        <constructor-arg index="1">
            <value>jdbc:firebirdsql:localhost/3050:d:/databases/dbarticles.gdb</value>
        </constructor-arg>
        <constructor-arg index="2">
            <value>someone</value>
        </constructor-arg>
        <constructor-arg index="3">
            <value>somepassword</value>
        </constructor-arg>
    </bean>
    <bean id="articlesManager" class="istia.st.articles.domain.ArticlesManagerWithDataBase">
        <property name="articlesDao">
            <ref bean="articlesDao"/>
        </property>
    </bean>
</beans>

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>
  1. [articlesManager] bean'ini uygulayan sınıf tanımlanmıştır: [ArticlesManagerWithDataBase]
  2. bean'in [articlesDao] alanı, <property name="articlesDao"> etiketiyle bir değer alır. Bu, [ArticlesManagerWithDataBase] sınıfında tanımlanan alandır:
public class ArticlesManagerWithDataBase implements IArticlesManager {

  // veri erişim arayüzü
  private IArticlesDao articlesDao;

  public IArticlesDao getArticlesDao() {
    return articlesDao;
  }

  public void setArticlesDao(IArticlesDao articlesDao) {
    this.articlesDao = articlesDao;
  }

[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:

public class TestSpringArticlesManagerWithDataBase extends TestCase {
    // iş sınıfını test eder [ArticlesManagerWithDataBase]

    // test edilen iş sınıfının bir örneği
    private IArticlesManager articlesManager;

    protected void setUp() throws Exception {
      // bir veri erişim örneğini alır
      articlesManager = (IArticlesManager) (new XmlBeanFactory(new ClassPathResource(
          "springArticlesManagerWithDataBase.xml"))).getBean("articlesManager");
    }

[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:

public class ArticlesManagerWithDataBase implements IArticlesManager {

  // veri erişim arayüzü
  private IArticlesDao articlesDao;

  public IArticlesDao getArticlesDao() {
    return articlesDao;
  }

  public void setArticlesDao(IArticlesDao articlesDao) {
    this.articlesDao = articlesDao;
  }

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:

    <bean id="articlesDao" class="istia.st.articles.dao.ArticlesDaoPlainJdbc">
...
    </bean>

örneğin şöyle olacaktır:

    <bean id="articlesDao" class="istia.st.articles.dao.ArticlesDaoIbatisSqlMap">
...
    </bean>

[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:

package istia.st.springioc.domain;

public class Personne {
  private String nom;
  private int age;

   // Kişi ekranı
  public String toString() {
    return "nom=[" + this.nom + "], age=[" + this.age + "]";
  }

   // başlatma-kapatma
  public void init() {
    System.out.println("init personne [" + this.toString() + "]");
  }

  public void close() {
    System.out.println("destroy personne [" + this.toString() + "]");
  }

   // alıcı-ayarlayıcılar
  public int getAge() {
    return age;
  }

  public void setAge(int age) {
    this.age = age;
  }

  public String getNom() {
    return nom;
  }

  public void setNom(String nom) {
    this.nom = nom;
  }
}

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:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" 
    "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <bean id="personne1" class="istia.st.springioc.domain.Personne" 
        init-method="init" destroy-method="close">
        <property name="nom">
            <value>Simon</value>
        </property>
        <property name="age">
            <value>40</value>
        </property>
    </bean>
    <bean id="personne2" class="istia.st.springioc.domain.Personne" 
        init-method="init" destroy-method="close">
        <property name="nom">
            <value>Brigitte</value>
        </property>
        <property name="age">
            <value>20</value>
        </property>
    </bean>
</beans>

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:

package istia.st.springioc.tests;

import istia.st.springioc.domain.Personne;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
import junit.framework.TestCase;

public class Tests extends TestCase {

   // bean fabrikası
  private ListableBeanFactory bf;

   // başlatma testleri
  public void setUp() {
    bf = new XmlBeanFactory(new ClassPathResource("config.xml"));
  }

  public void test1() {
    // Spring dosyasındaki [Personne] bean'lerini anahtarlarına göre alma
    Personne personne1 = (Personne) bf.getBean("personne1");
    System.out.println("personne1=" + personne1.toString());
    Personne personne2 = (Personne) bf.getBean("personne2");
    System.out.println("personne2=" + personne2.toString());
    personne2 = (Personne) bf.getBean("personne2");
    System.out.println("personne2=" + personne2.toString());
  }
}

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:

Image

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:

18 sept. 2004 11:28:53 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [config.xml]
18 sept. 2004 11:28:53 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'personne1'
init personne [nom=[Simon], age=[40]]
personne1=nom=[Simon], age=[40]
18 sept. 2004 11:28:53 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'personne2'
init personne [nom=[Brigitte], age=[20]]
personne2=nom=[Brigitte], age=[20]
personne2=nom=[Brigitte], age=[20]

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*
Personne personne1 = (Personne) bf.getBean("personne1");

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:

package istia.st.springioc.domain;

public class Voiture {
  private String marque;
  private String type;
  private Personne propriétaire;

   // oluşturucular

  public Voiture() {
  }

  public Voiture(String marque, String type, Personne propriétaire) {
    this.marque = marque;
    this.type = type;
    this.propriétaire = propriétaire;
  }

   // toString
  public String toString() {
    return "Voiture : marque=[" + this.marque + "] type=[" + this.type
        + "] propriétaire=[" + this.propriétaire + "]";
  }

     // alıcı-ayarlayıcılar
  public String getMarque() {
    return marque;
  }

  public void setMarque(String marque) {
    this.marque = marque;
  }

  public Personne getPropriétaire() {
    return propriétaire;
  }

  public void setPropriétaire(Personne propriétaire) {
    this.propriétaire = propriétaire;
  }

  public String getType() {
    return type;
  }

  public void setType(String type) {
    this.type = type;
  }

   // başlatma-kapatma
  public void init() {
    System.out.println("init voiture [" + this.toString() + "]");
  }

  public void close() {
    System.out.println("destroy voiture [" + this.toString() + "]");
  }

}

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:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" 
    "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <bean id="personne1" class="istia.st.springioc.domain.Personne" 
        init-method="init" destroy-method="close">
        <property name="nom">
            <value>Simon</value>
        </property>
        <property name="age">
            <value>40</value>
        </property>
    </bean>
    <bean id="personne2" class="istia.st.springioc.domain.Personne" 
        init-method="init" destroy-method="close">
        <property name="nom">
            <value>Brigitte</value>
        </property>
        <property name="age">
            <value>20</value>
        </property>
    </bean>
    <bean id="voiture1" class="istia.st.springioc.domain.Voiture" 
        init-method="init" destroy-method="close">
        <constructor-arg index="0">
            <value>Peugeot</value>
        </constructor-arg>
        <constructor-arg index="1">
            <value>307</value>
        </constructor-arg>
        <constructor-arg index="2">
            <ref bean="personne2"></ref>
        </constructor-arg>
    </bean>
</beans>

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:

    <bean id="voiture1" class="istia.st.springioc.domain.Voiture" 
        init-method="init" destroy-method="close">
        <property name="marque">
            <value>Peugeot</value>
        </property>
        <property name="type">
            <value>307</value>
        </property>
        <property name="propriétaire">
            <ref bean="personne2"/>
        </property>
    </bean>

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:

1
2
3
4
5
  public void test2() {
     // bean'in geri alınması [voiture1]
    Voiture Voiture1 = (Voiture) bf.getBean("voiture1");
    System.out.println("Voiture1=" + Voiture1.toString());
  }

[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:

18 sept. 2004 14:56:10 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [config.xml]
18 sept. 2004 14:56:10 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'voiture1'
18 sept. 2004 14:56:10 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'personne2'
init personne [nom=[Brigitte], age=[20]]
18 sept. 2004 14:56:10 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory autowireConstructor
INFO: Bean 'voiture1' instantiated via constructor [public istia.st.springioc.domain.Voiture(java.lang.String,java.lang.String,istia.st.springioc.domain.Personne)]
init voiture [Voiture : marque=[Peugeot] type=[307] propriétaire=[nom=[Brigitte], age=[20]]]
Voiture1=Voiture : marque=[Peugeot] type=[307] propriétaire=[nom=[Brigitte], age=[20]]

Açıklamalar:

  1. [test2] yöntemi, [voiture1] bean'ine bir referans talep eder
  2. 4. satır: Spring, [voiture1] bean'inin oluşturulmasına başlar, çünkü bu bean henüz oluşturulmamıştır (singleton)
  3. 6. satır: [voiture1] bean'i, [personne2] bean'ine referans verdiği için, bu son bean da sırayla oluşturulur
  4. 7. satır: [personne2] bean'i oluşturuldu. Ardından, bu bean'in [init] yöntemi yürütülür.
  5. 9. satır: Spring, [voiture1] bean'ini oluşturmak için bir oluşturucu kullanacağını belirtir
  6. 10. satır: [voiture1] bean'i oluşturuldu. Ardından [init] yöntemi çalıştırıldı.
  7. 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:

package istia.st.springioc.domain;

import java.util.Map;

public class GroupePersonnes {
  private Personne[] membres;
  private Map groupesDeTravail;

   // alıcılar - ayarlayıcılar
  public Personne[] getMembres() {
    return membres;
  }

  public void setMembres(Personne[] membres) {
    this.membres = membres;
  }

  public Map getGroupesDeTravail() {
    return groupesDeTravail;
  }

  public void setGroupesDeTravail(Map groupesDeTravail) {
    this.groupesDeTravail = groupesDeTravail;
  }

   // görüntüleme
  public String toString() {
    String liste = "membres : ";
    for (int i = 0; i < this.membres.length; i++) {
      liste += "[" + this.membres[i].toString() + "]";
    }
    return liste + ", groupes de travail = " + this.groupesDeTravail.toString();
  }

   // başlatma-kapatma
  public void init() {
    System.out.println("init GroupePersonnes [" + this.toString() + "]");
  }

  public void close() {
    System.out.println("destroy GroupePersonnes [" + this.toString() + "]");
  }
}

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:

    <bean id="groupe1" class="istia.st.springioc.domain.GroupePersonnes" 
        init-method="init" destroy-method="close">
        <property name="membres">
            <list>
                <ref bean="personne1"/>
                <ref bean="personne2"/>
            </list>
        </property>
        <property name="groupesDeTravail">
            <map>
                <entry key="Brigitte">
                    <value>Marketing</value>
                </entry>
                <entry key="Simon">
                    <value>Ressources humaines</value>
                </entry>
            </map>
        </property>
    </bean>
  1. <list> etiketi, dizi türünde olan veya List arayüzünü uygulayan bir alanı farklı değerlerle başlatmaya olanak tanır.
  2. <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:

1
2
3
4
5
  public void test3() {
    // [groupe1] bean'inin alınması
    GroupePersonnes groupe1 = (GroupePersonnes) bf.getBean("groupe1");
    System.out.println("groupe1=" + groupe1.toString());
  }

[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:

18 sept. 2004 15:51:45 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [config.xml]
18 sept. 2004 15:51:45 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'groupe1'
18 sept. 2004 15:51:45 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'personne1'
init personne [nom=[Simon], age=[40]]
18 sept. 2004 15:51:45 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'personne2'
init personne [nom=[Brigitte], age=[20]]
init GroupePersonnes [membres : [nom=[Simon], age=[40]][nom=[Brigitte], age=[20]], groupes de travail = {Brigitte=Marketing, Simon=Ressources humaines}]
groupe1=membres : [nom=[Simon], age=[40]][nom=[Brigitte], age=[20]], groupes de travail = {Brigitte=Marketing, Simon=Ressources humaines}

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:

Image

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

Image

[lib] klasöründeki dosyalar, projenin [ClassPath] klasörüne eklenmiştir.

2.4.6.2. [istia.st.demo.dao] paketi

Arayüz:

1
2
3
4
5
6
7
8
9
package istia.st.demo.dao;

/**
 * @author ST-ISTIA
 *  
 */
public interface IDao1 {
  public int doSometingInDaoLayer(int a, int b);
}

İlk uygulama sınıfı:

package istia.st.demo.dao;

/**
 * @author ST-ISTIA
 *  
 */
public class Dao1Impl1 implements IDao1 {

     // [dao] katmanında bir işlem yapılır
  public int doSometingInDaoLayer(int a, int b) {
    return a+b;
  }

}

İkinci sınıf uygulama:

package istia.st.demo.dao;

/**
 * @author ST-ISTIA
 *
 */
public class Dao1Impl2 implements IDao1 {

     // QZXW2HTML katmanında bir işlem yapılıyor [dao]
  public int doSometingInDaoLayer(int a, int b) {
    return a-b;
  }
}

2.4.6.3. [istia.st.demo.domain] paketi

Arayüz:

package istia.st.demo.domain;

/**
 * @author ST-ISTIA
 *  
 */
public interface IDomain1 {

     // [domain] katmanında bir şeyler yapıyoruz
  public int doSomethingInDomainLayer(int a, int b);
}

Birinci uygulama sınıfı:

package istia.st.demo.domain;

import istia.st.demo.dao.IDao1;

/**
 * @author ST-ISTIA
 *  
 */
public class Domain1Impl1 implements IDomain1 {

     // [dao] katmanına erişim hizmeti
  private IDao1 dao1;

  public Domain1Impl1() {
     // argüman içermeyen oluşturucu
  }

     // [dao] katmanına erişim hizmetini saklar
  public Domain1Impl1(IDao1 dao1) {
    this.dao1 = dao1;
  }

     // [domain] katmanında bir işlem gerçekleştirilir
  public int doSomethingInDomainLayer(int a, int b) {
    a++;
    b++;
    return dao1.doSometingInDaoLayer(a, b);
  }
}

İkinci sınıf uygulama:

package istia.st.demo.domain;

import istia.st.demo.dao.IDao1;

/**
 * @author ST-ISTIA
 *  
 */
public class Domain1Impl2 implements IDomain1 {

     // [dao] katmanına erişim hizmeti
  private IDao1 dao1;

  public Domain1Impl2() {
     // argüman içermeyen oluşturucu
  }

     // [dao] katmanına erişim hizmetini saklar
  public Domain1Impl2(IDao1 dao1) {
    this.dao1 = dao1;
  }

     // [domain] katmanında bir işlem gerçekleştirilir
  public int doSomethingInDomainLayer(int a, int b) {
    a--;
    b--;
    return dao1.doSometingInDaoLayer(a, b);
  }
}

2.4.6.4. [istia.st.demo.control] paketi

Arayüz

1
2
3
4
5
6
7
8
9
package istia.st.demo.control;

/**
 * @author ST-ISTIA
 *  
 */
public interface IControl1 {
  public int doSometingInControlLayer(int a, int b);
}

Birinci uygulama sınıfı:

package istia.st.demo.control;

import istia.st.demo.domain.IDomain1;

/**
 * @author ST-ISTIA
 *  
 */
public class Control1Impl1 implements IControl1 {
   // katmandaki iş sınıfı [domain]
    private IDomain1 domain1;

  public Control1Impl1() {
     // argüman içermeyen oluşturucu
  }

     // [domain] katmanına erişim hizmetinin depolanması
  public Control1Impl1(IDomain1 domain1) {
    this.domain1 = domain1;
  }

     // bir şey yapıyoruz
  public int doSometingInControlLayer(int a, int b) {
    a++;
    b++;
    return domain1.doSomethingInDomainLayer(a, b);
  }

}

İkinci sınıf bir uygulama:

package istia.st.demo.control;

import istia.st.demo.domain.IDomain1;

/**
 * @author ST-ISTIA
 *  
 */
public class Control1Impl2 implements IControl1 {

     // katmana erişim sınıfı [domain]
    private IDomain1 domain1;

  public Control1Impl2() {
     // argüman içermeyen oluşturucu
  }

     // katmana erişim sınıfını saklar [domain]
  public Control1Impl2(IDomain1 domain1) {
    this.domain1 = domain1;
  }

     // bir şey yapıyoruz
  public int doSometingInControlLayer(int a, int b) {
    a--;
    b--;
    return domain1.doSomethingInDomainLayer(a, b);
  }

}

2.4.6.5. [Spring] yapılandırma dosyaları

İlk [springMainTest1.xml] dosyası:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
     <!-- DAO sınıfı -->
    <bean id="dao" class="istia.st.demo.dao.Dao1Impl1">
    </bean>
     <!-- iş sınıfı -->
    <bean id="domain" class="istia.st.demo.domain.Domain1Impl1">
        <constructor-arg index="0">
            <ref bean="dao"/>
        </constructor-arg>
    </bean>
     <!-- denetim sınıfı -->
    <bean id="control" class="istia.st.demo.control.Control1Impl1">
        <constructor-arg index="0">
            <ref bean="domain"/>
        </constructor-arg>
    </bean>
</beans>

İkinci bir [springMainTest2.xml]:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
     <!-- DAO sınıfı -->
    <bean id="dao" class="istia.st.demo.dao.Dao1Impl2">
    </bean>
     <!-- iş sınıfı -->
    <bean id="domain" class="istia.st.demo.domain.Domain1Impl2">
        <constructor-arg index="0">
            <ref bean="dao"/>
        </constructor-arg>
    </bean>
     <!-- denetim sınıfı -->
    <bean id="control" class="istia.st.demo.control.Control1Impl2">
        <constructor-arg index="0">
            <ref bean="domain"/>
        </constructor-arg>
    </bean>
</beans>

2.4.6.6. [istia.st.demo.tests] test paketi

[main] türünde bir test:

package istia.st.demo.tests;

import istia.st.demo.control.IControl1;

import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

/**
 * @author ST-ISTIA
 *  
 */
public class MainTest1 {
  public static void main(String[] arguments) {
     // IControl1 arayüzünün bir uygulamasını alırız
    IControl1 control = (IControl1) (new XmlBeanFactory(new ClassPathResource(
        "springMainTest1.xml"))).getBean("control");
     // sınıfı kullanıyoruz
    int a = 10, b = 20;
    int res = control.doSometingInControlLayer(a, b);
     // sonuç görüntülenir
    System.out.println("control(" + a + "," + b + ")=" + res);
  }
}

Eclipse konsolunda elde edilen sonuçlar:

11 mars 2005 11:25:14 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [springMainTest1.xml]
11 mars 2005 11:25:14 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'control'
11 mars 2005 11:25:14 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'domain'
11 mars 2005 11:25:14 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'dao'
11 mars 2005 11:25:14 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory autowireConstructor
INFO: Bean 'domain' instantiated via constructor [public istia.st.demo.domain.Domain1Impl1(istia.st.demo.dao.IDao1)]
11 mars 2005 11:25:14 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory autowireConstructor
INFO: Bean 'control' instantiated via constructor [public istia.st.demo.control.Control1Impl1(istia.st.demo.domain.IDomain1)]
control(10,20)=34

İkinci yapılandırma dosyasını kullanan başka bir test: [Spring]:

package istia.st.demo.tests;

import istia.st.demo.control.IControl1;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

/**
 * @author ST-ISTIA
 *  
 */
public class MainTest2 {
  public static void main(String[] arguments) {
     // IControl1 arayüzünün bir uygulamasını alıyoruz
    IControl1 control = (IControl1) (new XmlBeanFactory(new ClassPathResource(
        "springMainTest2.xml"))).getBean("control");
     // sınıfı kullanıyoruz
    int a = 10, b = 20;
    int res = control.doSometingInControlLayer(a, b);
     // sonuç görüntülenir
    System.out.println("control(" + a + "," + b + ")=" + res);
  }
}

Eclipse konsolunda elde edilen sonuçlar:

11 mars 2005 11:28:52 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [springMainTest2.xml]
11 mars 2005 11:28:52 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'control'
11 mars 2005 11:28:52 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'domain'
11 mars 2005 11:28:52 org.springframework.beans.factory.support.AbstractBeanFactory getBean
INFO: Creating shared instance of singleton bean 'dao'
11 mars 2005 11:28:52 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory autowireConstructor
INFO: Bean 'domain' instantiated via constructor [public istia.st.demo.domain.Domain1Impl2(istia.st.demo.dao.IDao1)]
11 mars 2005 11:28:52 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory autowireConstructor
INFO: Bean 'control' instantiated via constructor [public istia.st.demo.control.Control1Impl2(istia.st.demo.domain.IDomain1)]
control(10,20)=-10

Son olarak, bir Junit testi:

package istia.st.demo.tests;

import istia.st.demo.control.IControl1;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
import junit.framework.TestCase;

/**
 * @author ST-ISTIA
 *  
 */
public class JunitTest2Control1 extends TestCase {
  public void testControl1() {
     // IControl1 arayüzünün bir uygulamasını alıyoruz
    IControl1 control1 = (IControl1) (new XmlBeanFactory(new ClassPathResource(
        "springMainTest1.xml"))).getBean("control");
     // sınıfı kullanıyoruz
    int a1 = 10, b1 = 20;
    int res1 = control1.doSometingInControlLayer(a1, b1);
    assertEquals(34, res1);
     // IControl1 arayüzünün başka bir uygulamasını alıyoruz
    IControl1 control2 = (IControl1) (new XmlBeanFactory(new ClassPathResource(
        "springMainTest2.xml"))).getBean("control");
     // sınıfını kullanıyoruz
    int a2 = 10, b2 = 20;
    int res2 = control2.doSometingInControlLayer(a2, b2);
    assertEquals(-10, res2);
  }
}

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.