I. What is Aspect-Oriented Software Developement? A. The problem, tangled code ------------------------------------------ THE PROBLEM CROSS-CUTTING CONCERNS AND TANGLING Some concerns cut across all modules: These have to be handled in many modules, resulting in tangled code ------------------------------------------ Other things like this? Why is it bad to handle this all over the code? B. Avoiding the problem 1. watching changes (e.g., logging, tracing, ...) ------------------------------------------ AVOIDING THE PROBLEMS (1) Observer pattern: Every type (Subject) that does logging instead implements: - protocol for managing listeners - notify method to call "actionPerformed" when something should be logged - calls notify when logging should happen But: ------------------------------------------ What problems would this cause? ------------------------------------------ WHAT'S NEEDED TO SUPPORT SPECTATORS Want to: - avoid the need for subjects to manage listeners - avoid the need for subjects to put in calls to notify - only pay at run-time when there are listeners ------------------------------------------ ------------------------------------------ JOIN POINTS In AspectJ, a *join point* is an event that occurs at every: - method call, constructor call - method/constructor body execution - initialization of an object - static initialization of a class - get of a field - set of a field - catch block handling an exception POINTCUTS In AspectJ, a *pointcut* is a set of join points. ------------------------------------------ What's the difference between a join point and a pointcut? ------------------------------------------ ADVICE In AspectJ, *advice* is code that is attached to a pointcut and can be run: - before - after each joinpoint in the pointcut. ------------------------------------------ ------------------------------------------ EXAMPLE (Kiselev listing 7.6) public aspect RigidLogger { pointcut logPoint() : execution(* tags.*.*(..)); before() : logPoint() { Init.log(thisJoinPoint); } } ------------------------------------------ 2. assisting at run-time (cacheing, buffering, pooling, authentication, ...) ------------------------------------------ AVOIDING THE PROBLEMS (2) Changing performance by cacheing (or buffering, pooling, etc.): Instead of using the raw type, use a proxy for it that: - contains an object of the raw type - delegates to it for basic functions - does the necessary cacheing, etc. by intercepting calls But: ------------------------------------------ ------------------------------------------ WHAT'S NEEDED TO SUPPORT ASSISTANCE Want to: - avoid runtime overhead for delegation when not needed - avoid the use of decorators to deal with layers of proxies - avoid spreading concerns across multiple proxy types - avoid the need to change existing references to point to proxies ------------------------------------------ ------------------------------------------ AROUND ADVICE In AspectJ, *around advice* is advice that is run as follows: - it is called before a join point - it can decide to proceed (delegate) - when the proceed returns, it can perform more actions ------------------------------------------ ------------------------------------------ EXAMPLE (Kiselev, from listing 7.6) import java.sql.*; import java.util.*; public aspect ReadCache { public static Map cache = new Hashtable(); pointcut read(String user): call(Collection StoriesDb.retrieve(String)) && args(user); Collection around(String user) throws SQLException : read(user) { Collection res = (Collection)cache.get(user); if (null == res) { res = proceed(); cache.put(user, res); } return res; } } ------------------------------------------ What if multiple pieces of around advice apply at the same join point? C. The promise (quickly) ------------------------------------------ THE PROMISE OF AOSD Modular separation of concerns. Cross-cutting concerns confined to single modules. Systematic way to deal with non-functional requirements in design. Thus systems are: - easier to understand, - easier to change, adapt, evolve Can add spectators and change behavior without editing existing code. ------------------------------------------ What are the benefits of these benefits? D. Typical Use (skip) ------------------------------------------ TYPICAL USES OF AOSD Uses of AOSD: - modularizing large programs (new behaves as the old code) e.g., exception handling in telecom code - modularizing to add new behavior (superimpose new behavior on old code) e.g., add authentication to a program - support of product lines (new code is a variant in some aspects) e.g., add code to control new airplane - support performance improvements (treat special cases faster) e.g., bypass code for unused network layers ------------------------------------------ II. Aspect-Oriented Programming in the defined language A. Desugaring before and after into around ------------------------------------------ BEFORE AND AFTER ARE SUGARS before get X do E ==> around get X do E; proceed after get X do E ==> around get X do let r = proceed in begin E; r end (where r is not free in E) ------------------------------------------ B. syntax ------------------------------------------ CONCRETE SYNTAX ::= {}* "a-program (advice-defs run exp)" ::= around "around-adv (pcd do body)" ::= get "get-pcd (id-pat)" ::= "id-pat (id)" | * "wild-pat ()" ::= ... | proceed "proceed-exp ()" ------------------------------------------ C. examples 1. get advice ------------------------------------------ EXAMPLES OF GET ADVICE around get x do 0 run let x = 3 in x around get y do add1(proceed) run let y = 10 in +(y, y) around get z do add1(proceed) run let y = 10 in +(y, y) around get y do begin set y = add1(proceed); proceed end run let y = 10 in +(y, y) around get * do add1(proceed) run let y = 10 z = 5 in +(y, z) ------------------------------------------ D. informal semantics, examples ------------------------------------------ INFORMAL SEMANTICS Assuming: one piece of advice, and the idpattern doesn't occur in the advice around get X do A run E[[X]] ==> E[[let proceed$c = proc() X in A]] proceed ==> (proceed$c) Example (1): around get y do add1(proceed) run let y = 10 in +(y, y) ==> let y = 10 in +(let proceed$c = proc() y in add1((proceed$c)), let proceed$c = proc() y in add1((proceed$c))) Example (2): around get y do begin set y = add1(proceed); proceed end run let y = 10 in +(y, y) ==> let y = 10 in +(let proceed$c = proc() y in begin set y = add1((proceed$c)); (proceed$c) end, let proceed$c = proc() y in begin set y = add1((proceed$c)); (proceed$c) end) ------------------------------------------ E. implementation 1. abstract syntax ------------------------------------------ ABSTRACT SYNTAX (define-datatype program program? (a-program (advice-defs (list-of advice?)) (exp expression?))) (define-datatype advice advice? (around-adv (pcd pcd?) (body expression?))) (define-datatype pcd pcd? (get-pcd (id-pat idpattern?))) (define-datatype idpattern idpattern? (id-pat (id symbol?)) (wild-pat)) (define-datatype expression expression? ;... ;; visible to programmers (proceed-exp) ;; Not visible to programmers (unadvisable-var-exp (id symbol?)) (continue-weave-get-exp (remaining-advice (list-of expression?)) (base-exp expression?)) ) ------------------------------------------ 2. The interpreter a. Program and Advice table i. eval-program ------------------------------------------ THE INTERPRETER'S CHANGES ;; Added to track advice (deftype *advice-table* advice-table) (define *advice-table* (empty-advice-table)) (deftype eval-program (-> (program) Expressed-Value)) (define eval-program (lambda (pgm) (set! *advice-table* (empty-advice-table)) (cases program pgm (a-program (advice-defs body) (begin (for-each (lambda (adv) (add-advice! *advice-table* adv)) advice-defs) (eval-expression body (init-env))))))) ------------------------------------------ Why do we have to reset the table when eval-program is called? ii. advice table datatype ------------------------------------------ ADVICE TABLE DATATYPE (deftype empty-advice-table (-> () advice-table)) (deftype add-advice! (-> (advice-table advice) void)) (deftype around-advice-for-get (-> (advice-table symbol) (list-of expression))) ;; ... ------------------------------------------ What would be a good implementation of the advice table? b. eval-expression ------------------------------------------ CHANGES TO EVAL-EXPRESSION (deftype eval-expression (-> (expression environment) Expressed-Value)) (define eval-expression (lambda (exp env) (cases expression exp ; ... (var-exp (id) (let ((applicable-around-advice (around-advice-for-get *advice-table* id))) (dynamically-weave-around-get applicable-around-advice (unadvisable-var-exp id) env))) (unadvisable-var-exp (id) (apply-env env id)) (proceed-exp () (if (defined-in-env? env 'proceed) (let ((proceed-closure (expressed->procval (apply-env env 'proceed)))) (apply-procval proceed-closure '())) (eopl:error 'eval-expression (string-append "proceed may only be used" " from within around" " advice")))) (continue-weave-get-exp (remaining-advice base-exp) (dynamically-weave-around-get remaining-advice base-exp env)) ))) ------------------------------------------ What happens if you use a varref-exp instead of a unadvisable-varref-exp? So what happens to advise get x do 0 run let x = 3 in x ? ------------------------------------------ (deftype dynamically-weave-around-get (-> ((list-of expression) expression environment) Expressed-Value)) (define dynamically-weave-around-get (lambda (advice-exps base-exp env) (if (null? advice-exps) (eval-expression base-exp env) (eval-expression (car advice-exps) (extend-env '(proceed) (list (procval->expressed (closure '() (continue-weave-get-exp (cdr advice-exps) base-exp) env))) env))))) ------------------------------------------ F. discussion What kind of scoping is given to the patterns? Does that allow static type checking? How would we add more patterns? What happens if there is more than one piece of advice? G. handling set advice 1. concrete syntax ------------------------------------------ CONCRETE SYNTAX FOR SET ADVICE ::= | set "set-pcd (id-pat)" ::= ... | LHS "varassign-LHS-exp = (rhs-exp)" | RHS "RHS-exp ()" ------------------------------------------ 2. abstract syntax ------------------------------------------ (define-datatype pcd pcd? (get-pcd (id-pat idpattern?)) (set-pcd (id-pat idpattern?))) (define-datatype expression expression? ;... ;; visible to programmers (varassign-LHS-exp (rhs-exp expression?)) (RHS-exp) ;; not visible to programmers (unadvisable-varassign-exp (id symbol?) (rhs-exp expression?)) (continue-weave-set-exp (remaining-advice (list-of expression?)) (base-exp expression?) (id symbol?) (rhs-exp expression?)) ------------------------------------------ Why are the unadvisable-varassign-exp's needed? 3. implementation of eval-expression ------------------------------------------ (define eval-expression (lambda (exp env) (cases expression exp ; ... (continue-weave-set-exp (remaining-advice base-exp id rhs-exp) (dynamically-weave-around-set remaining-advice base-exp id rhs-exp env)) (varassign-exp (id rhs-exp) (let ((applicable-around-advice (around-advice-for-set *advice-table* id))) (dynamically-weave-around-set applicable-around-advice (unadvisable-varassign-exp id rhs-exp) id rhs-exp env))) (unadvisable-varassign-exp (id rhs-exp) (begin (setref! (apply-env-ref env id) (eval-expression rhs-exp env)) (number->expressed 1))) (varassign-LHS-exp (rhs-exp) (if (defined-in-env? env 'LHS) (let ((action-closure (apply-env env 'LHS)) (rhs-val (eval-expression rhs-exp env))) (apply-procval (expressed->procval action-closure) (list rhs-val))) (eopl:error 'eval-expression "LHS = can only be used in advice on set"))) (RHS-exp () (if (defined-in-env? env 'RHS) (apply-procval (expressed->procval (apply-env env 'RHS)) '()) (eopl:error 'eval-expression "RHS can only be used in advice on set"))) ))) ------------------------------------------ ------------------------------------------ (deftype dynamically-weave-around-set (-> ((list-of expression) expression symbol expression environment) Expressed-Value)) (define dynamically-weave-around-set (lambda (advice-exps base-exp id rhs-exp env) (if (null? advice-exps) (eval-expression base-exp env) (eval-expression (car advice-exps) (extend-env '(proceed LHS RHS) (list ;; Binding for proceed below. When set advice proceeds, ;; the interpreter find the closure bound to proceed, and ;; runs it, which runs the next advice or the base expression. (procval->expressed (closure '() (continue-weave-set-exp (cdr advice-exps) base-exp id rhs-exp) env)) ;; Binding for LHS below. When an expression of the form ;; set LHS = ;; is encountered, we find the closure below, and call it, ;; passing it the value of the given . ;; The closure below will assign that value to the saved LHS name. (procval->expressed (closure (list '$rhs) (unadvisable-varassign-exp id (unadvisable-var-exp '$rhs)) env)) ;; Binding for RHS below. When an expression of the form ;; RHS ;; is encountered, we find the closure below, and call it ;; to obtain the value of the saved rhs-exp. (procval->expressed (closure '() rhs-exp env))) env))))) ------------------------------------------