AspectJ – Statik Crosscutting Özellikler

AspectJ dilinde, tavsiye yordamıyla dinamik crosscutting (enine kesen) özelliğini gerçekleştirerek sistemin davranışını şekillendirirken, statik crosscutting özellikleri ile sistemde bulunan tiplerin — sınıflar, arayüzler ve ilgiler — statik yapılarına müdahale etmiş oluyoruz.

AspectJ kendi içinde 4 farklı statik crosscutting yapısı bulundurmaktadır:

  1. Intertype Declaration (Introductions)
  2. Declare Parents
  3. Weawe-time Warnings ve Errors
  4. Softened Exceptions

Ara Tip Tanımlamaları

Ara tip tanımlamaları (intertype declarations ya da introductions), sistemdeki birimlerin elementler arasındaki karmaşıklığı ve karışıklığı önlemek açısından AspectJ dili ile bizlere sunulmaktadır. AspectJ, ilgi birimlerinin içerisinde başka tipler için Java elementleri — (statik, nonstatik) metod, alan ve yapıcı — tanımlamalarına izin vermektedir. Böylelikle sistemdeki tiplerin statik yapılarını değiştirmede büyük pay sahibi olmaktadır. Bir birimin kendisine ait olmayan ilgiler (concerns) için bünyesinde metod, alan ve hatta sadece o ilgilerin çalışması için yapıcı metod tanımlanması gerekebilir. Böyle durumlarda birimin içeriğini koruması açısından bu ek elementleri ilgi (aspect) birimlerinde referans gösterilen tip için yaratıp çalışmasını sağlayabiliriz.

Ara tip tanımlamarı başlıca
  • Ara Tip Metod Tanımlama (statik ve non-statik metodlar)
  • Ara Tip Alan Tanımlama (statik ve non-statik)
  • Ara Tip Yapıcı Tanımlama
Ara tip tanımlamaların genel özellikleri
  • İlgi birimleri tanımlanacak elemenleri sadece erişim belirleyicilerden public, protected ve default (paket korumalı) ile oluşturabilir.
  • İlgi birimlerinde tanımlanan aynı isimdeki ara tip elementlerin erişim belirleyicileri private olmak zorundadır.
  • Ara tip tanımlarken özel semboller ( *, .., + ) kullanılmaz.

Ara Tip Metod Tanımlama

Ara tip metod tanımlama (Intertype method declaration) ilgiye maruz kalmış sistemlerde diğer tanımlamalara oranla en çok kullanılan yaklaşımdır. Bu yapı ile sistemde istenen tiplere (sınıf, arayüz ve ilgi) ara metodlar bağlanır. Bu yapılar farklı sistem tasarımlarına ve amaçlarına göre ilgi birimlerinde yaratılır. public erişime sahip tüm ara metodlar, bağlandığı tipin olduğu her yerde kullanılır.

Ara Tip Metod Anatomisi:
    /**
     *   Doc Comment
     */
    <erişim belirleyicisi>          (1)
    <dönüş tipi>                    (2)
    [Tip].[metod ismi]()            (3)
    [istisna fırlatımı] {           (4)
        // ara tip metod gövdesi    (5)
    }

    abstract public <dönüş tipi> [Tip].[metod ismi](); (6)
  1. public , private ya da belirleyicisiz (default hali) kullanılır.
  2. Geleneksel metod tanımlamalarında olması gereken ve bu tanımlamada da olan dönüş tipi.
  3. Hangi tip için olması istendiğinde tipin ismi, metoda verilecek isim ve parametre bölümü.
  4. Bu metod tanımlamaları da istisna(lar) fırlatabilir.
  5. this kullanarak, bağlanan tipin içeriğine (alanlar ve metodlar) ulaşıldığı gibi ulaşılan içerik ilginin konuşlandığı paketin konumuna göre değişiklik gösterebilir.
  6. İlgi birimlerinin içinde soyut ara metodlar da tanımlanabilmektedir.

Örnek 1) Soyut ara tip metod tanımlama örneği:
    package com.kodcu.phones;
>>  abstract public class Phone { }                 (1)

    package com.kodcu.aspects;
    import com.kodcu.phones.Phone;
    public aspect DescriptionAspect {

>>      abstract public String Phone.phoneType();   (2)
    }

    package com.kodcu.phones;
    public class SmartPhone extends Phone {

        public String phoneType(){                  (3)
        	return "SmartPhone";
        }
    }
  1. Soyut sınıfımız Phone.
  2. AspectModule ilgi birimi içerisinde soyut Phone sınıfına soyut phoneType isminde ara metod bağlanmaktadır.
  3. Bir soyut sınıf içerisinde tanımlanan soyut metodlar, bu sınıftan türeyen sınıflarda somut hale getirilmesi gerekmektedir. Aynı işlem bu sefer AspectModule biriminde Phone sınıfı için tanımlanan soyut ara metoda yapılacaktır. SmartPhone sınıfı Phone sınıfına ait tüm soyut metodları — soyut ara tip metodlar dahil — yapılandırılmalıdır.

