Bu yazımda Dependency Injection'ın Spring framework'te nasıl uygulandığını anlatmaya çalışacağım.
Bildiğiniz gibi dependency injection class'ların birbiriyle olan bağımlılıklarını azalmak için kullanılan bir yöntemdir.
Örnek verecek olursak;
Elimizde "A" ve "B" class'ları olsun. "A" class'ında "B" nesnesi üretildiğini düşünelim. Şuan "A" class'ı "B" class'ına bağlı hale geldi. Bunların bağımlılıklarını azaltmamız gerekmektedir. Bu sebepten A class'ı içinde bir B nesnesini new anahtar kelimesiyle yaratmak yerine dışarıdan bunu vereceğiz.
Dışarıdan verebilmemiz için setter veya constructor kullanmamız gerekmektedir.
Peki bu constructor veya setter methodu içine B class'ı tipinde bir nesne mi alması gerekiyor? Tabikide hayır.
Bu işlem şu şekilde olmalıdır.
A class'ı setter metodu veya constructor'a bir interface tipinde nesne istemeli. Bu interfce tipindeki nesnenin ismini Ib olarak tanımladık diyelim.
Daha sonra B class'ı bu Ib interfacesini implement edip gerekli metodlarını override eder. Şuan B nesnesi Ib interfacesini impelemt ettiği için A class'ınının setter'ına veya constructor'ına parametre olarak geçilebilir.
Peki A setter veya constructor Ib interface nesnesini parametre olarak isterken nasıl B class'ı tipinde bir nesneyi parametre olarak gönderebileceğiz?
Bu sorunun cevabı Java tarafından sağlanan POLIMORFIZM'dir. Anlayamayanlar java polimorfizm konusunu detaylı incelemelidir.
Eğer yapıyı bu şekle getirebilirsek bir C class'ı eğer Ib interfacesini implement edip, gerekli metodları override ederse, C class'ını da A setter metod veya constructor'a parametre olarak geçebiliriz.
( Parametre olarak geçirilen kısım Ib interfacesine casting ediliyor diye düşünebiliriz.)
Biraz kafanız karışmış olmuş olabilir. Bu konuyu daha iyi anlamak için öncelikle Dependency Injection ( DI ) yazımı okumalıdır.
Spring DI
Spring framework'ünün en büyük özelliklerden birisi dependency injection desteği sağlamasıdır.
Spring'de iki tür injection vardır. Bunlar Setter Injection ve Constructor Injection.
Örnek üzerinden açıklamaya çalışa.
1.Setter Injection
import java.util.List; public class SearchEngine1 { private Search searchAlgorithm; public Search getSearchAlgorithm() { return searchAlgorithm; } public void setSearchAlgorithm(Search searchAlgorithm) { this.searchAlgorithm = searchAlgorithm; } public boolean search( List<Integer> list, int id ) { return searchAlgorithm.find(list, id); } }
import java.util.List; public interface Search { public boolean find( List<Integer> list, int id ); }
import java.util.Collections; import java.util.List; public class BinarySearch implements Search { @Override public boolean find(List<Integer> list, int id) { System.out.println("Binary Search"); Collections.sort( list ); return Collections.binarySearch(list, id)>=0; } }
import java.util.List; public class SequentialSearch implements Search { @Override public boolean find(List<Integer> list, int id) { for( Integer i : list ) { if( i == id ) return true; } System.out.println("Sequential Search"); return false; } }
SequentialSearch class'ını incelersek, Search interfacesini implement etmiş ve find() metodunu Override etmiş. find() metoduna kendi search algoritmasını uygulamış.
BinarySearch ve SequentialSearch class'larını incelersek ikiside Search interfacesini implement etmiş. Yani ikiside SearchEngine1 classında bulunan Search searchAlgorithm nesnesinin set edilmesi için kullanılan setSearchAlgorithm( ) metoduna parametre olarak verilebilir. Çünkü bu iki class'ta Search interfacesini implement edip ilgili metodu override etmiş durumdalar.
Bu özellik bize SearchEngine1 class'ının setter metoduna verilen algoritma tipine göre search yeteneği kazandırmış oldu. Bu sayede esnelik kazandık. İleride başka bir search algoritması üretilip uygulanmak istenirse SearchEngine1 içinde hiçbir değişiklik yapılmadan uygulanabilir.
Şimdi sıra geldi Spring'te bunu uygulamaya. Spring'te injection özelliği xml üzerinden yapılmaktadır.
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.com/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd"> <bean id="binarySearch" class="com.serdarkocerr.spring.search.BinarySearch" /> <bean id="sequentialSearch" class="com.serdarkocerr.spring.search.SequentialSearch" /> <bean id="searchEngineV1" class="com.serdarkocerr.spring.search.SearchEngine1" > <property name="searchAlgorithm" ref="binarySearch" /> <!-- Setter Injection --> </bean> </beans>Yukarıdaki xml konfigürasyon dosyasını inceleyelim. Bütün class'lar bean olarak tanılmış.
SearchEngine1 class'ı içerisinde deklare edilen
private Search searchAlgorithm
nesnesine bir atama yapılmalıdır. Yoksa nullPointerException hatası olur. Bu kısımda Spring'in bize sunduğu injection özelliği devreye girmektedir. Injection xml dosyasından direk yapılabilmektedir. Bu sayede bir nesne üretip setter fonksiyonuna gidip bir bir nesne üretip ardından nesneyi parametre olarak vermiyoruz. Hiç böyle kodlamalara girmeden Xml üzerinden rahat bir şekilde yapıyoruz.
searchAlgorithm interface nesnesine referans verilirken interface nesnesi gerekmektedir. Bean olarak tanıtılmış olan binarySearch ve sequentialSearch class'ları bu interfaceyi implement ettiğinden referans olarak verilebilir. Bu kısım bize Dependency Injection'ı sağlar. Çünkü referans olarak bu interfaceyi implement etmiş her nesneyi verebiliriz. Bize esneklik sağlar. İleride üretilecek başka search algoritmaları için searchEngine1 içinde değişiklik yapılmadan uygulanabilirlik sağlanmış olur.
Setter injection da ilgili interface nesnesi için setter metodu yazılması gerekiyor. Bu setter metounun beklediği nesnenin ismi (Search searchAlgorithm) ile referans olarak verilen isim name="searchAlgorithm" aynı olmalıdır. Yani hangi isme karşılık referans olarak bu bean veriliyorun cevabı gibi düşünülebilir.
Sonuç olarak setter fonksiyonuna verilen parametrenin ismi ise bean'de verilen referansın ismi aynı olmalıdır.
2.Constructor Injection
Bu injection tipinde constructorlar üzerinden dependency injection uygulanır.
import java.util.List; public class SearchEngine2 { private Search searchAlgorithm; public SearchEngine2( Search searchAlgorithm ) { this.searchAlgorithm = searchAlgorithm; } public boolean search( List<Integer> list, int id ) { return searchAlgorithm.find(list, id); } }
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.com/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd"> <bean id="binarySearch" class="com.serdarkocerr.spring.search.BinarySearch" /> <bean id="sequentialSearch" class="com.serdarkocerr.spring.search.SequentialSearch" /> <bean id="searchEngineV2" class="com.serdarkocerr.spring.search.SearchEngine2" > <constructor-arg name="searchAlgorithm" ref="sequentialSearch" /> </bean> </beans>
SearchEngine2 class'ına dikkat edilirse constructor içine bir Search interface tipinde nesne alıyor.
.xml dosyasını incelersek SearchEngine2 beanı tanımlanırken constructor üzerinden bir referans verilmiş. Bu referans bean id olarak verilir. sequentialSearch bean id'yi temsil eden SequentialSearch class'ı ise Search interfacesini implement etmişti yani referans olarak verilebilir. Başka bir search algoritması da eğer Search interfacesini implement ettiyse constructor'a parametre olarak geçilebilir. Bu sayede büyük bir esneklik kazanmış oluyoruz. Dependency Injection bu şekilde sağlanmış oluyor.
Örneklerimizde hem Setter hem de Constructor Injection özelliklerini kullanarak Dependency Injection mimarisini başarı bir şekilde uygulamış olduk. Spring bize injection bakımından çok fazla kodlama yapmamız konusunda yardımcı oluyor.
Aşağıdaki constructor'ı incelersek
Search tipinde bir interface constructor'a parametre olarak verilmeli.
İsmine dikkat edersek nesnenin ismi searchAlgorithm
public SearchEngine2( Search searchAlgorithm ) { this.searchAlgorithm = searchAlgorithm; }Daha sonra aşağıdaki bean tanımlamasını incelersek,
constructor 'a bir referans geçilmeli. Bu referans geçilecek nesnenin ismi searchAlgorithm olduğunu görmekteyiz.
<bean id="searchEngineV2" class="com.serdarkocerr.spring.search.SearchEngine2" > <constructor-arg name="searchAlgorithm" ref="sequentialSearch" /> </bean>
Yani sonuç olarak constructor'a tanıtılan parametre isimleri hep aynı olmalıdır.
Kaynaklar:
http://www.kazimsoylu.com/java/spring-framework-ile-dependency-injection.html