čtvrtek 9. listopadu 2006

Český java podcast je tu!

Tak jsem to spáchali milá Máňo. Já, Filemon, Roumen a Borůvek jsme nahráli přes porodní bolesti první podcast na téma Vývojová prostředí v Jave.

Související linky

neděle 5. listopadu 2006

Transparentní cache pomocí Aspektově Orientovaného Programování

Jeden z důvodů proč jsem si oblíbil aspektově orientované programování (dále AOP) je možnost transparentně rozšiřovat stávající API o další funkčnost bez nutnosti do něj přímo zasahovat. V tomto článku bych vám rád možnosti AOP ukázal na implementaci cacheování vrstvy pro přístup k databázi.

V každé aplikaci máme kousky kódu, které se nám prolínají všemi vrstvami naší aplikace, ale do žádné nepatří konkrétně. Může se jednat o kus kódu pro logování-debug, bezpečnost, auditování, synchronizaci atd. Těmto kouskům kódu můžeme říkat aspekty. To co nám AOP nabízí je že můžeme tyto aspekty prolínat stávajícím kódem aniž bychom tento kód museli modifikovat.

Pomocí AOP si tak velice snadno můžeme napsat například aspekt, který zaloguje jakoukoliv výjimku vyhozenou z našeho API aniž bychom museli dopsat čárku kódu. Obecně vzato se aspekty dělí na dva druhy, vývojové a nebo produkční. Vývojové aspekty slouží ryze pro účely vývoje např. trasování nebo měření výkonu. Vývojové aspekty jsou součástí aplikace pouze po dobu vývoje. Produkční aspekty jsou aspekty, které nám v aplikaci zůstávají napořád a zajišťují jednotlivé vlastnosti systému např. transakce, bezpečnost apod.

Základy AOP

Ať už budete používat jakýkoliv AOP framework tak jsou pojmy společné všem. Tyto pojmy musíte pochopit, abyste porozuměli aspektově orientovanému programovaní.

Join Point
Defiuje stavy, ve kterých se může nacházet vykonávání kódu např. volání metody, volání konstruktoru, statická inicializace atd.
Pointcut
Definuje jazyk, pomocí kterého se lze na jednotlivé Join Ponty zavěsit
Advice
Umožňuje nadefinovat vlastní kód, který se vykoná v závislosti na zavěšení pointcatu. V podstě když nastane tohle, udělám toto.

Pokud dáme dohromady Join Point, Pointcut a Advice dostaneme aspekt. Máme tedy aspekt, který něco umí. Pro psaní aspektu využíváme klasickou syntaxi jazyku Java, kterou kombinujeme se syntaxí AOP. AOP zavádí nové konstrukty jako pointcut, around, before, call atd. Jak aspekt vypadá, ilustruje následující obrázek.

Syntaxe aspektu
Plná velikost obrázku (cca 25KB)>

Otázkou je jak tento aspekt integrovat s naším kódem. To je úkol pro AOP framework a této činnost se říká weaving, což v doslovném překladu znamená proplétání. Weaving provádí takzvaný weaver a k vlastnímu propletení může dojít ve zvoleném časovém okamžiku z hlediska java kódu.

Weaver

Weaver vezme na vstupu nějaké třídy a na výstupu produkuje nějaké třídy, to se může stát v následujících čtyřech časových okamžicích.

compile time weaving
Provádí se během kompilace. K dispozici jsou přímo zdrojové kódy, které jsou zkompilovány a výsledkem jsou zkompilované kód obsahující aspekty. Aspekty mohou být ve formě zdrojových souborů a nebo zkompilované.
post-compile weaving
Provádí se na již zkompilovaném kódu. Výsledkem je opět zkompilovaný kód. Aspekty mohou být ve formě zdrojových souborů a nebo zkompilované.
Load-time weaving
Provádí se ve chvíli kdy zkompilovaný kód natahuje classloader do JVM. Samozřejmě k tomuto se musí použí speciální weaver classloader
Rutnime weaving
Modifikuje zkompilovaný kód, který byl již natažený do JVM, bez nutnosti třídu znovu načítat.

Samozřejmě záleží na dané implementaci AOP frameworku resp. weaveru, jaký typ weavingu podporuje. Například AspectJ, ze kterého v tomto článku vycházím, podporuje kromě runtime weavingu všechny výše uvedené způsoby.