Örnek 2) Arayüz & Soyut sınıf:
    package com.kodcu.phones;
>>  abstract public class Phone { }

    package com.kodcu.phones;
>>  public class SmartPhone extends Phone implements Recoverable {  (1)

        public String phoneType(){
        	return "SmartPhone";
        }
    }

    package com.kodcu.aspects;
    import com.kodcu.phones.Recoverable;
    public aspect RecoveryAspect {

>>      public void Recoverable.retrieveData(Phone phone){     (2)
            // retrieve the data of the phone
        }
    }

    package com.kodcu.aspects;
    import com.kodcu.phones.Phone;
    public aspect DescriptionAspect {

>>      abstract public String Phone.phoneType();
    }

    package com.kodcu.phones;
>>  public interface Recoverable {

        public void retrieveData(Phone phone);
    }
  1. Mevcut sınıf SmartPhone artık Recoverable arayüzünden de faydalanmaktadır.
  2. Eski yapılması gereken yöntem düşünüldüğünde, SmartPhone sınıfına retrieveData metodunu tam olarak oluşturmamız gerekmekteydi. Şu anki örneğimizde de gösterildiği gibi artık bu işlemi aspect birimleri içerisinde yapılmaktadır. Recoverable arayüzünde şablon halinde bulunan metodu artık aspect birimlerinde (ör: RecoveryAspect ) tam anlamıyla oluşturabiliriz.

Örnek 2 şu şekilde de yapılabilir:

    package com.kodcu.phones;
>>  abstract public class Phone { }

    package com.kodcu.phones;
>>  public class SmartPhone extends Phone implements Recoverable {

        public String phoneType(){
        	return "SmartPhone";
        }
    }

    package com.kodcu.aspects;
    import com.kodcu.phones.Recoverable;
>>  public interface Recoverable {                              (1)

        public void retrieveData(Phone phone);

        static aspect RecoveryAspect {

>>          public void Recoverable.retrieveData(Phone phone) {
        	    // retrieve the data of the phone
            }
        }
    }

    package com.kodcu.aspects;
    import com.kodcu.phones.Phone;
    public aspect DescriptionAspect {

>>      abstract public String Phone.phoneType();
    }
  1. Recoverable.aj dosyası içerisinde tanımlanan arayüz içinde statik iç aspect birimi yaratarak şablon olan retrieveData metodunu çalışır hale getirebiliriz.
Aspect birimleri .java uzantılı Java kaynak kodların bulunduğu dosyalarda oluşturulamazlar. .aj uzantılı dosyalar aspect birimleri için yaratılır ve bunların içerisinde tavsiyeye ihtiyaç duyulan noktalara müdahale edilir. Recoverable arayüzü bir .aj uzantılı dosyada tanımlanmıştır çünkü statik iç ilgi birimi — RecoveryAspect — burada oluşturulmuştur.

Diğer ara tip metod tanımlama örnekleri:

    package com.kodcu.phones;
    public class Phone {

        private int version;
        private String phoneName;
        boolean blocked;

        public int versionID() {
    	    return version;
        }
        public String phoneName() {
            return phoneName;
        }
    }

    package com.kodcu.aspects;
    import com.kodcu.phones.Phone;
    public aspect DescriptionAspect {

>>      private void Phone.sensitive(int degree){  }        (1)

>>      void Phone.batteryPower(double power){  }           (2)

>>      public String Phone.getFullName(){                  (3)
            return this.phoneName() + this.versionID();     (4)
        }
    }
  1. Phone sınıfına ait olacak sensitive metodu tanımlanıyor.
  2. Bir parametre alan batteryPower metodu, Phone sınıfının elementlerinin bir parçası olmaktadır.
  3. Aynı şekilde String tipinde değer döndüren getFullName, Phone sınıfının üyelerine dahil olmaktadır.
  4. Ara tip metod gövdelerinde bağlı olduğu tipin içeriğine de ulaşmaktadır. Phone sınıfında tanımlanmış phoneName ve versionID metodlarına erişebiliyoruz. Ayrıca, DescriptionAspect birimde tanımlanmış diğer Phone sınıfına bağlı metodlara da bu gövde içinde erişebiliriz. Erişilecek üyelerin bulunduğu paketler önemlidir. Örneğin, DescriptionAspect birimi içerisindeki ara tip metodlarının gövdelerinde blocked alanına ulaşamayız çünkü ilgi birimi ve sınıf farklı paketlerde oluşturulmuştur.

