Tuesday, October 29, 2013

Spring Çatısında Özyinelemeli Metotlar İçin İlgiye Yönelik Programlama Çözümü

Kaliteli yazılım geliştirmek için Nesneye Dayalı Programlama, İlgiye Dayalı Programlama, Üretken Programlama, Fonksiyonel Programlama gibi birçok yaklaşımı beraber kullanmak gerekir. Bu yaklaşımlar genellikle biri birilerine rakip teknikler olmayıp diğerinin eksikliğini ya da yetersizliğini kapatırlar. Sınıflar arasındaki bağı zayıflatmak için istemci ve sunucu rollerindeki sınıflar arasında bir arayüz tanımlıyoruz:
public interface SequenceService {
      long elementAt(int n);
}  
Bu arayüzü gerçekleyen servis rolündeki bir sınıfı Spring çatısında aşağıdaki gibi gerçekleştiriyoruz:
@Service("fibonacci")
public class FibonacciService implements SequenceService {
      @Override
      public long elementAt(int n) {
            if (n == 0 || n == 1)
                  return n;
            return elementAt(n - 1) + elementAt(n - 2);
      }
}
FibonacciService sınıfı Fibonacci dizisinin n-yinci değerini özyinelemeli bir şekilde hesaplamaktadır. Bu sınıfın örnek bir uygulamasını aşağıda bulabilirsiniz:
public class TestFibonacciService {
   public static void main(String[] args) {
       ConfigurableApplicationContext context=
              new ClassPathXmlApplicationContext("spring-config.xml");
       int n=100;
       SequenceService sequenceService= 
                       (SequenceService) context.getBean("fibonacci");
       System.out.println("Fibonacci("+n+") is " 
                          sequenceService.elementAt(n));
       context.close();
   }
}
Bu kodu çalıştırdığınızda, uygulamanın kısa zamanda sonlanmadığını fark edeceksiniz. Fibonacci dizisinin yukarıdaki gibi hesaplanması durumunda, işlem sayısı O(2n) biçiminde artmaktadır. Ancak dikkatlice bakıldığında aynı parametreli çağrıların defalarca yapıldığı rahatlıkla görülebilinir. Çözüm için ön bellek kullanılır. Aynı parametreli çağrılardan ilkinin sonucu ön belleğe atılır ve ondan sonraki çağrılarda ise ön bellekteki sonuç kullanılır. Ön bellek çözümü Spring tarafından sağlanmaktadır. Ön bellekleme için sınıfın ilgili metoduna @Cacheable notunu düşmek yeterli olur:
@Service("fibonacci")
public class FibonacciService implements SequenceService {

      @Cacheable(value = "numbers", key = "#n")
      @Override
      public long elementAt(int n) {
            if (n == 0 || n == 1)
                return n;
            return elementAt(n - 1) + elementAt(n - 2);
      }

}
Yukarıdaki örnek uygulamayı tekrar çalıştırdığınızda sürenin değişmediğini göreceksiniz. İlgiye yönelik programlama çözümü Java platformunda dinamik vekil kalıbı kullanılarak sağlanır. Bu kalıpta çağrıyı yapan ile sağlayıcı sınıf arasına bir vekil girer. Her vekilin ilgilendiği bir sorumluluk vardır. Bu sorumluluk günlük oluşturma, yetkilendirme, başarım ölçümü ya da ön bellekleme olabilir. Bu kalıp, sınıfın bir metodunun sınıfın bir başka metodunu çağırdığı durumlarda ya da özyinelemeli çağrılarda işe yaramaz. Bu problem, sınıfa vekilin referansını ekleyerek ve metot çağrısını bu referans üzerinden yaparak aşılabilir:
import javax.annotation.PostConstruct;

import org.springframework.beans.BeansException;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Service;

@Service("fibonacci")
public class FibonacciService implements SequenceService, ApplicationContextAware {
  private ApplicationContext context;
  private SequenceService self;

  @Cacheable(value = "numbers", key = "#n")
  @Override
  public long elementAt(int n) {
    if (n == 0 || n == 1)
       return n;
    return self.elementAt(n - 1) + self.elementAt(n - 2);
  }

  @PostConstruct
  private void init() {
     self = (SequenceService) context.getBean("fibonacci");
  }

