La Programmazione Orientata agli Aspetti (AOP) per alcuni è considerata il fallimento della programmazione ad oggetti perché destruttura l’architettura con cui sono collegati gli oggetti. Per altri invece è un toccasana e viene vissuta come un potenziamento della programmazione ad oggetti stessa perché permette di incapsulare certi comportamenti (aspetti, appunto) che sono trasversali al flusso di programmazione/elaborazione modellato nel grafo di oggetti che rappresenta un dominio. In questo post cercheremo di far capire cosa significa programmare ad aspetti, implementando i concetti dell’AOP nel nostro modello ad oggetti in modo semplice e diretto con il solo ausilio di CGLIB, senza scomodare nomi importanti come AspectJ e Spring AOP.

La programmazione ad Aspetti

Come dice wikipedia,

La programmazione orientata agli aspetti è un paradigma di programmazione basato sulla creazione di entità software – denominate aspetti – che sovrintendono alle interazioni fra oggetti finalizzate ad eseguire un compito comune. Il vantaggio rispetto alla tradizionale Programmazione orientata agli oggetti consiste nel non dover implementare separatamente in ciascun oggetto il codice necessario ad eseguire questo compito comune.

Chiaro no?! Quando si comincia a studiare l’AOP infatti, per entrare nell’ottica di questo nuovo approccio di progettazione, viene spesso portato come esempio la gestione del logging. Si modularizza per esempio il logging con una o più classi che assumeranno il compito di “aspect“, dopodiché verrà dichiarato un “pointcut” che indicherà il punto in cui il nostro aspetto (logging) dovrà intervenire e tagliare il modello ad oggetti.

Sicuramente si tratta di un esempio utile che rende l’idea, ma sfido chiunque a cimentarsi concretamente nello sviluppo di un aspetto esclusivamente per centralizzare il logging. Un esempio invece più interessante, a mio avviso, che può chiarificare l’utilità della programmazione ad aspetti può essere il problema della gestione delle transazioni sul database.

Immaginando la classica architettura software a 3 livelli: dove apro la connessione al database? Da dove faccio partire la transazione? Quando committo? Ripeto tutto il codice di apertura/commit/rollback nei DAO? Esistono infatti alcuni aspetti di un sistema informativo che non possono essere modellati come oggetti, semplicemente perché interessano l’applicazione nel suo insieme e la “tagliano” trasversalmente.

AOP e le sue implementazioni

L’implementazione di riferimento per Java dell’AOP è AspectJ: questo framework introduce nuove parole chiave in Java per la definizione di pointcut, cioè condizioni che scatenano un aspetto, o per la dichiarazione dell’aspetto stesso.

Fortunatamente chi avesse bisogno di entrare nel mondo della programmazione orientata agli aspetti spinta può avvalersi di altri framework che semplificano la vita e soprattutto non sono intrusivi: Spring AOP ne è un esempio. Con Spring si riescono a definire pointcut e aspetti sia tramite annotazioni che tramite XML senza rompere il modello ad oggetti. La stessa piattaforma Java Enterprise mette a disposizione la sua implementazione dell’AOP attraverso gli Interceptors: lavorando all’interno di un Application Server possiamo infatti creare delle classi che fanno da intercettori (ovvero aspetti) alle chiamate agli EJB.

Come fa a funzionare una architettura di questo tipo? Dove sta il trucco? Ogni sistema che supporta l’AOP è basato sul concetto di Inversion of Control (IoC): in pratica è il sistema a fornirci le istanze degli oggetti, non saremo mai noi ad istanziarle! Questo perché la factory che produce gli oggetti ci rende in realtà dei proxy dei nostri oggetti, corredandoli degli aspetti che abbiamo definito per certi pointcut! Senza un gestore di proxy quindi l’AOP non funziona.

Proxy: le nostre classi dietro il sipario

Come abbiamo già visto nel post CGLIB-era il Potenziale Java, uno strumento molto performante che permette di creare proxy di oggetti a runtime è cglib: Spring stesso per esempio, quando deve eseguire proxy di oggetti, usa questa libreria.

Perché mai dovremmo avere bisogno di un proxy? Torniamo al problema della transazione. Abbiamo detto che si tratta di un compito che si gestisce meglio con la AOP invece che con la OOP: sfruttiamo quindi la tecnica dei proxy per riuscire ad aprire e chiudere la transazione rispettivamente prima e dopo la chiamata dei metodi di una certa classe che scrive su un database, in modo da garantire la consistenza dei dati (cioè vogliamo che le operazioni siano ACID). Per chi conosce il mondo Java Enterprise, stiamo tentando di riprodurre quello che accade con una chiamata ad un EJB: di default infatti l’Application Server implementa degli interceptor sulle chiamate ai metodi EJB garantendo che vengano eseguiti in transazione. Perché reinventare la ruota allora? Ovviamente la soluzione che andiamo a proporre serve per far capire quanto sia utile l’AOP e volendo può essere presa come spunto in quegli ambienti dove non abbiamo gli strumenti Enterprise.