Ara Tip Alan Tanımlama

Ara tip alan tanımlama (Intertype field declaration) istenilen tiplere ilgi birimleri içerisinde alan yaratılmasını sağlamaktadır.

Ara Tip Alan Anatomisi:
    <erişim belirleyicisi>              (1)
    (static ve/veya final)?             (2)
    <alanın tipi>                       (3)
    [Tip].[alan ismi]  [ = ifade];
  1. public , private ya da belirleyicisiz (default) kullanılır.
  2. Yaratılan ara alan isteğe göre static ve/veya final karakterine sahip olabilir.
  3. Alanın alacağı tipi belirtir.
Örnek 3)
package com.kodcu.phones;
public class Phone implements Recoverable{
    private int version;
    private String phoneName;
    boolean blocked;
    public int versionID() {
        return version;
    }
    public String phoneName() {
    	return phoneName;
    }
}

package com.kodcu.aspects;
import com.kodcu.phones.*;
public aspect AspectModule {

    public void Recoverable.retrieveData(Phone phone) {
    	// retrieve the data of the phone
    }

    public void Recoverable.retrieveData() {
    	// retrieve the data of the current phone
    }

    public void Phone.sensitive(int degree){  }

    void Phone.batteryPower(double power){ 	}

}

package com.kodcu.aspects;
import com.kodcu.tests.Tests;
import com.kodcu.phones.Phone;
public aspect TestingAspect {           (1)

    private int Tests.count;            (2)

    public void Tests.incCount() {
    	count++;
    }

    public int Tests.totalCount() {
    	return count;
    }

    pointcut LogNOMCalls(Tests t) : call(* Phone.*(..)) && this(t); (3)

    before(Tests t) : LogNOCalls(t) {                               (4)
    	t.incCount();
    	System.out.println("Count:" + t.totalCount()
    	    +"::"+thisJoinPoint.toShortString());
    }
}

package com.kodcu.tests;
import com.kodcu.phones.Phone;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
public class Tests  {

    private int count = -1;                     (5)

    @Test
    public void testNOCalledMethod() {
        Phone phone = new Phone();
>>      phone.phoneName();
>>      phone.versionID();
        phone.retrieveData();
>>      phone.sensitive(5);

        assertEquals(3, this.totalCount());     (6)
    }
}
Testin Dökümü:
Count:1::call(Phone.phoneName())
Count:2::call(Phone.versionID())
Count:3::call(Phone.sensitive(..))
  1. Oluşturduğumuz test ilgi birimi. Bu ilgiyi kullanarak sistemdeki toplam çağırılan metod sayılarını elde edicez.
  2. Tests sınıfına bağlanmış private erişime sahip count ismindeki alan. (2) ve (5) numaralı satırlarda yaratılan aynı isimdeki alanlarda çakışma olmamaktadır çünkü alanların erişimleri private olarak atanmıştır. Doğal olarak bu alanlar yaratıldıkları birimlere aitlerdir sadece.
  3. Sadece Test sınıfını hedef alarak, erişim belirleyicilerine, dönüş tiplerine, parametre sayılarına ve metod isimlerine bakılmaksızın Phone sınıfına ait tüm metod çağırmalarını seçer.
  4. Seçilen birleşim noktaları tavsiye yapısı ile bağlanır ve gereken crosscutting ilgi eylemleri bulunan metod çağırmalarından önce çalışır.
  5. Test sınıfına ait olan count, TestingAspect ilgisinin içindekiyle bağlantılı değildir.
  6. Test sonucunda sadece Test sınıfı içindeki seçilen birleşim noktalarını topla dediğimiz için bize 3 tane metod çağırmanın gerçekleştiğini göstermektedir. retrieveData() metodunun çağırılması sayılmamaktadır çünkü bu metod Recoverable arayüzüne aittir.

Ara Tip Yapıcı Tanımlama

Ara tip yapıcı tanımlama (Intertype constructor declaration), diğer tanımlamaların arasında en az kullanılandır. [AspectJ Dili] kısmında gösterilen AspectJ projeleri de bunu desteklemektedirler ve bu ara yapıcı tanımlaması belirtilen projelerde görülmemektedir. Referans gösterilen tip için yapıcı metod ilgi biriminin içerisinde oluşturulmaktadır.