Jak na cacheování pomocí AOP

Cacheování je pěkným příkladem vlastnosti, která se nám prolíná celým systémem. V závislosti na nasazení aplikace (rozdílná data, zatížení atd.) se mohou různit požadavky na to co a kde cacheovat. To v těch horších případech vede k zavádění adhoc "cacheování" různými pokoutnými způsoby přímo do kódu. AOP nám umožňuje řešit cacheování velice elegantním způsobem, bez toho aniž bychom byly nuceni měnit stávající kód.

Jako příklad jsem zvolil situaci, kdy máme vytvořenou vrstvu pro přístup k datům, např. pomocí JDBC a rádi bychom tuto vrstvu cacheovali. Jako AOP framework jsem zvolil AspectJ, který je výborně integrovatelný s vývojovým prostředím Eclipse díky pluginu AJDT. Kromě zvýrazňování syntaxe, doplňování kódu, generování speciálního JavaDocu a dalších důležitých vlastností můžeme aspekty debugovat jako jakoukoliv jinou javovskou aplikaci.

Volba AspectJ neznamená, že bychom byli nějak svázáni s Eclipse. AspectJ má totiž i klasický kompilátor ovládaný z příkazového řádku nebo Antu. Kromě toho obsahuje i AspectJ Browser, což je samostatná GUI aplikace pro vývoj AOP. Ošem zpět k našemu příkladu.

Následující kód ukazuje klasický objekt pro přístup k datům (DAO - data access object), který definuje operace pro zápis, čtení, modifikaci a mazání (CRUD - create, read, update and delete). Pro jednoduchost jsem náš DAO objekt neinfikoval JDBC kódem, místo toho jsem použil java.util.Set, který simuluje databázovou tabulku.

public class FooDao {
  //pro simulaci databazove tabulky pouzijeme Set
  private Set<FooBean> table = new HashSet<FooBean>();
  
  public void update(FooBean bean){
    //JDBC kod pro update objektu v databázi
    //....
    
    //my pro simulaci pouzijeme vnitrni Set
    table.remove(bean);
    table.add(bean);
  }
  
  public void save(FooBean bean){
    //JDBC kod pro insert objektu do databáze
    //....
    
    //my pro simulaci pouzijeme vnitrni Set
    table.add(bean);
  }
  
  public void delete(FooBean bean){
    //JDBC kod pro smazani objektu v databázi
    //....
    
    //my pro simulaci pouzijeme vnitrni Set
    table.remove(bean);
  }
  
  public FooBean findById(int id){
    //JDBC kod pro smazani objektu v databázi
    //....
    
    //my pro simulaci pouzijeme vnitrni Set
    FooBean wanted = new FooBean(id);
    FooBean founded = null;
    for (FooBean bean: table) {
      if(wanted.equals(bean)){      
        founded = bean;
        break;
      }
    }
    return founded;
  }
  
  public List<FooBean> getAll(){
    //JDBC kod pro smazani objektu v databázi
    //....
    
    //my pro simulaci pouzijeme vnitrni Set
    return new ArrayList<FooBean>(table);
  }
}

Nyní jakým způsobem naimplementovat danou cache pomocí aspektu. Musíme si nadefinovat jakým způsobem bude cache nad DAO obejkty fungovat.

Pravidla cacheování
  • Cacheuje se vždy nad daným DAO objektem, tj. všechny cacheované hodnoty jsou sdruženy v jedné skupině. Jméno skupiny odpovídá packagi a třídě DAO objektu.
  • Cacheují se pouze výsledky operací pro čtení.
  • Jakákoliv jiná operace (mazání, modifikace, zápis) nad DAO objektem invaliduje danou cacheovanou skupinu, tj. všechny nacacheované objekty pro dané DAO.

Začneme vytvořením aspektu, který má jako jakýkoliv jiný java objekt konstruktor a vnitřní proměnné. Každý aspekt vystupuje jako singleton, pokud neřekneme jinak. To využijeme k tomu, abychom prostřednictvím konstruktoru zinicializovali cache. Já jsem jako cacheovací framework zvolil Java Caching System z rodiny Jakarta projektů. Zároveň v rámci aspektu nadefinujeme metody pro práci s cache. Ty můžeme označit jako protected pro případ, že bychom v budoucnu vytvořit specializovanější verzi našeho aspektu. Ano, v rámci aspektů můžeme použít dědičnost.