Ammettiamo di avere un modello ad oggetti come segue:

Modello di dominio

La classe UserAccountManager ha, tra gli altri, un metodo che permette di salvare un oggetto User e Account in modo atomico.

public class UserAccountManager {

     private final UserDAO userDAO = new UserDAO();
     private final AccountDAO accountDAO = new AccountDAO();

     UserAccountManager() {
     }

     @Transactional
     public void save(Account account) throws SQLException {
          accountDAO.save(account);
          userDAO.save(account.getUser());
     }
     
     ...
}

Affinché le query di INSERT dei due DAO siano consistenti è necessario gestire la transazione già al livello della classe manager. Se creiamo una istanza di questa classe così com’è, niente ci garantisce che il metodo save venga eseguito in modo atomico.

Vediamo a questo punto come agire in modo poco invasivo attraverso un proxy. Abbiamo definito infatti una annotazione per indicare alla factory del proxy (ebbene si, anche noi avremmo bisogno della nostra BeanFactory come Spring per lavorare con un proxy del nostro manager!) che il metodo save deve essere eseguito in transazione. L’annotazione deve essere disponibile a runtime ed essere applicabile esclusivamente a metodi:

@Target(value = ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Transactional {

}

Non è un caso che il costruttore della classe manager abbia visibilità package-private: in questo modo l’unica possibilità di istanziare questa classe passerà da un BeanFactory che chiameremo BeanManagerFactory:

public class BeanManagerFactory {

     private static final Logger logger = Logger
               .getLogger(BeanManagerFactory.class);

     @SuppressWarnings("unchecked")
     public static  T getBean(Class clazz) {
          Enhancer enhancer = new Enhancer();
          enhancer.setSuperclass(clazz);
          enhancer.setCallback(new MethodInterceptor() {

               @Override
               public Object intercept(final Object obj, Method method,
                         final Object[] args, final MethodProxy proxy)
                         throws Throwable {
                    logger.debug("Calling proxied method " + method.getName());

                    Object result = null;
                    if (method.getAnnotation(Transactional.class) != null) {
                         logger.debug("Execute in transaction");
                         beginTransaction();
                         result = proxy.invokeSuper(obj, args);
                         endTransaction();
                    } else {
                         logger.debug("Execute out of transaction");
                         result = proxy.invokeSuper(obj, args);
                    }
                    return result;
               }
          });
          return (T) enhancer.create();
     }

     private static void beginTransaction() throws SQLException {
          ///Begin Transaction
     }

     private static void endTransaction() throws SQLException {
          ///Commit/Rollback Transaction
     }

}

La factory ha un solo metodo: riceve una classe in ingresso e restituisce un’istanza di un proxy di quel tipo. In particolare, se viene trovata l’annotazione @Transactional, viene:

  1. aperta la transazione tramite il metodo beginTransaction();
  2. eseguita la chiamata al metodo originale tramite proxy.invokeSuper(obj, args);
  3. gestito il commit/rollback della transazione e chiusura della transazione nel metodo endTransaction().

altrimenti il proxy delega la chiamata direttamente alla classe originale.

Conclusioni

Con una semplice classe come BeanManagerFactory siamo riusciti a creare un proxy generico che apre e chiude le transazioni in base ad una annotazione. Non vuole essere una soluzione esaustiva e soprattutto non c’è sempre bisogno di reinventare la ruota sperando che la nostra venga più tonda! Può essere preso come un esempio didattico sull’uso dell’AOP, oppure può essere applicato per progetti di piccole dimensioni dove magari non vogliamo scomodare grossi framework!

83 Posts

Sono un software engineer focalizzato nella progettazione e sviluppo di applicazioni web in Java. Presso OmniaGroup ricopro il ruolo di Tech Leader sulle tecnologie legate alla piattaforma Java EE 5 (come WebSphere 7.0, EJB3, JPA 1 (EclipseLink), JSF 1.2 (Mojarra) e RichFaces 3) e Java EE 6 con JBoss AS 7, in particolare di CDI, JAX-RS, nonché di EJB 3.1, JPA2, JSF2 e RichFaces 4. Al momento mi occupo di ECM, in particolar modo sulla customizzazione di Alfresco 4 e sulla sua installazione con tecnologie da devops come Vagrant e Chef. In passato ho lavorato con la piattaforma alternativa alla enterprise per lo sviluppo web: Java SE 6, Tomcat 6, Hibernate 3 e Spring 2.5. Nei ritagli di tempo sviluppo siti web in PHP e ASP. Per maggiori informazioni consulta il mio curriculum pubblico. Follow me on Twitter - LinkedIn profile - Google+