Ara Tip Yapıcı Anatomisi:
    <erişim belirleyicisi>              (1)
    [Tip].new()                          (2)
    [istisna fırlama]{
        // yapıcı gövdesi
    }
  1. public , private ya da belirleyicisiz (default) kullanılır.
  2. Yapıcılar isim almadıkları için new anahtarıyla yeni bir yapıcı istenen tip için yaratılır.

Örnek 4) Aynı tanımdaki yapıcıların çakışması:
package com.kodcu.phones;

public class Phone implements Recoverable{
    private int version;
    private String phoneName;
    boolean blocked;

    public int versionID() {
    	return version;
    }
    public String phoneName() {
    	return phoneName;
    }
}

package com.kodcu.phones;
import com.kodcu.phones.Phone;;
public aspect InformationAspect {

>>  public Phone.new(){                 (1)
    	System.out.println("InformationAspect:Phone:public");
    }

}

package com.kodcu.aspects;

import com.kodcu.tests.Tests;
import com.kodcu.phones.Phone;
public aspect TestingAspect {

>>  public Phone.new(){                 (2)
    	System.out.println("TestingAspect:Phone:public");
    }
}
  1. Aynı tanımlamaya sahip public ve parametre almayan yapıcı metod hem TestingAspect hem de InformationAspect birimlerinde tanımlanıyor. Derleme zamanı şu hata oluşmaktadır: intertype declaration from com.kodcu.phones.InformationAspect conflicts with intertype declaration: void com.kodcu.phones.Phone.() from com.kodcu.aspects.TestingAspect.
  2. (1). satıra bağlı olarak aynı hata gösterilmektedir. Çözüm olarak 2 yapıcı da private olabilir, biri private diğeri de public belirleyici kullanabilir ya da paket konumuna göre yapıcılar default olarak kalabilir.
Java, tekrarlanan yapıcı (recursive constructor) oluşturulmak istendiğinde derleme sırasında bize hata döndürmektedir. Bu hata gösterimi AspectJ dilinde şuan gösterilmiyor ve tekrarlanabilen yapıcı oluşturulmasına izin veriliyor fakat, sistemi çalıştırdığımızda StackOverflow hatasını almamız kaçınılmaz oluyor.

Declare Parents Yapısı

AspectJ aynı zamanda statik bir şekilde tiplerin hiyerarşilerini değiştirmemize olanak tanımaktadır. Bu özellik, birden fazla tipin aynı bağ yapısına sahip olması gerektiği sistemlerde tek bir seferde ayarlanmasına yardımcı olmaktadır. declare parents yapısı ile birlikte mevcut tiplerin hangi birimden türediğini ve hangi arayüzlerden faydalandığını statik olarak ilgi birimlerinde tanımlayabiliyoruz:

    declare parents : [Tip Deseni] implements [Arayüz(ler)]; (1)

    declare parents : [Tip Deseni] extends [Sınıf];
  1. [Tip deseni] içerisinde özel sembollerden (*, .. , +) faydalanılabilir.

(3). örnekte Phone sınıfının Recoverable arayüzünden faydalandığını görüyoruz (Phone implements Recoverable). Sınıfların ve arayüzlerin arasındaki bu bağı ilgi birimlerinin içinde de tanımlayabiliyoruz artık. Aşağıdaki kod parçasında örnek 3’ün sadece belli kısımlarını alıp önceden yapılmış bağlantının aynısını bir ilgi biriminde oluşturacağız:

3. örneğin ‘declare parents’ ile yapılışı:
package com.kodcu.phones;
>> public class Phone {                         (1)
        private int version;
        private String phoneName;
        boolean blocked;
        public int versionID() {
            return version;
        }
        public String phoneName() {
        	return phoneName;
        }
}

....
....    Değişmeyen kodları temsil eder (örnek 3)
....

aspect AspectTypeDeclare {

>>   declare parents: Phone implements Recoverable; (2)

}
Aynı Testin Dökümü:
Count:1::call(Phone.phoneName())
Count:2::call(Phone.versionID())
Count:3::call(Phone.sensitive(..))
  1. Phone sınıfının yeni halinin hiç bir arayüzden faydalanmadığını görmekteyiz.
  2. AspectTypeDeclare birimininde akraba bağlarının düzenlenmesi için declare parents içerisinde Phone sınıfının Recoverable arayüzünü uygulayacağını statik bir şekilde ayarlıyoruz. Bu işlemi yaptıktan sonra sistem tekrar derleniyor ve istenilen çıktı tekrar elde ediliyor.

