Saturday, October 19, 2013

Spring Çatısına Kendi Injector Sınıfımızı Eklemek

Nesneye dayalı yaklaşım ile tasarım yapıldığında uyulması tavsiye edilen bir dizi ilke vardır. Bu ilkelerden biri yüksek uyum ve zayıf bağdır (=High Cohesion-Low Coupling). Uyumluluk bir sınıfın metotları için tanımlanır. Eğer sınıfın metotları aynı problemin çözümüne odaklanmışlar ise sınıfın metotları arasında bir uyumdan bahsedilir. Yazdığımız her sınıfa tek bir sorumluluk verirsek ya da tek bir konuda uzmanlaşmasını sağlarsak sınıf metotları arasında yüksek uyum sağlamış oluruz. Böylelikle sınıfı değiştirmek için tek bir nedenimiz olur. Ancak yüksek uyumluluk sağlamaya çalışırken, ortaya başka bir problem çıkar. Bir sınıf metodu işini yaparken kendi uzmanlığı dışında bir iş yapması gerektiğinde, bu işi uzmanına ihale edecektir. Bu durumda sınıflar arasında hizmet alma ve verme ilişkisi ortaya çıkar. Bir sınıf hem istemci hem de sunucu rolüne sahip olabilir. İstemci sınıf hizmet alacağı sınıfın referansını içerir. Böylelikle sınıflar arasında bağımlılıklar ortaya çıkacaktır. Bu bağımlılığı yönetebilmek gerekir. Bu bağımlılıktan dolayı oluşan bağın zayıf olmasını sağlamak üzere istemci sınıf hizmet alacağı sınıfın referansını doğrudan içermez. Bunun yerine istemci ile sunucu rollerindeki sınıflar arasında bir arayüz/sözleşme tanımlanır. Sınıflar bir birlerini doğrudan görmezler. İstemci sınıf bu sözleşmeye göre hizmet alır, servis sınıfı da bu sözleşmeye bağlı kalarak hizmet verir. İstemci sınıf ileride kodda değişiklik yapmadan hizmet alacağı sınıfı değiştirebilir. Nasıl cep telefon numaranızı değiştirmeden GSM operatörünüzü değiştirebiliyorsanız ya da internet servis sağlayıcınızı değiştirdiğinizde ADSL modeminizi değiştirmeniz gerekmiyorsa, istemci sınıf da hizmet aldığı sınıfı değiştirdiğinde kodda değişiklik yapması gerekmemelidir. Sözleşmeyi soyut sınıf ya da genellikle arayüz ile tanımlıyoruz. Bu şekilde istemci ve sunucu sınıflar arasındaki bağı gevşek tutmuş oluyoruz. Bu bağı daha da gevşetmenin yolu Java ya da C# programlama dillerindeki damgalama/not düşme yeteneğini kullanmaktır. Bu yaklaşımı genellikle bileşen tabanlı yazılım geliştirmede kullanıyoruz. Bileşenler aynı sınıflardan yarattığımız nesneler gibidir. Ancak onlardan farklı olarak yaşayabilmeleri, var olabilmeleri için bir barınağa ya da kaba (=container) ihtiyaç duyarlar. Barınak bileşenlerin yaşam döngülerini yönetir ve bazı sık kullanılan hizmetler sunar. Kurumsal Java uygulamalarındaki uygulama sunucuları Web kabı ve EJB kabı olmak üzere iki kap ve bileşen modeli içerir. Web kabında Servlet ve JSP bileşen modelleri yer alır. EJB kabında ise Session Bean, Message Driven Bean bileşen modelleri bulunur. Bu bileşenler, uygulama sunucusunun sağladığı hizmetlerden standart bir dizi damga yardımıyla yararlanır.
Arayüz kullanarak bağı zayıflatmış olsak da yürütme zamanında istemci sınıfa hangi sınıftan hizmet alacağını söylememiz gerekir. Yani bağımlılıkların çözülmesi gerekir. Karmaşık sınıf ilişkilerinin olduğu bir yazılımda bağımlılıkları görmek ve düzgün sırada bağımlıkları çözmek zordur. Bunun için genellikle Fabrika Kalıbı kullanılır. Karmaşık nesneleri yaratma sorumluluğu bu fabrika sınıflarına verilir. Ancak bu durumda çok sayıda fabrika tasarlamak gerekir. Üstelik nesnelerin  bağlantıları değiştirildiğinde fabrika sınıfını değiştirmek ya da yeni bir fabrika sınıfı yazmak gerekecektir. Spring çatısı bu bağları otomatik olarak çözebilen genelleştirilmiş bir fabrika sunar. Bu fabrika her türlü sınıftan nesneyi tüm bağımlılıkları ile birlikte yaratabilme yeteneğine sahiptir. Bunun dışında istersek biz de kendi Injector sınıfımızı yazmamıza imkan verir. Bu yazının konusu bu tür bir Injector sınıfının nasıl yazılacağı ve Spring IoC kabına tanıtılması ile ilgilidir.
Bunun için org.springframework.beans.factory.config.BeanPostProcessor arayüzünü gerçekleyen bir sınıf, com.example.mod04.processors.RandomPostProcessor, tasarlıyoruz:

1:  package com.example.mod04.processors;  
2:    
3:  import com.example.mod04.annotation.RandomNumberGenerator;  
4:  import org.springframework.beans.BeansException;  
5:  import org.springframework.beans.factory.config.BeanPostProcessor;  
6:    
7:  public class RandomPostProcessor implements BeanPostProcessor {  
8:    
9:       @Override  
10:       public Object postProcessAfterInitialization(Object bean, String beanname) throws BeansException {  
11:            RandomNumberGenerator.inject(bean);  
12:            return bean;  
13:       }  
14:    
15:       @Override  
16:       public Object postProcessBeforeInitialization(Object bean, String beanname) throws BeansException {  
17:            return bean;  
18:       }  
19:    
20:  }  

Buradaki RandomRandomGenerator sınıfının kodunu aşağıda bulabilirsiniz:
1:  package com.example.mod04.annotation;  
2:    
3:  import java.lang.reflect.*;  
4:  import java.util.Collections;  
5:  import java.util.List;  
6:    
7:  public class RandomNumberGenerator {  
8:       public static int random(int min, int max) {  
9:            int len = max - min + 1;  
10:            return (int) (Math.random() * len + min);  
11:       }  
12:    
13:       public static void inject(Object obj) {  
14:            Class<? extends Object> cls = obj.getClass();  
15:            for (Field field : cls.getDeclaredFields()) {  
16:                 if (field.isAnnotationPresent(Random.class)) {  
17:                      Class<? extends Object> type = field.getType();  
18:                      Random random = field.getAnnotation(Random.class);  
19:                      int minValue = random.minValue();  
20:                      int maxValue = random.maxValue();  
21:                      int size = random.size();  
22:                      boolean sorted = random.sorted();  
23:                      boolean unique = random.unique();  
24:                      field.setAccessible(true);  
25:                      if (type.isPrimitive()) {  
26:                           try {  
27:                                field.set(obj, random(minValue, maxValue));  
28:                           } catch (IllegalArgumentException | IllegalAccessException e) {  
29:                                e.printStackTrace();  
30:                                return;  
31:                           }  
32:                      }   
33:                      else if (type.equals(List.class)) {  
34:                           List<Integer> list;  
35:                           try {  
36:                                list = (List<Integer>) field.get(obj);  
37:                           } catch (IllegalArgumentException | IllegalAccessException e) {  
38:                                e.printStackTrace();  
39:                                return;  
40:                           }  
41:                           for (int i = 0; i < size; ++i) {  
42:                                int candidate;  
43:                                do {  
44:                                     candidate = random(minValue, maxValue);  
45:                                     if (!unique)  
46:                                          break;  
47:                                } while (list.contains(candidate));  
48:                                list.add(candidate);  
49:                           }  
50:                           if (sorted)  
51:                                Collections.sort(list);  
52:                      }  
53:                 }  
54:            }  
55:       }  
56:  }  

Random damgasının tanımı aşağıda verilmiştir. Random damgası sadece sınıf özniteliklerine uygulanabilir, yürütme zamanında tip bilgisine, Class sınıfı üzerinden erişilebilinir. Random damgasının minValue, maxValue, unique, sorted, size öznitelikleri ile rastgele sayı üretilmesi sağlanmaktadır. Bu damgayı RandomNumberGenerator hizmet sınıfı kullanmaktadır. Bu sınıfın tek bir metodu bulumaktadır: inject. Bu metot parametre olarak Object sınıfından bir referans alır. java.lang.reflect paketini kullanarak nesnenin tip bilgisine erişerek, Random damgası ile damgalanmış bir özniteliği olup olmadığını sorgular. Bu damga ile damgalanmış bir özniteliği varsa damganın minValuemaxValueuniquesortedsize değerlerine uygun olarak bir rastgele değer belirler ve  nesnenin özniteliğinin değerini bu değer ile değiştirir. Random damgasının minValuemaxValueuniquesortedsize değerlerinin hepsi için bir varsayılan değer tanımlanmıştır. Dolayısı ile sadece @Random() ile damgalama yapılabilinir.
1:  package com.example.mod04.annotation;  
2:    
3:  import java.lang.annotation.Documented;  
4:  import java.lang.annotation.ElementType;  
5:  import java.lang.annotation.Retention;  
6:  import java.lang.annotation.RetentionPolicy;  
7:  import java.lang.annotation.Target;  
8:  @Documented  
9:  @Target(ElementType.FIELD)  
10:  @Retention(RetentionPolicy.RUNTIME)  
11:  public @interface Random {  
12:       int minValue() default 1;  
13:       int maxValue() default 100;  
14:       boolean unique() default false;  
15:       boolean sorted() default false;  
16:       int size() default 1;   
17:  }  
Şimdi bir kaç Random damgası ile damgalanmış sınıf örneğine bakalım:
1:  package com.example.mod04.domain;  
2:  import com.example.mod04.annotation.Random;  
3:    
4:    
5:  public class Circle {  
6:       @Random() private int x;  
7:       @Random() private int y;  
8:       @Random(minValue=20,maxValue=40) private int radius;  
9:         
10:       public Circle() {            
11:       }  
12:         
13:       public Circle(int x, int y, int radius) {  
14:            this.x = x;  
15:            this.y = y;  
16:            this.radius = radius;  
17:       }  
18:    
19:       public int getX() {  
20:            return x;  
21:       }  
22:       public void setX(int x) {  
23:            this.x = x;  
24:       }  
25:       public int getY() {  
26:            return y;  
27:       }  
28:       public void setY(int y) {  
29:            this.y = y;  
30:       }  
31:       public int getRadius() {  
32:            return radius;  
33:       }  
34:       public void setRadius(int radius) {  
35:            this.radius = radius;  
36:       }  
37:    
38:       @Override  
39:       public String toString() {  
40:            return "Circle [x=" + x + ", y=" + y + ", radius=" + radius + "]";  
41:       }  
42:  }      