  @Override
  public void setApplicationContext(ApplicationContext context) throws BeansException {
     this.context = context;
  }
}
Spring yapılandırma dosyası, spring-config.xml, ise aşağıda verilmiştir:
<?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:aop="http://www.springframework.org/schema/aop"
      xmlns:context="http://www.springframework.org/schema/context"
      xmlns:cache="http://www.springframework.org/schema/cache"
      xmlns:p="http://www.springframework.org/schema/p"
      xsi:schemaLocation="http://www.springframework.org/schema/aop 
         http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
         http://www.springframework.org/schema/beans 
         http://www.springframework.org/schema/beans/spring-beans.xsd
         http://www.springframework.org/schema/cache 
         http://www.springframework.org/schema/cache/spring-cache-3.2.xsd
         http://www.springframework.org/schema/context 
   http://www.springframework.org/schema/context/spring-context-3.2.xsd">
      <aop:aspectj-autoproxy />
      <context:annotation-config />
      <cache:annotation-driven />
      <context:component-scan base-package="com.example.mod06.service" />
      <bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
        <property name="caches">
           <set>      <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="default" />
 <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="numbers" />
           </set>
        </property>
    </bean>
</beans>
Yukarıdaki örnek uygulamayı tekrar çalıştırdığınızda uygulamanın hemen sonuca hızlıca ulaştığını göreceksiniz:
Oct 29, 2013 11:31:14 AM org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@561ba49d: startup date [Tue Oct 29 11:31:14 EET 2013]; root of context hierarchy
Oct 29, 2013 11:31:14 AM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [spring-config.xml]
Oct 29, 2013 11:31:14 AM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@7b70a0d3: defining beans [org.springframework.aop.config.internalAutoProxyCreator,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.cache.annotation.AnnotationCacheOperationSource#0,org.springframework.cache.interceptor.CacheInterceptor#0,org.springframework.cache.config.internalCacheAdvisor,auditAspect,cache,accountService,fibonacci,memoryAccountDao,cacheManager,org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor]; root of factory hierarchy
fibonacci(100) is 3736710778780434371
Oct 29, 2013 11:31:15 AM org.springframework.context.support.AbstractApplicationContext doClose
INFO: Closing org.springframework.context.support.ClassPathXmlApplicationContext@561ba49d: startup date [Tue Oct 29 11:31:14 EET 2013]; root of context hierarchy
Oct 29, 2013 11:31:15 AM org.springframework.beans.factory.support.DefaultSingletonBeanRegistry destroySingletons

INFO: Destroying singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@7b70a0d3: defining beans [org.springframework.aop.config.internalAutoProxyCreator,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.cache.annotation.AnnotationCacheOperationSource#0,org.springframework.cache.interceptor.CacheInterceptor#0,org.springframework.cache.config.internalCacheAdvisor,auditAspect,cache,accountService,fibonacci,memoryAccountDao,cacheManager,org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor]; root of factory hierarchy

Çözümün kaynak koduna Maven projesi olarak bu bağlantıdan erişebilirsiniz. Yukarıdaki çözüm bizi amacımıza ulaştırmış olsa da çok temel bir tasarım problemi barındırmaktadır. FibonacciService sınıfı Spring çatısı ve özellikle AspectJ ile ilgili bilgi içermektedir. Spring, ilgiye yönelik programlamada her zaman vekil tabanlı bir çözüm sunar. Bu yüzden de öz yinelemeli çağrılar için yukarıda verilen yaklaşım dışında bir çözüm bulunmamaktadır. AspectJ ise daha farklı bir yaklaşım kullanır. Yazdığımız sınıflara, derleme aşamasında ya da çalıştırma zamanında sınıfları yüklerken, ilgileri sınıfa dokuyarak değişiklikler yapar. Eğer bu değişiklik sınıf yükleme aşamasında yapılıyorsa, buna Yükleme Zamanında Dokuma (=Load-Time Weaving) adını veriyoruz. Spring çatısında bunun nasıl uygulandığına bir bakalım. Önce Spring yapılandırma dosyasında (spring-config.xml) bununla ilgili bir tanımlama yapmamız gerekecek:

<?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.org/schema/context"
     xmlns:aop="http://www.springframework.org/schema/aop"
     xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
           http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  <context:annotation-config/>
  <context:component-scan base-package="com.example.service"/>
  <bean id="cache" class="com.example.aop.CachingAspect"/>
  <context:load-time-weaver/>
  <aop:aspectj-autoproxy proxy-target-class="true">
    <aop:include name="cache"/>
  </aop:aspectj-autoproxy>

</beans>

İkinci olarak AspectJ için bir yapılandırma dosyası ekliyoruz, META-INF/aop.xml:

<?xml version="1.0" encoding="UTF-8"?>
<aspectj >
     <weaver options="-Xreweavable -verbose">
           <include within="com.example.service.*" />
           <include within="com.example.aop.*" />
     </weaver>
     <aspects>
           <aspect name="com.example.aop.CachingAspect" />
     </aspects>
</aspectj>

Cep bellek ilgisini ise basitçe AspectJ damgalarını kullanarak aşağıdaki gibi kodluyoruz:


package com.example.aop;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class CachingAspect {
     private Map<Integer,Object> cache= new ConcurrentHashMap<>();
     @Around("methodsToBeCached()")
     public Object cache(ProceedingJoinPoint pjp) throws Throwable {
        Object[] params= pjp.getArgs();
        int key= 0;
        if (params!=null && params.length>0){
             key= params[0].hashCode();
        }
        Object value= cache.get(key);
        if (value==null){
           value=  pjp.proceed();
           cache.put(key, value);
        }
        return value;
     }
     @Pointcut("execution(public * com.example..*Service.*(..))")
     public void methodsToBeCached() {
     }
}

Son olarak, bu uygulamayı çalıştırırken, sınıf yüklenirken bu dokuma işini yapacak temsilciyi Java Sanal Makinesine tanıtmak gerekecektir:

java -javaagent:aspectjweaver-1.7.2.jar -javaagent:spring-instrument-3.2.4.RELEASE.jar com.example.App

Uygulama çalıştırıldığında aşağıdaki ekran çıktısını üretmektedir:

[AppClassLoader@65fe28a7] info AspectJ Weaver Version 1.7.2 built on Friday Feb 15, 2013 at 18:40:45 GMT
[AppClassLoader@65fe28a7] info register classloader sun.misc.Launcher$AppClassLoader@65fe28a7
[AppClassLoader@65fe28a7] info using configuration /C:/workspace/workspace-spring-core-oct-2013-thy/aop-aspectj-ltw/target/classes/META-INF/aop.xml
[AppClassLoader@65fe28a7] info using configuration file:/C:/Users/bkurt/.m2/repository/org/springframework/spring-aspects/3.2.4.RELEASE/spring-aspects-3.2.4.RELEASE.jar!/META-INF/aop.xml
[AppClassLoader@65fe28a7] info register aspect com.example.aop.CachingAspect
[AppClassLoader@65fe28a7] info register aspect org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect
[AppClassLoader@65fe28a7] info register aspect org.springframework.scheduling.aspectj.AnnotationAsyncExecutionAspect
[AppClassLoader@65fe28a7] info register aspect org.springframework.transaction.aspectj.AnnotationTransactionAspect
[AppClassLoader@65fe28a7] info register aspect org.springframework.cache.aspectj.AnnotationCacheAspect
[AppClassLoader@65fe28a7] info weaver operating in reweavable mode.  Need to verify any required types exist.
.
.
.
Nov 01, 2013 9:20:02 PM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@51b30472: defining beans [org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,fibonacci,cache,org.springframework.context.weaving.AspectJWeavingEnabler#0,org.springframework.context.config.internalBeanConfigurerAspect,loadTimeWeaver,org.springframework.aop.config.internalAutoProxyCreator,org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor]; root of factory hierarchy
[AppClassLoader@65fe28a7] info processing reweavable type com.example.aop.CachingAspect: com\example\aop\CachingAspect.java
[AppClassLoader@65fe28a7] info successfully verified type com.example.aop.CachingAspect exists.  Originates from com\example\aop\CachingAspect.java
Nov 01, 2013 9:20:02 PM org.springframework.context.support.AbstractApplicationContext doClose
INFO: Closing org.springframework.context.support.ClassPathXmlApplicationContext@74eb011d: startup date [Fri Nov 01 21:20:01 EET 2013]; root of context hierarchy
fibonacci(100)=3736710778780434371
Nov 01, 2013 9:20:02 PM org.springframework.beans.factory.support.DefaultSingletonBeanRegistry destroySingletons
INFO: Destroying singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@51b30472: defining beans [org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,fibonacci,cache,org.springframework.context.weaving.AspectJWeavingEnabler#0,org.springframework.context.config.internalBeanConfigurerAspect,loadTimeWeaver,org.springframework.aop.config.internalAutoProxyCreator,org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor]; root of factory hierarchy
Nov 01, 2013 9:20:02 PM org.springframework.context.weaving.DefaultContextLoadTimeWeaver destroy
INFO: Removing all registered transformers for class loader: sun.misc.Launcher$AppClassLoader

Bu uygulamayı Maven projesi olarak bu bağlantıdan 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