AspectJ dilinde, tavsiye yordamıyla dinamik crosscutting ö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:
-
Intertype Declaration (Introductions)
-
Declare Parents
-
Weawe-time Warnings ve Errors
-
Softened Exceptions
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 Dahili-Tip Üyesi Tanımlama
-
- Ara tip tanımlamaların genel özellikleri
-
-
İlgi birimleri tanımlanacak elemenleri sadece erişim belirleyicilerden
public
,protected
vedefault
(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 (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.
/** * 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)
-
public
,private
ya da belirleyicisiz (default
hali) kullanılır. -
Geleneksel metod tanımlamalarında olması gereken ve bu tanımlamada da olan dönüş tipi.
-
Hangi tip için olması istendiğinde tipin ismi, metoda verilecek isim ve parametre bölümü.
-
Bu metod tanımlamaları da istisna(lar) fırlatabilir.
-
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. -
İlgi birimlerinin içinde soyut ara metodlar da tanımlanabilmektedir.
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";
}
}
-
Soyut sınıfımız
Phone
. -
AspectModule
ilgi birimi içerisinde soyutPhone
sınıfına soyutphoneType
isminde ara metod bağlanmaktadır. -
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
birimindePhone
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.
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);
}
-
Mevcut sınıf
SmartPhone
artıkRecoverable
arayüzünden de faydalanmaktadır. -
Eski yapılması gereken yöntem düşünüldüğünde,
SmartPhone
sınıfınaretrieveData
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();
}
-
Recoverable.aj
dosyası içerisinde tanımlanan arayüz içinde statik iç aspect birimi yaratarak şablon olanretrieveData
metodunu çalışır hale getirebiliriz.
Important
|
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.
|
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)
}
}
-
Phone
sınıfına ait olacaksensitive
metodu tanımlanıyor. -
Bir parametre alan
batteryPower
metodu,Phone
sınıfının elementlerinin bir parçası olmaktadır. -
Aynı şekilde String tipinde değer döndüren
getFullName
,Phone
sınıfının üyelerine dahil olmaktadır. -
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
veversionID
metodlarına erişebiliyoruz. Ayrıca,DescriptionAspect
birimde tanımlanmış diğerPhone
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övdelerindeblocked
alanına ulaşamayız çünkü ilgi birimi ve sınıf farklı paketlerde oluşturulmuştur.
Ara tip alan tanımlama (Intertype field declaration) istenilen tiplere ilgi birimleri içerisinde alan yaratılmasını sağlamaktadır.
<erişim belirleyicisi> (1) (<static ve/veya final>)? (2) <alanın tipi> (3) <Tip>.<alan ismi> [ = ifade];
-
public
,private
ya da belirleyicisiz (default
) kullanılır. -
Yaratılan ara alan isteğe göre static ve/veya final karakterine sahip olabilir.
-
Alanın alacağı tipi belirtir.
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 org.junit.Test;
import com.kodcu.phones.Phone;
import static org.junit.Assert.assertEquals;
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)
}
}
Count:1::call(Phone.phoneName()) Count:2::call(Phone.versionID()) Count:3::call(Phone.sensitive(..))
-
Oluşturduğumuz test ilgi birimi. Bu ilgiyi kullanarak sistemdeki toplam çağırılan metod sayılarını elde edicez.
-
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şimleriprivate
olarak atanmıştır. Doğal olarak bu alanlar yaratıldıkları birimlere aitlerdir sadece. -
Sadece
Test
sınıfını hedef alarak, erişim belirleyicilerine, dönüş tiplerine, parametre sayılarına ve metod isimlerine bakılmaksızınPhone
sınıfına ait tüm metod çağırmalarını seçer. -
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.
-
Test
sınıfına ait olan count,TestingAspect
ilgisinin içindekiyle bağlantılı değildir. -
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 metodRecoverable
arayüzüne aittir.
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.
<erişim belirleyicisi> (1) <Tip>.new() (2) [istisna fırlama]{ // yapıcı gövdesi }
-
public
,private
ya da belirleyicisiz (default
) kullanılır. -
Yapıcılar isim almadıkları için
new
anahtarıyla yeni bir yapıcı istenen tip için yaratılır.
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");
}
}
-
Aynı tanımlamaya sahip
public
ve parametre almayan yapıcı metod hemTestingAspect
hem deInformationAspect
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.<init>() from com.kodcu.aspects.TestingAspect
. -
(1). satıra bağlı olarak aynı hata gösterilmektedir. Çözüm olarak 2 yapıcı da
private
olabilir, biriprivate
diğeri depublic
belirleyici kullanabilir ya da paket konumuna göre yapıcılardefault
olarak kalabilir.
Important
|
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.
|
Diğer ara tip tanımlamaları dışında ilgi birimlerinin içerisinde hedef gösterilen sınıfına dahili (inner) sınıf tanımlamaları da yapılmaktadır. Yaratılan dahili sınıflar static
yapıda olmak zorundadır.
[erişim belirleyicisi] static class (1) [Sınıf ismi].[isim] { (2) // dahili sınıf gövdesi }
-
Erişim belirleyicisi,
public
ya da paket erişimi (belirleyicisiz) olabilir. -
[Sınıf ismi]
, hedef alınan yani dahili sınıfın konuşlanacağı sınıfın ismi yazılmalıdır. Hedef sınıfın içinde yaratılacak sınıfın[isim]
'i belirlenmektedir.
package com.kodcu.core;
public class MyClass {
public static void main(String[] args) {
new Inner().run(); (1)
}
}
package com.kodcu.crosscutting;
import com.kodcu.core.MyClass;
aspect Magic {
public static class MyClass.Inner { (2)
public void run() {
System.out.println("Inner.run() executing");
}
}
}
-
MyClass
sınıfının içerisinde dahiliInner
sınıfının nesnesinin tanımlanıp ona aitrun
metodunun çağırılması. -
public static
özelliğine sahipMyClass
içerisinde yaratılanInner
ismindeki yeni tip üyesi ve ona ait olanstatik
run
metodu.
Important
|
Diğer tanımlamalara göre ara tip dahili sınıf tanımlamasının ismi bir ilgi biriminde birden fazla olamaz. Yaratılacak aynı isme sahip sınıf her ne kadar farklı sınıflar için kullanılmak istense de bir birim içerisinde aynı isme sahip sadece bir ara tip dahili olmalıdır. |
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];
-
[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:
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)
}
Count:1::call(Phone.phoneName()) Count:2::call(Phone.versionID()) Count:3::call(Phone.sensitive(..))
-
Phone
sınıfının yeni halinin hiç bir arayüzden faydalanmadığını görmekteyiz. -
AspectTypeDeclare
birimininde akraba bağlarının düzenlenmesi içindeclare parents
içerisindePhone
sınıfınınRecoverable
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.
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 : <Pointcut> : "Warning Message"; (1)
declare error : <Pointcut> : "Error Message"; (2)
-
<Pointcut> yapısı içerisinde seçilen birleşim noktalarına ulaşıldığında istenilen uyarı mesajını gösterir.
-
<Pointcut> 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.
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";
}
-
Geliştiriciye gösterilmesi gereken hata noktası.
-
Sadece
Library
paketinin içerisinde bulunan erişim belirleyicisine, dönüş tipine ve parametre sayısına bakılmaksızınLibrarian
sınıfınındoSomethingLibrarian
metodunun çağırıldığı noktalardamethod call is not allowed in the class Library
mesajını göster.
Important
|
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.
|
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 : ExceptionType : (Pointcut); (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ırlatlanIllegalArgumentException
,declare soft
yapısının içerisinde tanıtılmalıdır.ExceptionType
içinde özel semboller kullanılmamaktadır. Örneğin;*Exception
yanlış bir kullanımdır.
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);
}
}
-
Method-execution birleşim noktasında meydana gelen
Exception
fırlatımıdeclare soft
tarafından yakalanmaktadır.
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
-
Java istisna mekanizmasını pas geçerek
SoftException
fırlatılmaktadır.