Approfondimento Aspect Oriented Programming (AOP) 1 Sandro Pedrazzini AOP Aspect Oriented Programming • Paradigma di programmazione, che prevede l’inserimento di funzionalità comune (cross-cutting concerns) a funzionalità esistente • Lo scopo è quello di separare aspetti comuni (logging, security, gestione eccezioni, ecc.) da business logic nel senso più stretto 2 Sandro Pedrazzini AOP Aspect Oriented Programming (AOP) • Tutti i paradigmi, in particolare il paradigma a oggetti, prevedono l’incapsolamento di funzionalità in entità separate, creando astrazioni (metodi, classi). • Esistono però funzionalità che potrebbero non rientrare facilmente in un’organizzazione di questo tipo, perché più trasversali (cross-cutting) • Esempio: logging 3 Sandro Pedrazzini AOP Cross-cutting concerns tipici • • • • • • • • • Sincronizzazione Gestione della memoria Persistenza Security Caching Logging Monitoring Business rules … 4 Sandro Pedrazzini AOP Implementazioni • Le implementazini più diffuse per Java sono AspectJ e Hyper/J • AOP viene integrata in diversi framework, soprattutto attraverso l’implementazione di AspectJ – Spring – JBoss AOP – Guice 5 Sandro Pedrazzini AOP Terminologia di base • Cross-cutting concern Funzionalità comune (trasversale) da condividere tra più funzioni. Esempio: funzionalità di logging, identica, all’interno di metodi o classi diverse, in cui, ad esempio, un log dev’essere eseguito in entrata e uno in uscita. • Advice Codice aggiuntivo da applicare al modello di business esistente. Esempio: codice di logging, unico, da chiamare separatamente quando necessario. 6 Sandro Pedrazzini AOP Terminologia di base (2) • Pointcut Punto di esecuzione all’interno dell’applicazione. Punto in cui un cross-cutting concern dev’essere applicato. Esempio: punto raggiunto quando un certo metodo inizia (logging di entrata) o termina (logging di chiusura) • Aspect Modulo che combina la descrizione del pointcut (join-point) e il body dell’advice. Esempio: aggiungiamo un aspect all’applicazione, specificando modalità di logging e quando questa va eseguita 7 Sandro Pedrazzini AOP Logging • Tipicamente una funzionalità da aspect come logging la si trova in più parti nel codice, rendendo difficoltosa la sua comprensione e la sua manutenzione • Una modifica al logging può significare modificare diversi moduli, classi e metodi 8 Sandro Pedrazzini AOP Logging (2) • Codice in ogni modulo 9 Sandro Pedrazzini AOP Logging (3) • Centralizzazione in un unico aspect 10 Sandro Pedrazzini AOP Esempio • Funzionalità di pagamento con carta di credito public class BillingService implements IBillingService { … public Receipt chargeOrder(Order order, CreditCard card) throws Exception { ChargeResult result= fCreditCardProcessor.process(order.getAmount(), card); return ...; } } 11 Sandro Pedrazzini AOP Esempio (2) • Abbiamo però anche bisogno di controlli di security e logging public Receipt chargeOrder(User user, Order order, CreditCard card) throws Exception { logger.info(“Inizio pagamento…”); if (!checkUserPermission(user) { logger.info(“Utente non autorizzato…”); throw new UnauthorizeException(); } ChargeResult result= fCreditCardProcessor.process(order.getAmount(), card); logger.info(“Fine pagamento…”); return ...; } 12 Sandro Pedrazzini AOP Esempio (3) • Nel codice precedente, elementi di logging e di security sono da considerare cross-cutting concerns • Cosa succede se dobbiamo modificare elementi di security nell’applicazione? Siccome questi elementi sono sparsi in tutta l’applicazione, le modifiche saranno parecchie. • Meglio sarebbe poterli gestire in modo centrale 13 Sandro Pedrazzini AOP Aspect • AOP spinge a risolvere il problema della presenza di singoli elementi in più parti del codice permettendo di esprimere questi cross-cutting concerns attraverso moduli chiamati aspects • Un aspect contiene advice (codice da eseguire) e pointcut (dichiarazione di quando gli advice vanno eseguiti) • Esempio: un aspect potrebbe contenere – codice di verifica della security – specifica che la verifica viene eseguita ogni volta prima della chiamata a charge() 14 Sandro Pedrazzini AOP Aspect (2) • Possibile esempio in AspectJ “pseudocode” public aspect SecurityCheck { before() : within(Receipt IBillingService.chargeOrder( User user, Order order, CreditCard card)) && call(ChargeResult ICreditCardProcessor.charge( long, CreditCard)) { if (!checkUserPermission(user) { logger.info(“Utente non autorizzato…”); throw new UnauthorizeException(); } } 15 Sandro Pedrazzini AOP Aspect (3) • AspectJ permette tutta una serie di modalità dichiarative per specificare i join points • I joint point sono l’elemento più critico, perché attraverso la loro dichiarazione, espressa anche attraverso regular expressions, si specifica il match • Espressioni complesse rendono il codice poco “prevedibile” e quindi anche poco mantenibile 16 Sandro Pedrazzini AOP Esempi di pointcut • execution(* set*(*)) match con l’esecuzione di tutti i metodi il cui nome inizia con “set” e hanno un unico parametro di qualsiasi tipo • within(ch.supsi.*) limita lo scope del pointcut a qualsiasi cosa (classe, metodo) nel package “ch.supsi” • this(CreditCard) Questo pointcut risolve quando l’oggetto attuale in esecuzione (this) è un’istanza di CreditCard • execution(* set*(*)) && within(ch.supsi.*) && this(CreditCard) Combinazione dei tre criteri precedenti 17 Sandro Pedrazzini AOP Implementazioni • Esistono fondamentalmente due tipi di implementazione per AOP – Class-waving Integra le implementazioni degli aspect direttamente nelle classi in cui devono essere eseguite. Il weaving può essere applicato sia a livello di compilazione, che loading o runtime. – Proxy La chiamata al metodo di un oggetto viene intercettata. Vengono usati Java dynamic proxy o CGLIB (code generation lib) I framework che si basano sul proxy sono generalmente più semplici e si basano quasi esclusivamente sul meccanismo di method interceptor. 18 Sandro Pedrazzini AOP Implementazione: proxy (1) • Viene generata una sottoclasse della classe da estendere, o dinamicamente, a runtime (Java dynamic proxy), oppure modificando il bytecode durante la generazione (CGLIB) Subject request() ... Proxy request() ... RealSubject request() ... 19 Sandro Pedrazzini AOP Implementazione: proxy (2) • Il meccanismo di Java dynamic proxy può essere applicato solo se esiste un’interfaccia della classe da estendere • Con CGLIB, invece, il proxy eredita dalla stessa classe che estende RealSubject request() ... Proxy request() ... RealSubject request() ... 20 Sandro Pedrazzini AOP AOP in framework • Framework come Spring, Guice o altri, integrano le funzionalità di AspectJ, creando se possibile un livello di astrazione – Spring: ne facilita l’utilizzo, pur lasciando aperta la possibilità di accedere a funzionalità avanzate e specifiche di AspectJ – Guice: ne facilita e ne delimita l’utilizzo, allo scopo di promuoverne l’impiego dove questo migliora il codice, ma evitarne l’abuso, con conseguenze nefaste nel codice 21 Sandro Pedrazzini AOP AOP con Guice • Guice prevede AOP attraverso il meccanismo di “method interception” • In questo modo si può eseguire un advice ogni volta che uno specifico metodo viene invocato • In Guice si parla di – Matcher: elemento esegue il match => pointcut – MethodInterceptor: parte di codice da eseguire => advice 22 Sandro Pedrazzini AOP AOP con Guice (2) • Matcher Dichiarazione che permette di accettare o rifiutare un valore. In Guice servono due matcher: uno per specificare la classe e uno per specificare il metodo. • MethodInterceptor Eseguito all’invocazione di un metodo che risolve il match. Riceve informazioni sulla chiamata: il metodo, i suoi argomenti, e l’istanza dell’oggetto invocante. 23 Sandro Pedrazzini AOP Esempio • Riutilizzare l’esempio del pagamento con carta di credito già usato per DI • Eseguire durante l’esecuzione del pagamento un controllo attraverso AOP per verificare i giorni in cui può avvenire il pagamento • Implementato: specificare uno o più giorni in cui il servizio non può essere usato 24 Sandro Pedrazzini AOP Esempio (2) • Codice attuale public class BillingService implements IBillingService { … public Receipt chargeOrder(Order order, CreditCard card) throws Exception { ChargeResult result= fCreditCardProcessor.process(order.getAmount(), card); return ...; } } 25 Sandro Pedrazzini AOP Matcher • Definiamo un’annotation per specificare il Matcher. Servirà a segnalare il metodo interessato. @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface NotOnWeekends { } 26 Sandro Pedrazzini AOP Matcher (2) • Utilizzo dell’annotation public class BillingService implements IBillingService { … @NotOnWeekends public Receipt chargeOrder(Order order, CreditCard card) throws Exception { ChargeResult result= fCreditCardProcessor.process(order.getAmount(), card); return ...; } } 27 Sandro Pedrazzini AOP Interceptor public class WeekendBlocker implements MethodInterceptor { public Object invoke(MethodInvocation invocation) throws Throwable { Calendar today = new GregorianCalendar(); String todayDisplayName = today.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.LONG, Locale.ENGLISH); Order order = (Order) invocation.getArguments()[0]; if (todayDisplayName.equals(”Sunday")) { throw new IllegalStateException( invocation.getMethod().getName() + " for " + order.getAmount() + " not allowed on " + todayDisplayName); } return invocation.proceed(); } } 28 Sandro Pedrazzini AOP Interceptor (2) • Chiamata al metodo vero e proprio invocation.proceed(); • Accesso ai parametri invocation.getArguments() • Accesso alle informazioni del metodo invocation.getMethod() 29 Sandro Pedrazzini AOP Modulo di bind • Guice utilizza un modulo per specificare i vari binding public class NotOnWeekendsModule extends AbstractModule { protected void configure() { bindInterceptor(Matchers.any(), Matchers.annotatedWith(NotOnWeekends.class), new WeekendBlocker()); bind(ITransactionLog.class). toProvider(DatabaseTransactionProvider.class); bind(ICreditCardProcessor.class). to(PaypalCreditCardProcessor.class); bind(IBillingService.class).to(BillingService.class); } } 30 Sandro Pedrazzini AOP Modulo di bind (2) Match con ogni classe bindInterceptor( Solo i metodi con questa annotation Matchers.any(), Matchers.annotatedWith(NotOnWeekends.class), new WeekendBlocker()); Oggetto contenente il metodo interceptor 31 Sandro Pedrazzini AOP Main public class Main{ public static void main(String[] args) { Injector injector = Guice.createInjector(new NotOnWeekendsModule()); … IBillingService billingService = injector.getInstance(IBillingService.class); … } } 32 Sandro Pedrazzini AOP Esempio 2: security • Controllo se un certo utente cerca di eseguire una determinata azione e se ha il diritto di farlo • Le azioni che un utente può eseguire vengono specificate seguendo un approccio che utilizza ruoli • Specifichiamo con annotation quali ruoli hanno diritto ad eseguire una determinata azione 33 Sandro Pedrazzini AOP Esempio 2: security (2) • Un interceptor viene creato per verificare se l’utente che chiama l’azione ha il ruolo necessario (in un caso reale, ruolo e azione sono legati con permission, perciò andrebbero verificate anche queste) • User manager che mantiene le informazioni sull’utente registrato: public interface IUserManager { void setCurrentUser(User user); User getCurrentUser(); } 34 Sandro Pedrazzini AOP User Manager • All’interno di un’applicazione multiutente lo user manager utilizzerebbe la sessione per gestire gli utenti registrati • In un’applicazione desktop, invece, basta un singleton • Lo user possiamo rappresentarlo come una semplice classe che gestisce il nome dell’utente (informazione minima) e un set di ruoli appartenenti all’utente 35 Sandro Pedrazzini AOP User public class User { private String fName; private Set<Role> fRoles; public User(String name, Set<Role> roles) { fName = name; fRoles = roles; } public String getName() { return fName; } public Set<Role> getRoles() { return fRoles; } … } 36 Sandro Pedrazzini AOP Ruoli • Per specificare ruoli in modo semplice e controllato, usiamo una enumeration public enum Role { CUSTOMER, EMPLOYEE } 37 Sandro Pedrazzini AOP Annotazione delle azioni • Le annotation ci permettono di segnalare quali ruoli sono richiesti per eseguire una certa operazione • Usata per segnalare un metodo e accessibile a runtime • value() serve a specificare il ruolo necessario @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface RequiresRole { Role value(); } 38 Sandro Pedrazzini AOP Azioni • Supponendo ora di avere una classe con delle azioni da eseguire, le annotation ci permettono di specificare con quale ruolo public class VideoRental { … @RequiresRole(Role.CUSTOMER) ... @RequiresRole(Role.EMPLOYEE) … } 39 Sandro Pedrazzini AOP Azioni (2) public class VideoRental { @Inject IUserManager fUserManager; @RequiresRole(Role.CUSTOMER) public void rentMovie(long movieId) { System.out.println(String.format( "Movie %d rented by user %s.”, movieId, fUserManager.getCurrentUser())); } @RequiresRole(Role.EMPLOYEE) public void registerNewMovie(String name) { System.out.println(String.format( "New movie \"%s\" registered by user %s.", name, fUserManager.getCurrentUser())); } } 40 Sandro Pedrazzini AOP Interceptor • A questo punto ci vuole un interceptor che faccia uso delle informazioni specificate dalle annotation per verificare i ruoli public class RoleValidationInterceptor implements MethodInterceptor { @Inject private IUserManager fUserMgr; public Object invoke(MethodInvocation invocation) throws Throwable { Role requiredRole = invocation.getMethod().getAnnotation(RequiresRole.class).value(); if (fUserMgr.getCurrentUser() == null || !fUserMgr.getCurrentUser().getRoles().contains(requiredRole)){ throw new IllegalStateException(”…”); } return invocation.proceed(); } } 41 Sandro Pedrazzini AOP Module • Bisogna inoltre specificare quando viene usato l’interceptor public class ExampleModule extends AbstractModule { public void configure() { bind(IUserManager.class).to(UserManager.class).in(Scopes.SINGLETON); RoleValidationInterceptor roleValidationInterceptor = new RoleValidationInterceptor(); bindInterceptor(any(), annotatedWith(RequiresRole.class), roleValidationInterceptor); //necessario, per risolvere @Inject in rileValidationInterceptor requestInjection(roleValidationInterceptor); } } 42 Sandro Pedrazzini AOP Module (2) • Prima di tutto associamo un’implementazione di IUserManager all’interfaccia. Definendola come singleton, specifichiamo che lo stesso oggetto usato in VideoRental venga usato anche in RoleValidationInterceptor • In seguito associamo l’interceptor a tutti i metodi annotati con RequiresRole • L’ultimo passaggio è necessario per iniettare nell’interceptor le sue dipendenze (nel nostro caso con UserManager) 43 Sandro Pedrazzini AOP Creazione di un framework AOP 44 Sandro Pedrazzini AOP