TSM - Aspect Oriented Programming - O introducere și câteva idei

Knall Andreas - Java Team Lead


Paradigme precum MDD (model driven development) sau TDD (test driven development) joacă un rol important în dezvoltarea software în ziua de astăzi, o paradigmă nouă, numită AOP (aspect oriented programming) a început să devină din ce în ce mai populară în ultima perioadă. AOP are rolul de a modulariza anumite aspecte centrale ale unei aplicații, numit în limbajul de specialitate și cross cutting concern.

Următoarele exemple ilustrează într-o manieră simplă câteva situaţii în care utilizarea a AOP într-o aplicație software ar putea face diferenţa:

Desigur în practică se găsesc o sumedenie de altfel de situații de genul celor de sus, dar cele de sus au fost alese tocmai pentru că au toate cel puțin două lucruri în comun. În primul rând toate aceste soluții cad în aceași categorie de cross cutting concerns. Această sintagmă acoperă toate funcționalitățile care sunt comune unui set de clase, dar care reprezintă o funcționalite secundară a acestor clase. Pe lângă exemplele prezentate anterior, adică logging și error handling, se pot admite și alte funcționalități cum ar fi autorizarea sau aspecte legate de securitatea aplicațiilor. Rolul acestor funcționalități nu este minimalizat, dar sunt considerate funcționalități secundare, nefăcând parte din logica de business. În al doilea rând, presupunând că avem de-a face cu o aplicație tipică enterprise, este clar că implementarea oricăreia din ideile prezentate mai sus, implică de regulă intoducerea de cod, în mai multe locuri din aplicație, codul fiind aproape indentic în toate aceste locuri. Cu alte cuvinte avem un potențial mare de cod duplicat. O soluție elegantă ar fi crearea a unui singur bloc de cod pentru fiecare problemă prezentată și folosirea acestui bloc de cod oriunde este necesar în cadrul aplicației. Pentru această categorie de probleme, AOP poate fi soluția salvatoare. Înainte de a prezenta un exemplu concret de AOP voi trece în revistă noțiunile care stau la baza acestei paradigme.

Aceasta paradigmă va avea diverse implementări în diverse limbaje de programare, în lumea Java existând mai multe framework-uri care oferă facilități AOP. Dintre limbajele și framework-urile care oferă facilități AOP, ar fi de amintit următoarele: .NET FRAMEWORK, Java, Cobol, Javascript. Dacă vorbim în particular despre Java, cele mai populare framework-uri care aduc funcționalități AOP sunt AspectJ și Spring AOP. De reținut este faptul că implementările AOP pot avea un model de bază ușor diferit sau mai evoluat decât cel reprezentat de cele patru elemente prezentate anterior (advice, join points, pointcut și aspect). Toate implementările diferă una de alta, unele oferind soluții complete AOP, precum AspectJ, iar altele doar funcționalități de bază. Pentru a exemplifica acest lucru se vor prezenta cele patru tipuri de advice-uri pe care le oferă Spring AOP, descrierea lor succintă fiind următoarea:

După cum se poate bine observa Spring AOP oferă doar posibilitatea de a adăuga un advice unei metode. Alte framework-uri, AspectJ fiind probabil cel mai popular, oferă și posibilitatea de a adăuga un aspect membrilor de clasă. Mai mult decât atât, AspectJ oferă o gamă largă de funcționalități, de exemplu un Around advice pentru metode, dar doar dacă acestea au un anumit tip de parametri. Continuăm cu prezentarea unui exemplu concret de folosire a AOP, prin implementarea AOP aparținând framework-ului Spring. Am ales Spring, ținând cont de popularitatea și simplitatea acestui framework, ceea ce permite o înțelegere mai facilă a exemplului dat. Exemplul se folosește de toate conceptele prezentate anterior (advice, join points, pointcut și aspect) și asigură o facilitate de logging pentru clasele de DAO dintr-o aplicație Java enterprise.

