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
Scarica

AOP - Supsi