1:  package com.example.mod04.domain;  
2:    
3:  import java.util.ArrayList;  
4:  import java.util.List;  
5:    
6:  import com.example.mod04.annotation.Random;  
7:    
8:  public class Lottery extends Object {  
9:       @Random(minValue=1,maxValue=49,size=6,unique=true,sorted=true)   
10:       private List<Integer> numbers;  
11:    
12:       public Lottery() {  
13:            numbers= new ArrayList<Integer>();  
14:       }  
15:    
16:       @Override  
17:       public String toString() {  
18:            return "Lottery "+numbers;  
19:       }  
20:    
21:       public List<Integer> getNumbers() {  
22:            return numbers;  
23:       }  
24:  }  
Spring IoC kabının yapılandırma dosyası, spring-config.xml, ise aşağıdaki gibi oluşturulmalıdır:
1:  <?xml version="1.0" encoding="UTF-8"?>  
2:  <beans xmlns="http://www.springframework.org/schema/beans"  
3:       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
4:       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">  
5:     <bean class="com.example.mod04.processors.RandomPostProcessor"/>  
6:     <bean id="cember" class="com.example.mod04.domain.Circle"/>  
7:     <bean id="sayisal" class="com.example.mod04.domain.Lottery"/>   
8:  </beans>  
Örnek uygulama ve çıktısı aşağıda verilmiştir:
1:  package com.example.mod04.console;  
2:    
3:  import com.example.mod04.domain.Circle;  
4:  import com.example.mod04.domain.Lottery;  
5:    
6:  import org.springframework.context.ConfigurableApplicationContext;  
7:  import org.springframework.context.support.ClassPathXmlApplicationContext;  
8:    
9:  public class TestRandomPostProcessor {  
10:    
11:       /**  
12:        * @param args  
13:        */  
14:       public static void main(String[] args) {  
15:            ConfigurableApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");  
16:            Circle circle= context.getBean("cember",Circle.class);  
17:            System.out.println(circle);  
18:            Lottery sayisal= context.getBean("sayisal",Lottery.class);  
19:            System.out.println(sayisal);  
20:            context.close();  
21:       }  
22:  }  

 Oct 19, 2013 12:02:21 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh  
 INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@17ac9cff: startup date [Sat Oct 19 12:02:21 EEST 2013]; root of context hierarchy  
 Oct 19, 2013 12:02:21 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions  
 INFO: Loading XML bean definitions from class path resource [beans.xml]  
 Oct 19, 2013 12:02:21 PM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons  
 INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@7393ccd5: defining beans [com.example.mod04.processors.RandomPostProcessor#0,cember,sayisal]; root of factory hierarchy  
 postProcessAfterInitialization()  
 postProcessAfterInitialization()  
 Circle [x=50, y=17, radius=33]  
 Lottery [7, 14, 23, 34, 35, 43]  
 Oct 19, 2013 12:02:21 PM org.springframework.context.support.AbstractApplicationContext doClose  
 INFO: Closing org.springframework.context.support.ClassPathXmlApplicationContext@17ac9cff: startup date [Sat Oct 19 12:02:21 EEST 2013]; root of context hierarchy  
 Oct 19, 2013 12:02:21 PM org.springframework.beans.factory.support.DefaultSingletonBeanRegistry destroySingletons  
 INFO: Destroying singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@7393ccd5: defining beans [com.example.mod04.processors.RandomPostProcessor#0,cember,sayisal]; root of factory hierarchy  
Projenin tamamını Maven projesi olarak bu adresten indirebilirsiniz. Spring konusunda daha fazla bilgi edinmek isterseniz bu bağlantıdaki Spring sınıf eğitimlerini tavsiye ederim.

No comments:

Post a Comment