Pointcut yapılarıyla tam olarak tavsiyelere ne zaman başvurağımızı belirtirken, tavsiye (advice) yapılarıyla da tam olarak ne yapacağımızı belirtiyoruz. AspectJ dili dinamik crosscutting özelliğini tavsiye yapıları aracılığıyla sağlamaktadır. Sistemin gereksinimine göre belli senaryolarla birlikte enine kesen ilgileri farklı zamanlarda çalıştırmak isteyebiliriz. Sistemin davranışını belli noktalarda (yani belli birleşim noktalarında) tavsiye yapılarıyla bağlanan pointcut’lar ile beraber değiştirme imkanına sahip oluyoruz. AspectJ 3 çeşit tavsiye sunmaktadır:
-
Before Advice
-
Birleşim noktasının çalışmasından önce çalışan tavsiye yapısı.
-
-
After Advice
-
After advice, birleşim noktasının çalışmasından sonra işleme girer. Ayrıca bu yapı kendi içinde 3 sınıfa daha ayrılmaktadır:
-
After (finally) Advice: Bağlanan yerin sonucuna bakılmaksızın, birleşim noktalarından sonra çalışan yapıdır. Bu özelliği ile aynı zamanda after finally advice olarak da gösterilir.
-
After Throwing Advice: Birleşim noktalarında beliren hata fırlatmalarından sonra çalışması istenen advice yapısıdır.
-
After Returning Advice: Başarıyla çalışan ve biten birleşim noktalarından sonra çalışması istenen yapıdır. İstisna fırlatmanın yaşanmadığı noktalarda devreye girmektedir.
-
-
-
Around Advice
-
Özel bir yapı olan around advice, eşlenen birleşim noktalarını çevreleyen bir özelliğe sahiptir. Bu yapı, eşlenen birleşim noktalarının içeriğinde değişiklik yapmadan ya da yaparak çalıştırabilir, ayrıca sistemdeki bu noktaları devre dışı bırakabilir ya da birden fazla çalışmasına izin verebilir.
-
Tavsiye, metod tanımlamasına benzer bir özelliğe sahiptir. Daha önce de bahsedildiği gibi (Bkz. [AspectJ Dili]) aspect birimi içerisindeki yeni bileşenler derlenerek Java bileşenlerinin yapısını almaktadırlar. Tavsiyeler de byte kod dönüşümü sonrasında metod davranışa sahip olurlar. Tavsiye tanımlaması yapılarına göre biraz değişiklik gösterebilir ama genel tanımlama olarak aşağıdaki düzen içerisinde bir tavsiye yaratılmaktadır:
@AdviceName("advicename") (1) <advice tanımlaması> : (2) <pointcut tanımlamaları> (3) { // tavsiye gövdesi (4) }
-
AspectJ,
org.aspectj.lang.annotation
paketinin içerisindekiAdviceName
notasyonu ile istenilen tavsiye yapısına isim verilmesini sağlar. Opsiyonel bir özelliktir. -
Hangi tavsiye yapısı olacağını belirten ilk bölge.
-
pointcut
yapılarıyla oluşan bölge. Birleşim noktaları ile tavsiyenin eşlendiği yerdir. -
Metod gövdesi ile aynı özelliğe sahiptir. AspectJ ile birlikte gelen bazı özel değişkenler de bu gövde içinde kullanılır.
Important
|
Tavsiye yapıları, çalışma zamanında AspectJ Weaver ile metod yapısına dönüştüğü için tavsiye yapılarına isim verme işlemi gene çalışma zamanında belirlenmektedir. Çalışma zamanı belirlenen örnek bir tavsiye ismi: ajc$after$com_book_aspects_AspectModule$2$e0a2f451 . $2$e0a2f451 ifadesindeki ilk bölge ($2$ ), ilgi biriminin içinde üstten 2. sırada olan tavsiye birimi olduğunu göstermekte ve her tavsiye için bir hash değeri verilmektedir. @AdviceName notasyonunu sadece tavsiyeleri birbirlerinden ayırt etmek için kullanırız. Aynı isme sahip birden fazla tavsiye tanımlaması hataya neden olmaz.
|
aspect AspectModule {
after() returning(String ret): (1)
call(* com.book.Library.doSomething(String,..)) {
// advice body
}
}
-
returning()
içerisinde tanımlanan değer (yaniret
),doSomething
metodundan geriye dönen değeri temsil etmektedir. Ayrıca, bu değeri tavsiye gövdesinin içerisinde farklı amaçlar doğrultusunda kullanabiliriz.
Tavsiye yapılarının metod yapılarına benzer olduğu belirtilmişti. Tavsiye yapıları, AOP yaklaşımının gerçekleşeceği bölgeler olarak benimsenmektedir. Enine kesen ilgilerin belirlenen noktalara tavsiyelerde bulunmasını en kolay şekilde geliştiricilere sunmak adına bu yapı Java’nın metod yapısına benzer şekilde tasarlanmıştır. Bu 2 yapı arasında elbette benzerlikler ve farklılıklar olmaktadır. Benzerlikler ile tavsiye yapılarına alışılması daha kolay olacağı gibi farklılıklar ile AspectJ dilinin bize kazandırdığı değişik fonksiyonları da görmüş olacağız.
- İki yapı arasındaki benzerlikler
-
-
Tavsiye yapıları da metodlar gibi parametre alabilmektedir. Bu parametreler birleşim noktalarından gelen bilgileri bize döndürmektedir. Ayrıca, bunlar tavsiye gövdesinde farklı amaçlar için kullanılabilir.
-
Tavsiyeler de istisna (hata) fırlatabilir.
-
Tavsiye gövdeleri metod gövdelerindeki özelliklerin tümünü karşılamaktadır.
-
this
anahtar kelimesi, mevcut nesnenin (yani aspect nesnesi) referansı olarak metod gövdesinde kullanıldığı gibi kullanılabilir. -
Sadece
around
advice yapısında dönüş tipi belirlenebilir ve geriye değer döndürür. -
Runtime istisnalar fırlatabilir.
-
Eğer birleşim noktaları metod ise ve bu metodlar birden fazla istisna fırlatma yeteneğine sahipse, tavsiye yapıları içlerinden sadece 1 tanesini fırlatabilir.
-
- Tavsiyenin metod olmadığını belli eden farklılıklar
-
-
Opsiyonel olarak tavsiyeleri birbirinden ayırmak için isimlendirilebilir.
-
Direkt olarak çağrılamazlar. Runtime içerisinde bu tavsiyelerin çağırılmasını sağlayan işlemler mevcuttur.
-
Erişim belirleyicilerine sahip değiller.
-
After advice ve before advice yapıları değer döndürmezler.
-
Around advice yapısına özel
proceed()
metodu vardır. -
Tavsiye gövdesinin içinde kullanılacak özel değişkenler mevcuttur.
-
Important
|
Tavsiye yapıları sistemin çalışma akışını değiştirme özelliğine sahiptirler. |
Before advice yapısı, birleşim noktasında bulunan esas kodlardan önce çalışmaktadır. Bu yapının içinde fırlatılan hata sistemin sonlanmasına neden olur. Enine kesen ilgilerden tracing, security ve validation işlemlerinde bu yapı daha sık kullanılmaktadır.
package com.book.info;
public class Library {
public void doSomething(int param1){
// core concerns
System.out.println("core concerns in progress...");
}
}
package com.book.aspects;
public aspect ValidationAspect { (1)
void outError(String msg) {
System.err.println("Error: " + msg);
System.exit(0);
}
before(int param1) : execution(* do*(int)) && args(param1) {
// advice body
if (param1 <= 0)
this.outError("number must be greater than 0");
}
}
package test;
import com.book.info.Library;
public class Test {
public static void main(String... args) {
Library lib = new Library();
>> lib.doSomething(0);
}
}
Error: number must be greater than 0
-
Validation işleminin sitemde farklı sınıflara saçıldığını düşünelim ve sistemde tüm
do*
ile başlayan metodların çağırılmasından önce girilen parametrenin doğru olup olmadığına bakalım.
Eğer yukarıda yapılan yaklaşımı AspectJ kullanmadan yapsaydık sistemin mevcut görünümü şu şekilde olabilirdi:
package com.book.info;
public class Library {
public void doSomething(int param1){
// core concerns
System.out.println("core concerns in progress...");
}
}
package com.book.utility;
public class Print { (1)
public static void outError(String msg) {
System.err.println("Error: " + msg);
System.exit(0);
}
}
package test;
import com.book.info.Library;
public class Test {
public static void main(String... args) {
Library lib = new Library();
// crosscutting concern (2)
int param1 = 0;
if (param1 <= 0)
Print.outError("number must be greater than 0");
>> lib.doSomething(param1);
}
}
Error: number must be greater than 0
-
Yeni yaratılan bir
public
Print
sınıfı oluşabilir -
Sadece bir tanesi gösterilen ama
do*
ile başlayan tüm metodların sistemde çok fazla bulunduğunu ve hepsinin aynı doğrulamayı yaptığını düşünürsek, bu enine kesen ilginin çok fazla alana saçılacağını da söyleyebiliriz.
After advice yapısı, birleşim noktalarının çalışmasından sonra gerçekleşecek enine kesen ilgi eylemlerinin çalışmasını sağlamaktadır. Genel olarak birleşim noktalarındaki dönüş değerleri tek tip gelmemektedir. AspectJ dili bu yüzden after advice
adı altında 3 farklı yapı sunmaktadır. After finally yapısı, hata dönsün ya da dönmesin her koşulda birleşim noktalarından sonra çalışır.
package com.book.aspects;
public aspect LoggingAspect {
after() : call(* com.book.info.Library.*(..)) { (1)
// log after methods calls declared in Library
}
}
package test;
import com.book.info.Library;
import com.book.store.StoreBook;
public class Test {
public static void main(String... args) {
Library lib = new Library();
>> lib.anyMethodDeclaration();
>> lib.staticMethod();
>> Library.staticMethod2();
StoreBook store = new StoreBook();
store.doNothing();
}
}
-
Library
nesnesine ait veLibrary
sınıfına ait (statik) tüm metod çağırmalarından sonra çalışan tavsiye tanımlaması.
AOP yaklaşımını kullanmadan önceki geleneksel loglama sisteminde, log yapılacak noktaları kendimiz bizzat tespit ederek çağırılan metodlardan sonra her durumda çalışacak kodları sistemin içine entegre etmemiz gerekecekti:
package test;
import com.book.info.Library;
import com.book.store.StoreBook;
public class Test {
public static void main(String... args) {
Library lib = new Library();
try {
lib.anyMethodDeclaration();
} finally { (1)
// log after calling anyMethodDeclaration()
}
try {
lib.staticMethod();
} finally { (2)
// log after staticMethod()
}
try {
Library.staticMethod2();
} finally { (3)
// log after staticMethod2()
}
StoreBook store = new StoreBook();
store.doNothing();
}
}
-
Her koşulda
Library
sınıfına ait metodların çağırılmasından sonra log işleminin çalışması için, çağırılan metodlarıtry {}
bloğu içine alıpfinally {}
bloğu ile bitirmek gerekmektedir. -
Aynı işlemler bu satırda da yapılıyor.
-
Aynı işlemler bu satırda da yapılıyor.
Sorunsuz olarak biten birleşim noktalarından sonra çalışan tavsiye yapılarıdır. Bu yapı sayesinde birleşim noktasının void
dışında geri dönüş tipini de tavsiye bloğunun içerisinde kullanabiliyoruz.
package com.book.aspects;
public aspect LoggingAspect {
4> after() returning() : call(* com.book.info.Library.*(..)) { (1)
// log after methods calls declared in Library
}
1> after() returning(String ret) : (2)
call(String com.book.info.Library.doArchive(..)) {
// log after doArchive()
}
}
package test;
import com.book.info.Library;
import com.book.store.StoreBook;
public class Test {
public static void main(String... args) {
Library lib = new Library();
>> lib.anyMethodDeclaration();
>> lib.staticMethod();
>> Library.staticMethod2();
>>>> String result = lib.doArchive(); (3)
}
}
-
Bu tavsiye yapısı 4. örnekteki ile aynı fonksiyonelliğe sahiptir yalnız çağırılan metodların her hangi birinde oluşacak hata fırlatımı
after() finally
gibi çalışmasına engel olacak ve gereken ilgi kodları çalışmayacaktır. Dönüş tipi belirtilmediği (returning()
) için bu tavsiyemain()
metodu içindeki 4 birleşim noktasınada bağlanır. (>>
, birleşim nokta gölgelerini göstermektedir). -
Dönüş tipi
String
olandoArchive
metoduna bağlanır. -
Toplam 2 tavsiye yapısı bu birleşim noktasına bağlanmıştır ( (1) ve (2) ). Birden fazla tavsiye bağına sahip birleşim noktalarının çalışma akışı tavsiyelerin oluşturulma düzenine göre belirlenir. Bu örneğimizde
LoggingAspect
biriminin içinde ilk (1) sonra (2) tanımlandığı için sistem (3) → (1) → (2) sırasında çalışacaktır.
Important
|
Eğer bir birleşim noktasına bir ilgi biriminde oluşturulmuş birden fazla tavsiye yapısı bağlanıyorsa, o noktada tavsiyelerin çalışma sırası yaratıldıkları sıraya göre belirlenir (en yukarıdan aşağıya doğru) (Bkz. örnek 6). Eğer bir birleşim noktasına farklı ilgi birimlerinde oluşturulan tavsiye yapıları bağlanıyorsa, çalışma akışını belirlemek için aspect precedence özelliği kullanılmaktadır. Bu özellik aspect bölümünde detaylı anlatılacak.
|
After returning tavsiyesine benzer özelliktedir. Birleşim noktasının çalışmasında meydana gelen istisna fırlatımı sonrası çalışan tavsiye yapısıdır. Normal şartlarda sorunsuz çalışan bir metod çağırma ya da yürütme noktasından sonra bu yapı çalışmaz.
package com.book.aspects;
public aspect AspectModule {
after() returning(): call(* com.book.info.Library.*(..)){ (1)
System.out.println("After returning!");
}
after() throwing() : call(* com.book.info.Library.*()){ (2)
System.err.println("After throwing!");
}
after() throwing(Exception ex) : call(* com.book.info.Library.*()){
System.err.println("After throwing! exception : "+ ex.getMessage()); (3)
}
}
package com.book.info;
public class Library {
public void doException() throws Exception {
throw new Exception("an exception occurs!");
}
}
package test;
import com.book.info.Library;
public class Test {
public static void main(String... args) {
Library lib = new Library();
try {
>> lib.doException(); (4)
} catch (Exception e) {
System.err.println("catch block");
}
}
}
1 After throwing! 2 After throwing! exception : an exception occurs! 3 catch block
-
doException
metoduna bağlı olan tavsiye, metodun hata fırlatmadığı zamanlar çalışacak -
doException
metodunun istisna çıkarttığı durumdan sonra çalışacak olan tavsiye yapısı. Fırlatılan istisnanın bilgilerine bu tavsiye yapısında ulaşamıyoruz. -
doException
metodunun istisna çıkarttığı durumdan sonra çalışacak olan tavsiye yapısı. Fırlatılan istisnanın bilgilerine bu tavsiye yapısında ulaşabiliyoruz.throwing(Exception ex)
parametresinde tanımlı istina türü ile tavsiyenin gövdesinde farklı senaryolar için kullanılabilir. -
Hedef alınan birleşim noktası.
Around advice, birleşim noktalarını çevreleyen bir yapıya sahiptir. Bu yapı, pooling, caching, tracing, exception handling ve transaction management ilgileri için kulanılabilir. Diğer tavsiye yapılarından farklı olarak around yapısı:
-
before advice ve after (finally) advice özelliğini barındırır.
-
Birleşim noktasını aynı ya da farklı içerikler ile çalıştırabilir.
-
Birleşim noktalarının birden fazla çalışmasını sağlayabilir.
-
Birleşim noktalarının çalışmasını atlayabilir.
-
Özel
proceed()
metodu sayesinde birleşim noktasını tavsiye içerisinde çalıştırabilir. -
Dönüş tipine sahiptir.
package com.book.info;
public class Library {
>> public String doSomethingLibrary() {
System.out.println("doSomethingLibrary: core concerns in progress...");
return "result +";
}
>> public void doSomething(int param1){
// core concerns
System.out.println("doSomething: core concerns in progress...");
}
>> public void doException() throws Exception {
throw new Exception("an exception occurs!");
}
}
package com.book.aspects;
public aspect AspectModule {
void around() : execution(* doException() throws Exception){ (1)
try {
proceed(); (2)
} catch (Exception e) {
System.out.println("around body :: Caught Exception : "+ e.getMessage());
}
}
void around() : execution(* com.book.info.Library.doSomething(..)){ (3)
System.out.println("around body :: NEW core concerns in progress...");
}
String around() : execution(* com.book.info.Library.doSomethingLibrary(..)){ (4)
String jpoint = proceed(); (5)
jpoint += " after advice behavior :: additional actions "+
"after the execution of doSomethingLibrary()!";
return jpoint;
}
}
package test;
import com.book.info.Library;
public class Test {
public static void main(String... args) {
Library lib = new Library();
String result = lib.doSomethingLibrary();
System.out.println(result);
lib.doSomething(0);
try {
lib.doException();
} catch (Exception e) {
e.printStackTrace(); (6)
}
}
}
1 doSomethingLibrary: core concerns in progress... 2 result + after advice behavior :: additional actions after the execution of doSomethingLibrary()! 3 around body :: NEW core concerns in progress... 4 around body :: Caught Exception : an exception occurs!
-
Exception
hatası fırlatan her birdoException
metodunu çevreliyor.doException
metodunu olduğu gibi kapsamaktadır. Sistemde bu metod yerine bağlanan tavsiye yapısının önceliği vardır. -
proceed()
metodu ileLibrary
sınıfı içerisinde oluşturulandoException
metodunu çağırıyor. -
Library
sınıfı içindekidoSomething
metodunu çevreliyor. Around tavsiyesiproceed()
metodunu kullanmadığı içindoSomething
metodu sistemin çalışma akışında kullanılmıyor. Bu metod yerine around tavsiyesi sistemde devreye giriyor. -
Library
sınıfı içindekidoSomethingLibrary
metodunu çevreliyor. -
proceed()
metodu yardımıyladoSomethingLibrary
metodunu çağırarak metodun içindeki esas kodların çalışmasını sağlıyor ve daha sonra ek ilgileri çalıştırıp geriye değer döndürüyor. -
Sistemin çıktısından da anlaşılacağı gibi
e.printStackTrace();
yani catch bloğuna giriş yapılmamaktadır. Bu işlem (1)'de yaratılan around tavsiyesi içinde gerçekleşmektedir.
Important
|
Around advice yapısı sistemin çalışma akışını doğrudan etkileyebilir.
|
AspectJ, birleşim noktalarındaki statik ve dinamik içeriklere kolaylıkla ulaşılması için kendi bünyesinde oluşturduğu Reflection API içinde 3 özel değişken barındırmaktadır: thisJoinPoint
, thisJoinPointStaticPart
ve thisEnclosingJoinPointStaticPart
. Her birleşim noktası, dinamik içerik bilgilerini tutan bir nesneden ve statik bilgileri barındıran iki nesneden oluşmaktadır. Örneğin; bir metod yürütme noktasındaki metodun ismi ve metodun bulunduğu dosyanın ismi ve satır numarası statik bir bilgi olduğu gibi aynı metod noktasının farklı nesneler ile çalıştırılmasından doğan bilgiler de dinamik olarak gözükmektedir.
-
thisJoinPoint : Tavsiye alan birleşim noktalarının dinamik bilgilerini döndürmektedir. Hedef nesneye, bulunduğu konumun nesnesine veya parametrelere bu değişken sayesinde ulaşabiliriz.
thisJoinPoint.getStaticPart()
komutuyla, birleşim noktasının statik bilgilerethisJoinPointStaticPart
değişkeni kullanmadan da ulaşılabilir. -
thisJoinPointStaticPart : Tavsiye alan birleşim noktalarının statik bilgilerini döndürmektedir. Birleşim noktasına ait değişmeyen özelliklere ( isim, dosya konumu, birleşim noktası türü, imza deseni ve bulunduğu dosyanın ismi gibi ) ulaşabiliriz.
-
thisEnclosingJoinPointStaticPart : Tavsiye alan birleşim noktalarını çevreleyen birleşim noktasına ait statik bilgilerini döndürmektedir. Eğer tavsiye alan bir birleşim noktasının bir üstünü (parent) yani onu çevreleyen birleşim noktasını öğrenmek için 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 librarian = new Librarian();
>> librarian.doSomethingLibrarian(null, 4);
}
}
package com.book.staff;
import com.book.info.Library;
>> public class Librarian extends Library{
>> public void doSomethingLibrarian(Object object, int b) {
>> doNothing();
}
>> public void doAnything(Object object) {
>> System.out.println("Voila!!");
}
>> public void doNothing() {
>> doAnything(null);
}
}
package com.book.store;
>> public class StoreBook {
>> public void doSomethingMore(Object param1, int param2) {
}
}
package com.book.aspects;
import com.book.info.Library;
aspect AspectCflow {
pointcut doPointcut():
cflow(execution(* Library.doSomethingLibrary()))
&& !within(AspectCflow);
before(): doPointcut(){
System.out.println(thisJoinPoint.getKind() + " :: " + (1)
thisJoinPointStaticPart.getSourceLocation() + " :: " + (2)
thisEnclosingJoinPointStaticPart.toShortString()); (3)
}
}
package test;
import com.book.info.Library;
public class Test {
public static void main(String... args) {
Library lib = new Library();
lib.doSomethingLibrary();
System.out.println("Done!");
}
}
-
Her bir birleşim noktasının türünü öğreniyoruz.
-
Her bir birleşim noktasının bulunduğu dosya konumunu öğreniyoruz.
-
Tavsiye alan birleşim noktalarını çevreleyen birleşim noktalarını öğreniyoruz.
method-execution :: Library.java:8 :: execution(Library.doSomethingLibrary()) constructor-call :: Library.java:9 :: execution(Library.doSomethingLibrary()) (1) staticinitialization :: StoreBook.java:0 :: staticinitialization(StoreBook.<clinit>) preinitialization :: StoreBook.java:2 :: preinitialization(StoreBook()) initialization :: StoreBook.java:2 :: initialization(StoreBook()) constructor-execution :: StoreBook.java:2 :: execution(StoreBook()) method-call :: Library.java:10 :: execution(Library.doSomethingLibrary()) method-execution :: StoreBook.java:4 :: execution(StoreBook.doSomethingMore(..)) constructor-call :: Library.java:11 :: execution(Library.doSomethingLibrary()) staticinitialization :: Librarian.java:0 :: staticinitialization(Librarian.<clinit>) preinitialization :: Librarian.java:5 :: preinitialization(Librarian()) preinitialization :: Library.java:6 :: preinitialization(Library()) initialization :: Library.java:6 :: initialization(Library()) constructor-execution :: Library.java:6 :: execution(Library()) initialization :: Librarian.java:5 :: initialization(Librarian()) constructor-execution :: Librarian.java:5 :: execution(Librarian()) method-call :: Library.java:12 :: execution(Library.doSomethingLibrary()) method-execution :: Librarian.java:7 :: execution(Librarian.doSomethingLibrarian(..)) method-call :: Librarian.java:8 :: execution(Librarian.doSomethingLibrarian(..)) method-execution :: Librarian.java:15 :: execution(Librarian.doNothing()) method-call :: Librarian.java:16 :: execution(Librarian.doNothing()) method-execution :: Librarian.java:11 :: execution(Librarian.doAnything(..)) field-get :: Librarian.java:12 :: execution(Librarian.doAnything(..)) method-call :: Librarian.java:12 :: execution(Librarian.doAnything(..)) Voila!! Done!
-
StoreBook store = new StoreBook();
satırını incelersek: constructor-call türünde bir birleşim noktası olduğunu,Library.java
dosyasının içinde 9. satırda bulunduğunu ve bu birleşim noktasını çevreleyenin de metod yürütme (method-execution) noktasına sahipdoSomethingLibrary()
metodu olduğunu görüyoruz.