Warnings ve Errors

Sistemdeki özel birleşim noktalarını AspectJ’nin sağladığı declare warning ve declare error yapıları ile geliştiricilere belli sinyaller gösterebiliyoruz. Eğer sistemde, bu yapıların içinde belirlenen birleşim noktaları ile geliştiricinin ulaşmak istediği noktalar aynı ise, belirlenen o noktalarda uyarı ya da hata mesajları gösterilir. Bu sayede geliştirici, belirli birleşim noktalarında uyarılmış olur.

Declare warning ve error yapısı:
    declare warning :  : "Warning Message";   (1)

    declare error :  : "Error Message";     (2)
  1. yapısı içerisinde seçilen birleşim noktalarına ulaşıldığında istenilen uyarı mesajını gösterir.
  2. yapısı içerisinde seçilen birleşim noktalarına ulaşıldığında istenilen hata mesajını gösterir. Bu yapıların içerisinde birden fazla noktaya ulaşılması için pointcut tanımlamasında özel semboller de kullanılır.
Bir ‘declare error’ örneği:
package com.book.info;

import com.book.staff.Librarian;
import com.book.store.StoreBook;

public class Library {
    public void doSomethingLibrary() {
      StoreBook store = new StoreBook();
      store.doSomethingMore(null, 1);
      Librarian lib = new Librarian();
      lib.doSomethingLibrarian(null, 4); (1)
    }
}

package com.book.aspects;
public aspect AspectWarning {

         (2)
    declare error : call(* com.book.staff.Librarian.doSomethingLibrarian(..)) &&
                    within(com.book.info.Library)
                  : "method call is not allowed in the class Library";
}
  1. Geliştiriciye gösterilmesi gereken hata noktası.
  2. Sadece Library paketinin içerisinde bulunan erişim belirleyicisine, dönüş tipine ve parametre sayısına bakılmaksızın Librarian sınıfının doSomethingLibrarian metodunun çağırıldığı noktalarda method call is not allowed in the class Library mesajını göster.
declare warning ve declare error sistemin çalışmasına engel olmamaktadır. Bu sinyaller geliştiriciyi bazı birleşim noktalarına hiç ulaşmaması gerektiğini göstermek adına yapılır.

Softened Exceptions

AspectJ kendi içinde Java’nın istisna mekanizmasını direkt pas geçerek org.aspectj.lang.SoftException fırlatmasını sağlamaktadır. İsminden de anlaşılacağı gibi yumuşatılmış istisna fırlatmalarını AspectJ ile gelen declare soft yapısı tarafından yapılmaktadır. Diğer bir deyişle, AspectJ, Java’nın statik istina kontrol sistemini devre dışı bırakarak SoftException istisnasını belirli birleşim noktalarında uygulamamıza olanak sağlamaktadır.

Declare soft yapısı:
    declare soft : ExceptionType : (Pointcut); (1)
  1. birleşim noktalarında hangi tipde istina fırlatılıyorsa ExceptionType bölümüne o istisna yazılmalıdır. Örneğin; bir metod çalışma noktasında fırlatlan IllegalArgumentException, declare soft yapısının içerisinde tanıtılmalıdır.
Bir ‘declare soft’ örneği:
package com.book.store;
public class StoreBook {

>>  public void doSomethingMore(Object param1, int param2){
        throw new Exception("an exception occurs");
    }

}

package com.book.aspects;
public aspect AspectSoftened {

>>  declare soft : Exception                            (1)
                 : execution(* com.book.store.StoreBook.doSomethingMore(..)) ;
}

package test;
import com.book.store.StoreBook;
public class Test {
    public static void main(String... args) {
        StoreBook store = new StoreBook();
        store.doSomethingMore(null, 1);
    }
}
  1. Method-execution birleşim noktasında meydana gelen Exception fırlatımı declare soft tarafından yakalanmaktadır.
Hata çıktısı:
Exception in thread "main" org.aspectj.lang.SoftException       (1)
	at com.book.store.StoreBook.doSomethingMore(StoreBook.java:5)
	at test.Test.main(Test.java:6)
Caused by: java.lang.Exception: an exception occurs
	... 2 more
  1. Java istisna mekanizmasını pas geçerek SoftException fırlatılmaktadır.

 

Bir sonraki yazımızda AspectJ dilinin son bileşeni “Aspect” yapısını ayrıntılarıyla ele alacağız.

3 Comments

Post a Comment

Comment
Name
Email
Website