public aspect CacheAspect {  
  private JCS cache; //instance cache manageru
  
  /**
   * Konstruktor aspektu, který inicializuje cache.
   
   @see CacheAspect#initCache()
   */
  public CacheAspect() {
    initCache();
  }  
  
  /**
   * Provede inicializaci cache. Metoda je volána pouze jednou při vytvoření
   * aspektu. 
   */
  protected void initCache(){
    try{
      this.cache = JCS.getInstance("");//získáme defaultní cache
    catch (CacheException e){
      throw new RuntimeException(e);
    }
  }
    
  /**
   * Invaliduje všechny cacheované objekty v dané skupině.
   @param groupName jméno skupiny
   */
  protected void invalidate(String groupName){
    this.cache.invalidateGroup(groupName);
  }
  
  /**
   * Odstraní objekt z dané cacheovací skupiny.
   @param cachedObjectKey klíč objektu, který bude odstraněn 
   @param groupName jméno skupiny 
   */
  protected void remove(String cachedObjectKey, String groupName) {
    this.cache.remove(cachedObjectKey, groupName);
  }
  
  /**
   * Vloží objekt do dane cache skupiny.
   *   
   @param cachedObjectKey klíč objektu
   @param groupName jméno skupiny
   @param cachedObject objekt, který bude cacheován
   */
  protected void put(String cachedObjectKey, String groupName, Object cachedObject){
    try{
      this.cache.putInGroup(cachedObjectKey, groupName, cachedObject);
    }catch(CacheException e){
      throw new RuntimeException(e);
    }
  }
  
  /**
   * Vrací objekt z dané cache skupiny.
   @param cachedObjectKey klíč objektu
   @param groupName jméno skupiny
   @return cacheovaný objekt, pokud ještě není cacheováný pak <code>null</code>
   */
  protected Object get(String cachedObjectKey, String groupName) {
    return this.cache.getFromGroup(cachedObjectKey, groupName);
  }
}

Nyní je na čase v našem aspektu nadefinovat pointcaty, které nám umožní zavěšení na jednotlivé metody DAO objektu.

  /**
   * Pointcut, který zareaguje na každé volání veřejné metody <code>get*<code>
   * nebo <code>find*</code> na objektu <code>*Dao</code>, který leží v package 
   <code>cz.sweb.pichlik.aspectj.samples.dao</code>.
   <p>Pozn:
   * vzor <code>get*</code> je potencionálně nebezpečný, protože by se nám mohl
   * aplikovat i na gettry spadající do rodiny klasických accesoru, které nemají
   * nic společného s CRUD metodami. 
   */
  pointcut getExecs()
      call(public * cz.sweb.pichlik.aspectj.samples.dao.*Dao.get*(..))
      || call(public * cz.sweb.pichlik.aspectj.samples.dao.*Dao.find*(..));
  
  /**
   * Pointcut, který zareaguje na každé volání veřejné metody <code>update*<code>
   <code>dalete*</code> nebo <code>save*</code> na objektu <code>*Dao</code>
   * který leží v package <code>cz.sweb.pichlik.aspectj.samples.dao</code>.
   */
  pointcut deleteOrUpdateOrSaveExecs():
    call(public * cz.sweb.pichlik.aspectj.samples.dao.*Dao.update(..))
    || call(public * cz.sweb.pichlik.aspectj.samples.dao.*Dao.delete(..))
      || call(public * cz.sweb.pichlik.aspectj.samples.dao.*Dao.save(..));

Pointcat má své symbolické jméno, na které se budeme moci odkázat v advice části (výkoná část) našeho aspektu. Pravidla pro zavěšení napíšeme na základě předpokladu, že naše DAO objekty leží v package cz.sweb.pichlik.aspectj.samples.dao a definuji veřejné metody.

Nadefinujeme si dva pointcaty, jeden pro čtecí operace a jeden pro CUD operací. Pro zachycení volání metod je použito klíčové slovo call. Pro určení významu operací použijeme obecné pravidlo, že čtecím operacím odpovídají metody get* a find* (hvězdička ja klasický zástupný symbol, který můžeme využít). CUD operacím odpovídají metody save, update a delete.

Nyní nám zbývá nadefinovat dvě advice, jednu pro čtecí operace a jednu pro CUD operace.

  /**
   * Advice, který obaluje metody pro získáni objektu. V případě, že objekt 
   * ještě není cacheován, původní metoda se zavolá a její výsledek se uloží
   * do cache. V případě, že je objekt cacheován, vrací se z cache.
   *  
   @return cacheovaný objekt
   */
  Object around(): getExecs() {
    String groupName = getGroupName(thisJoinPoint);
    String cachedObjectKey = getCachedObjectKey(thisJoinPoint);
    //zjistime jestli je objekt cacheovan
      Object result = get(cachedObjectKey, groupName)
      
      if(result == null) { //objekt neni v cache
        result = proceed();//zavolame puvodni metodu
        if(result != null) {
          put(cachedObjectKey, groupName, result);//vysledek ulozime do cache
        }
      else {
        System.out.println("Objekt:" + result + " vrácen z cache");
      }
      
      return result;
  }
  
  /**
   * Advice před vykonáním smazání, vytvoření nebo změny objektu, která invaliduje celou
   * cacheovanou skupinu.
   */
  before(): deleteOrUpdateOrSaveExecs() {
    String groupName = getGroupName(thisJoinPoint);
    invalidate(groupName);
  }

Advice má svůj typ např. around, before, after, after returning nebo after throwing. Typ určuje kdy bude advice aplikovaná z hlediska volání kódu. Tedy before před před provedením metody, after po provedení metody, after returning po provedení metody s klasickým návratem, after returning po provedení metody ukončené vyhozením výjimky a around vlastní metodu obaluje.

Součástí advice je také jméno pointcatu, případně pointcat výraz, který danou advice aktivuje. V případě CUD operací můžeme využít before advice a invalidovat celou cache skupinu. V případě operací pro čtení musíme použít around advice.

Around advice je specifická v tom, že máme kontrolu nad tím jestli se původní metoda provolá či nikoliv a toho využijeme. V případě, že je hodnota nacechovaná nebudeme původní metodu DAO objektu volat. Jinak necháme původni metodu DAO objektu zavolat pomocí klíčové klauzule proceed() a výsledek uložíme do cache.

V rámci každého aspektu máme přístup k instanční proměnné thisJoinPoint typu org.aspectj.lang.JoinPoint. Díky tomu dokážeme zjistit jméno cílové třídy, jméno volané metody, argumenty metody atd. To nám pomůže stanovit skupinu, ve které budeme cacheovat a klíč, pod kterým budeme ukládat/vybírat do/z cache.

  /**
   * Vrací jméno cacheovací skupiny. To je určeno na základě jména třídy.
   @param jp aktuální joinpoint
   */
  protected String getGroupName(JoinPoint jp){
    return jp.getStaticPart().getSignature().getDeclaringType().getName();
  }
  
  /**
   * Vrací klíč pro cacheovaný objekt. Klíč je vypočítán na základě jména metody, 
   * která se volá a hodnot, které jsou předány jako argumenty (parametry) 
   * metody.
   
   @param jp aktuální joinpoint
   */
  protected String getCachedObjectKey(JoinPoint jp){
    String methodName = jp.getStaticPart().getSignature().getName();
    StringBuilder key = new StringBuilder(methodName);
    //z hodnot argumentu volane metody vytvorime klic cacheovaneho objektu
    Object args[] = jp.getArgs()
    for (Object arg: args) {
      if(arg != null) {
        key.append(arg.hashCode());
      }
    }
    return key.toString();
  }

Celý kód aspektu.

/**
 * Aspekt, který slouží jako cache pro DAO vrstvu. Vrácený objekt je cacheován v 
 * případě, že je vrácen z DAO vrstvy (leží v package <code>cz.sweb.pichlik.aspectj.samples.dao</code> 
 * má v názvu suffix <code>Dao</code>) metodou začínající na <tt>find</tt>
 * a nebo <tt>get</tt>.
 
 <p>Z hlediska klienta DAO vrstvy se vše chová transparentně, klient nepozná 
 * jestli došlo k dotazu na databázi a nebo jestli je objekt vrácen z cache. Jako
 * cacheovací framework byl použit <a href="http://jakarta.apache.org/jcs/index.html">Java Caching System</a>.  
 */
public aspect CacheAspect {  
  private JCS cache; //instance cache manageru
  
  /**
   * Konstruktor aspektu, který inicializuje cache.
   
   @see CacheAspect#initCache()
   */
  public CacheAspect() {
    initCache();
  }  
  
  /**
   * Pointcut, který zareaguje na každé volání veřejné metody <code>get*<code>
   * nebo <code>find*</code> na objektu <code>*Dao</code>, který leží v package 
   <code>cz.sweb.pichlik.aspectj.samples.dao</code>.
   <p>Pozn:
   * vzor <code>get*</code> je potencionálně nebezpečný, protože by se nám mohl
   * aplikovat i na gettry spadající do rodiny klasických accesoru, které nemají
   * nic společného s CRUD metodami. 
   */
  pointcut getExecs()
      call(public * cz.sweb.pichlik.aspectj.samples.dao.*Dao.get*(..))
      || call(public * cz.sweb.pichlik.aspectj.samples.dao.*Dao.find*(..));
  
  /**
   * Pointcut, který zareaguje na každé volání veřejné metody <code>update*<code>
   <code>dalete*</code> nebo <code>save*</code> na objektu <code>*Dao</code>
   * který leží v package <code>cz.sweb.pichlik.aspectj.samples.dao</code>.
   */
  pointcut deleteOrUpdateOrSaveExecs():
    call(public * cz.sweb.pichlik.aspectj.samples.dao.*Dao.update(..))
    || call(public * cz.sweb.pichlik.aspectj.samples.dao.*Dao.delete(..))
      || call(public * cz.sweb.pichlik.aspectj.samples.dao.*Dao.save(..));
  
  
  /**
   * Advice, který obaluje metody pro získáni objektu. V případě, že objekt 
   * ještě není cacheován, původní metoda se zavolá a její výsledek se uloží
   * do cache. V případě, že je objekt cacheován, vrací se z cache.
   *  
   @return cacheovaný objekt
   */
  Object around(): getExecs() {
    String groupName = getGroupName(thisJoinPoint);
    String cachedObjectKey = getCachedObjectKey(thisJoinPoint);
    //zjistime jestli je objekt cacheovan
      Object result = get(cachedObjectKey, groupName)
      
      if(result == null) { //objekt neni v cache
        result = proceed();//zavolame puvodni metodu
        if(result != null) {
          put(cachedObjectKey, groupName, result);//vysledek ulozime do cache
        }
      else {
        System.out.println("Objekt:" + result + " vrácen z cache");
      }
      
      return result;
  }
  
  /**
   * Advice před vykonáním smazání, vytvoření nebo změny objektu, která invaliduje celou
   * cacheovanou skupinu.
   */
  before(): deleteOrUpdateOrSaveExecs() {
    String groupName = getGroupName(thisJoinPoint);
    invalidate(groupName);
  }
  
  /**
   * Vrací jméno cacheovací skupiny. To je určeno na základě jména třídy.
   @param jp aktuální joinpoint
   */
  protected String getGroupName(JoinPoint jp){
    return jp.getStaticPart().getSignature().getDeclaringType().getName();
  }
  
  /**
   * Vrací klíč pro cacheovaný objekt. Klíč je vypočítán na základě jména metody, 
   * která se volá a hodnot, které jsou předány jako argumenty (parametry) 
   * metody.
   
   @param jp aktuální joinpoint
   */
  protected String getCachedObjectKey(JoinPoint jp){
    String methodName = jp.getStaticPart().getSignature().getName();
    StringBuilder key = new StringBuilder(methodName);
    //z hodnot argumentu volane metody vytvorime klic cacheovaneho objektu
    Object args[] = jp.getArgs()
    for (Object arg: args) {
      if(arg != null) {
        key.append(arg.hashCode());
      }
    }
    return key.toString();
  }
  
  //-----------------------------------------//
  //    Definice metod pro praci s cache     //
    //-----------------------------------------//
  
  /**
   * Provede inicializaci cache. Metoda je volána pouze jednou při vytvoření
   * aspektu. 
   */
  protected void initCache(){
    try{
      this.cache = JCS.getInstance("");//získáme defaultní cache
    catch (CacheException e){
      throw new RuntimeException(e);
    }
  }
    
  /**
   * Invaliduje všechny cacheované objekty v dané skupině.
   @param groupName jméno skupiny
   */
  protected void invalidate(String groupName){
    this.cache.invalidateGroup(groupName);
  }
  
  /**
   * Odstraní objekt z dané cacheovací skupiny.
   @param cachedObjectKey klíč objektu, který bude odstraněn 
   @param groupName jméno skupiny 
   */
  protected void remove(String cachedObjectKey, String groupName) {
    this.cache.remove(cachedObjectKey, groupName);
  }
  
  /**
   * Vloží objekt do dane cache skupiny.
   *   
   @param cachedObjectKey klíč objektu
   @param groupName jméno skupiny
   @param cachedObject objekt, který bude cacheován
   */
  protected void put(String cachedObjectKey, String groupName, Object cachedObject){
    try{
      this.cache.putInGroup(cachedObjectKey, groupName, cachedObject);
    }catch(CacheException e){
      throw new RuntimeException(e);
    }
  }
  
  /**
   * Vrací objekt z dané cache skupiny.
   @param cachedObjectKey klíč objektu
   @param groupName jméno skupiny
   @return cacheovaný objekt, pokud ještě není cacheováný pak <code>null</code>
   */
  protected Object get(String cachedObjectKey, String groupName) {
    return this.cache.getFromGroup(cachedObjectKey, groupName);
  }
}

Pro ověření funkčnosti, si vytvoříme jednoduchý kód, který bude volat náš DAO objekt.

public static void main(String[] args) {
    FooDao dao = new FooDao();
    FooBean bean = new FooBean(1);
    dao.save(bean);
    dao.findById(1)//puvodni metoda
    dao.findById(1)//cache
    dao.findById(1)//cache
    dao.update(bean);//invalidace cache
    dao.findById(1)//puvodni metoda
    dao.findById(1)//cache
    dao.delete(bean);//invalidace
    dao.save(bean);  //invalidace
    dao.findById(1)//puvodni metoda
    dao.findById(1)//cache
    dao.save(bean);  //invalidace
    dao.getAll();    //puvodni metoda
    dao.getAll();    //cache
    dao.delete(bean);//invalidace
    dao.getAll();    //původní metoda
  }

Abychom si nemuseli zatahovat logovací kód do DAO objektu, uděláme si ještě jeden aspekt, tentokrát logovací. Rozdíl oproti cacheovacímu aspektu je v tom, že pointcat je přímo součástí advice.

/**
 * Logovací aspekt (no spíš trasovací ;-), který slouží k logování událostí DAO vrstvy. Narozdíl od <code>
 * CacheAspectu</code> je <tt>pointcut</tt> nadefinován přímo v rámci <tt>advice</tt>
 * a používá konstruktů <code>within</code> <code>execution</code>. Rozdíl mezi
 <code>execution</code> <code>call</call> je z hlediska toho jestli se zavěšení 
 * provede na volajícím nebo volaném objektu. V případě <code>execution</code> 
 * se zavěšení provede na volaném objektu, oproti <code>call</call> kde je to 
 * na volajícím.   
 <p>
 <code>thisJoinPoint</code> ({@link JoinPoint}) a <code>thisJoinPointStaticPart</code> 
 {@link JoinPoint.StaticPart} jsou implicitní objekty každého aspektu.
 */
public aspect LoggingAspect {
  
  before() : within(cz.sweb.pichlik.aspectj.samples.dao.*Dao&&  execution(public * *(..)){                  
    String clazzName  = 
      thisJoinPoint.getStaticPart().getSignature().getDeclaringType().getName();
    String methodName = 
      thisJoinPointStaticPart.getSignature().getName();
    
    Object args[] = thisJoinPoint.getArgs();
    StringBuilder sb = new StringBuilder("Volána metoda: ");
    sb.append(clazzName);
    sb.append(".");
    sb.append(methodName);
    sb.append("(");
    for (int i = 0; i < args.length; i++) {
      if(i != 0){
        sb.append(",");
      }
      sb.append(args[i]);  
    }
    sb.append(")");
    System.out.println(sb.toString());
  }
}

Taky jsme použili execution místo call (vysvětlení viz. javadoc logovacího aspektu) a within pro určení nad jakým objektem se má logovat.

Nyní už zbývá pouze rozkrýt výstup jenž se objeví při spuštění programu.


Volána metoda: cz.sweb.pichlik.aspectj.samples.dao.FooDao.save(cz.sweb.pichlik.aspectj.samples.FooBean@20)
Volána metoda: cz.sweb.pichlik.aspectj.samples.dao.FooDao.findById(1)
Objekt:cz.sweb.pichlik.aspectj.samples.FooBean@20 vrácen z cache
Objekt:cz.sweb.pichlik.aspectj.samples.FooBean@20 vrácen z cache
Volána metoda: cz.sweb.pichlik.aspectj.samples.dao.FooDao.update(cz.sweb.pichlik.aspectj.samples.FooBean@20)
Volána metoda: cz.sweb.pichlik.aspectj.samples.dao.FooDao.findById(1)
Objekt:cz.sweb.pichlik.aspectj.samples.FooBean@20 vrácen z cache
Volána metoda: cz.sweb.pichlik.aspectj.samples.dao.FooDao.delete(cz.sweb.pichlik.aspectj.samples.FooBean@20)
Volána metoda: cz.sweb.pichlik.aspectj.samples.dao.FooDao.save(cz.sweb.pichlik.aspectj.samples.FooBean@20)
Volána metoda: cz.sweb.pichlik.aspectj.samples.dao.FooDao.findById(1)
Objekt:cz.sweb.pichlik.aspectj.samples.FooBean@20 vrácen z cache
Volána metoda: cz.sweb.pichlik.aspectj.samples.dao.FooDao.save(cz.sweb.pichlik.aspectj.samples.FooBean@20)
Volána metoda: cz.sweb.pichlik.aspectj.samples.dao.FooDao.getAll()
Objekt:[cz.sweb.pichlik.aspectj.samples.FooBean@20] vrácen z cache
Volána metoda: cz.sweb.pichlik.aspectj.samples.dao.FooDao.delete(cz.sweb.pichlik.aspectj.samples.FooBean@20)
Volána metoda: cz.sweb.pichlik.aspectj.samples.dao.FooDao.getAll()    
    

Aspekty nabízejí další rozměr programování. V tomto článku jsme se dotkli jenom některých možností aspektu. Pokud Vás AOP zaujalo doporučuji nakouknout do dokumentace AspectJ, protože obsahuje velké množství užitečných informací. Na závěr přidávám ještě popis AJDT Eclipse pluginu.

AJDT

Plugin AJDT (update adresa pro Eclipse 3.2.x http://download.eclipse.org/tools/ajdt/32/update) do vývojového prostředí Eclipse nabízí perspektivu Aspect visualization. V ní je graficky vidět aplikování jednotlivých aspektů.

Aspect visualization perspektiva
Plná velikost (cca. 125KB)

Většinu času ovšem stejně stráví člověk při psaní kódu v klasické Java perspektivě. Tu AJDT rozšiřuje na úrovni view Outline, to je krásně vidět na následujícím obrázku v pravo nahoře. Modré blesky ozančují pointcaty a šipky podle typu jednotlivé advice.

Java perspektiva, rozšířené view Outline při editaci aspektu.
Plná velikost (cca. 142KB)

Na následujícím obrázku je opět Java perspektiva, tentokrát při editaci kódu, na který se aplikuje advice. Při najetí na advice symbol (šipky označují typ advice - before, around) je vidět jaká advice, v jakém aspektu.

Java perspektiva při editaci kódu, který je advicnutý.
Plná velikost (cca. 139KB)

Aspekty jdou debugovat jako jakýkolv jiný kód.

Debug perspektiva při ladění aspektu.
Plná velikost (cca. 125KB)

Součástí každého API by měla být jeho dokumentace. Přes Project >> Generate AJdoc... lze vygenerovat obohacený JavaDoc o informace o aspektech a jejich aplikování.

AJDoc - JavaDoc obohacený informacemi o aspektech.
Plná velikost (cca. 54KB)