Presupunem în continuare că avem următorul obiect de DAO (data acces object), BookDAO, care are rolul de a citi cărți din baza de date și de a insera cărți în baza de date. Metoda de citire aruncă o excepție specifică aplicației în momentul în care nu a fost găsită cartea căutată.

public class BookDAO
{
 
 public BookEntity findBookById(Long name)  
 throws ItemNotFoundException {
//Finding Book code
  }
 
  public BookEntity saveBook(){
  //Saving Book code
  }
}

Entitatea Book, în forma ei simplificată arată în felul următor:

public class Book
{
 
private Long id;
private String name;
 
 public void setName(String name) {
   this.name = name;
 }
 
 public void setId(Long id) {
  this.id = id;
 }
 
  public Long setId(){
   return id;
  }
 
  public String geName(){
   return name;
  }
 }

Dacă clasa Book are o formă simplă, în schimb implementarea advice-ului nostru pentru logare este puțin mai complicată, implementată în clasa MyAroundMethod. Pentru a oferi un exemplu cât se poate de concret am decis să implementez un advice de tipul Around Advice. Pentru a realiza acest lucru clasa noastră MyAroundMethod va trebui să implementeze interfața MethodInterceptor.

public class MyAroundMethod implements MethodInterceptor {

@Override public Object invoke(MethodInvocation method Invocation) throws Throwable { System.out.println( "Numele Metodei in care ne aflam : " + methodInvocation.getMethod().getName());   System.out.println( "Parametrii metodei in care ne aflam : " + Arrays.toString(methodInvocation .getArguments())); System.out.println( "MyAroundMethod: Interceptie inaintea" + "executiei metodei"); try { Object result = methodInvocation.proceed(); System.out.println("MyAroundMethod : " +" Interceptie dupa executarea metodei"); return result; } catch (ItemNotFoundException e) { System.out.println("MyAroundMethod :" +"Interceptie in urma aruncarii unei " +"exceptii");   throw e; } } }

MainClass, precum sugerează numele deja, este clasa care rulează aplicația noastră mică. Scenariul pe care îl urmărim este următorul: căutăm o carte din baza de date, îi modificăm numele și o salvăm din nou în baza de date. Apoi încercăm să încărcăm din baza de date cartea cu Id-ul 2, care desigur pentru a fi un exemplu reușit nu există în baza de date.

public class MainClass {
 
public static void main(String[] args) {
 ApplicationContext appContext = 
   new ClassPathXmlApplicationContext(
    new String[] { "Spring-DAO.xml" });
 
 BookDAO bookDAO = (BookDAO) appContext
	.getBean("BookDAOProxy");
 
 try {
	
  System.out.printn("*******************");
  
  BookEntity bookEntity = bookDAO.
   findBookById(new Long(1));
 
  bookEntity.setName("Nume nou de carte!");
  System.out.println("********************");
 
  bookDAO.saveBook(bookEntity);
  System.out.println("*********************");
 
  BookEntity bookEntity = bookDAO.
    findBookById(new Long(2));	
 
} catch (Exception e) {
  //prinde o exceptie
  }
 }
}

Configurația Spring, prezentă prin fișierul Spring-DAO.xml este fișierul care definește pentru exemplul nostru toate elementele AOP prezentate anterior. Cititorul care nu este familiarizat cu elementetele specifice Spring poate să înțeleagă cu ușurință punctele esențiale din acest fișier de configurație urmărind comentariile elementelor.

Ceea ce se observă este lipsa din fișierul de configurare a unei liste explicite de join point-uri. Aceste puncte nu se definesc de regulă în fișierele de configurare, ele fiind un rezultat al definirii a unui sau a mai multor pointcut(uri). În cazul de față am definit un pointcut folosind regular expression Java. Aceste pointcut-uri se pot defini în mai multe feluri, depinzând evident de soluția AOP aleasă. Am optat pentru varianta cu expresia regulară pentru a ilustra cât de puternică este totuși această facilitate de a indentifica join point-urile, cunoscută fiind puterea și flexibilitatea regular expressions Java. În cazul exemplului nostru join point-urile sunt cele două metode definite în BooksDAO.

Ceea ce se poate vedea în urma execuției codului în consolă este următoarea secvență de mesaje.

*************************
Numele metodei in care ne aflam : findBookById
Parametrii metodei in care ne aflam: [Long(1)]
MyAroundMethod : Interceptie înainte executiei metodei
MyAroundMethod : Interceptie dupa executarea metodei
*************************
Numele metodei in care ne aflam: saveBook
Method arguments : [Book]
MyAroundMethod : Interceptie înainte executiei metodei
MyAroundMethod : Interceptie dupa executarea metodei
*************************
Numele metodei in care ne aflam: findBookById
Parametrii metodei in care ne aflam: [Long(2)]
MyAroundMethod : Interceptie înainte executiei metodei
MyAroundMethod : Interceptie in urma aruncarii unei exceptii

Așadar folosind AOP am reușit să introducem un mecanism de logare în toate clasele noastre DAO, fără a scrie mult cod, care să fie împrăștiat prin toată aplicația. Analog fiecare cross-cutting concern din aplicația voastră poate fi implementat într-o manieră transparentă, în propria lui clasă (separate de logica de business) și folosit în toate locurile în care este necesar.

În ultima parte, dorim să vă expunem anumite idei care s-au cristalizat de-a lungul timpului în jurul conceptului de AOP, precum și puncte de vedere pe care echipa noastră le-am acumulat din folosirea AOP.

Cel mai important lucru de reținut este faptul că AOP nu este o soluție universală pentru multe din problemele care apar pe parcursul dezvoltării software. Este o abordare relativ nouă, care în unele situații ne poate ajuta, dar în altele nu.

Bineînțeles, sunt și dezavantaje, iar acestea sunt legate în principal de două aspecte importante. Primul este legat de performanță în momentul în care într-o aplicație mare sunt folosite multe aspecte, care pot posibil interacționa. De aceea, este de preferat ca doar anumite funcționalități ale unei aplicații, și aici ne referim la cele cu adevărat importante, să fie modelate și modularizate cu AOP.

Un al doilea dezavantaj îl constituie organizarea si executarea codului, care prin introducerea AOP îngreunează procesul de debugging. Prezentăm un scenariu, care pare a fi des întâlnit când se folosește o astfel de abordare. Imaginați-vă o aplicație medie spre mare, de tip enterprise, în care o serie de aspecte acoperă mare parte din funcționalitatea secundară, iar membrii echipei de dezvoltare care nu sunt obișnuiti cu AOP fac debugging pe o funcționalitate relativ complexă. În astfel de cazuri se întâmplă ca membrul(i) echipei de dezvoltare să nu își poată explica fluxul codului în totalitate ceea ce poate duce la apariția unor efecte cel puțin bizare la execuția codului. Misterul poate fi greu elucidat dacă aspectele nu sunt definite prin intermediul anotărilor ci prin fișiere de configurare bine ascunse și nefamiliare. Chiar și atunci când se trece peste aceste inconveniențe inițiale și se folosesc tool-uri specializate, debugging-ul poate fi, prudent formulat, neplăcut, iar fluxul codului în continuare greu de urmărit. În acest punct trebuie amintit că există diferite forme sub care elemente de AOP pot fi definite. De exemplu un advice poate fi definit ca în exemplul nostru, stil old school prin intermediul unui fișier de configurare sau într-un mod mai actual, anume prin adnotări. Dar din nou e de amintit că aceste elemente diferă de la implementare la implementare.

Concluzie

Am expus folosirea AOP pe baza unui scurt exemplu bazat pe Spring AOP. Ce ar trebui să reținem? AOP este o nouă paradigmă, cu multiple implementări, care, dacă este folosită cu cap poate aduce avantaje majore dezvoltării software.